当前位置: 首页 > news >正文

Linux 网络编程:深入理解套接字与通信机制

在 Linux 系统中,网络通信是进程间交互的关键方式。Socket 编程作为其核心机制,广泛应用于客户端-服务器模型、分布式系统等场景。本文将简明介绍网络通信的基本概念,包括 IP 与端口的关系、TCP 与 UDP 协议差异、网络字节序的重要性,并深入解析 Socket 编程中常用的结构与接口,帮助你从原理到实践全面掌握 Linux 网络编程。

文章目录

  • 一、网络通信的本质:进程间通信
  • 二 理解IP地址和端口号
  • 三、端口号与进程 ID(PID)的区别
    • 3.1端口号与进程 ID | 解耦合
    • 3.2 常见端口使用问题解析
  • 四、TCP和UDP协议分析
    • 4.1 “TCP”与“UDP"”
    • 4.2 “有连接”与“无连接”
    • 4.3 为什么互联网仍然需要 UDP
  • 五、 网络字节序(Network Byte Order)
    • 5.1 网络中的字节地址定义
    • 5.2 为什么需要网络字节序?
  • 六、 socket编程
    • 6.1 sockaddr 结构
    • 6.2 套接字地址结构的设计与关系
    • 6.3 套接字地址结构详解:`sockaddr`、`sockaddr_in` 与 `sockaddr_un`
    • 6.4 in_addr 结构
  • 七、Socket 接口概览
    • 7.1 创建 Socket
    • 7.2 绑定地址和端口(bind)【服务器端】
    • 7.3 启动监听(listen)
    • 7.4 接受连接(accept)
    • 7.5 发起连接(connect)
    • 7.6 设置套接字选项(setsockopt)
    • 7.7 地址转换函数(inet_pton / inet_ntop)
    • 7.8 数据传输函数
      • TCP
      • UDP
    • 7.9 使用建议与注意事项

一、网络通信的本质:进程间通信

问题】:两台机器进行通信,真的是“机器在通信”吗?

在这里插入图片描述

实际上,并不是物理意义上的主机在通信,而是运行在主机上的进程在通过网络进行数据交换。也就是说,当我们打开 QQ、微信等聊天工具,实际收发数据的是软件进程,而不是“主机”本身。

网络协议栈中,传输层以下负责数据的可靠传输,而应用层的进程才是通信的真正参与者。因此,从本质上讲,网络通信即是跨主机的进程间通信

二 理解IP地址和端口号

要实现两个进程之间的通信,系统必须准确地找到通信双方。这就依赖于两个核心信息:

  • 【IP 地址】:用于在网络中唯一标识某台主机,相当于“找到哪台机器”;
  • 【端口号】:用于在主机内唯一标识某个进程,相当于“找到哪个程序”。

端口号是传输层协议(如 TCP、UDP)的一部分,占 16 位(即 2 字节),可用范围为 0 到 65535。

系统规定:一个端口只能被一个进程绑定,而一个进程可以绑定多个端口号,从而支持多个通信服务。

因此,**“IP + 端口号的组合”**才能唯一标识网络中某一进程,是通信的基本寻址单位。

三、端口号与进程 ID(PID)的区别

3.1端口号与进程 ID | 解耦合

我们已经有了进程 ID(PID),为什么还需要端口号?这是因为两者的设计目的和使用场景完全不同:

  • PID:是由操作系统内核分配,用于操作系统内部管理进程;
  • 端口号 :是网络通信的标识符,属于协议栈的一部分,只在进行**“网络通信”**时才被使用。

如果强行使用 PID 进行网络识别,会导致系统实现与网络协议强耦合,带来设计复杂性。再者,并不是所有进程都进行网络通信,因此只有需要进行网络通信的进程才会申请端口号

在这里插入图片描述

就像一个人有身份证(PID),但在学校有学号,在公司有工号(端口号),分别用于不同场景下的身份识别。

3.2 常见端口使用问题解析

  1. 客户端如何知道服务端的端口号?

一般是通过约定俗成的方式,比如 HTTP 使用端口 80,HTTPS 使用 443,FTP 使用 21,客户端程序中通常已经内置这些端口号。

  1. 一个进程可以绑定多个端口号吗?

可以。比如一个 Web 服务既监听 80 端口处理 HTTP,又监听 443 端口处理 HTTPS。

  1. 一个端口号可以绑定多个进程吗?

不可以。端口号的作用就是唯一标识进程,如果多个进程同时绑定同一端口,会导致冲突,系统将拒绝该操作。

综上所述,网络通信的真正参与者是进程,通信发生在不同主机的进程之间;IP 地址负责找到哪台主机,端口号负责确定主机中的哪个进程;PID 与端口号面向的体系不同,不能混用。最终,IP 地址 + 端口号才是网络通信中精确标识通信双方的完整单位。

四、TCP和UDP协议分析

4.1 “TCP”与“UDP"”

在传输层,**TCP(Transmission Control Protocol,传输控制协议)UDP(User Datagram Protocol,用户数据报协议)**是两种最基础也最常见的协议。它们分别适用于不同场景,具有显著的结构和特性差异:

特性TCPUDP
协议类型传输层协议传输层协议
连接机制有连接(需要三次握手)无连接
可靠性可靠传输(确保不丢、不重、按序)不可靠传输(不保证送达、顺序)
数据组织面向字节流(数据连续)面向数据报(按报文传输)

4.2 “有连接”与“无连接”

有连接和无连接的区别,可以通过生活中的沟通方式来理解:

  • 【TCP 的“有连接”】:就像我们打电话聊天,在开始通话前会说“喂”,彼此确认接通之后才开始交谈。这个“建立连接”的过程确保双方的通信是可靠的、有保障的。
  • 【UDP 的“无连接”】:则像是寄信或发邮件,写好一封信直接投递出去,不会事先确认对方是否在线、是否能收到。只管发出去,至于是否送达、对方是否收到,不在考虑范围之内。

4.3 为什么互联网仍然需要 UDP

初看之下,TCP 提供连接、保证可靠传输、顺序正确,似乎全面优于 UDP,但这并不代表 UDP 就“差”。实际上,二者的设计目标不同。在计算机世界中,术语如“可靠”与“不可靠”并不代表好坏,仅仅是中性地描述特征

就像化学中“惰性气体”不是贬义词,它只是描述了一种“不会轻易参与反应”的状态。

可靠通信的前提 | 网络可达

无论 TCP 多么可靠,都依赖一个前提条件:网络必须连通。如果基础的网络连接不可达,即使协议再“可靠”,也无法传输任何数据。因此,TCP 的重传机制并不是“万能保险”,它只是在可达前提下尽力保障交付的机制

特性维度TCP(Transmission Control Protocol)UDP(User Datagram Protocol)
连接方式有连接(需三次握手)无连接
可靠性保障确认应答机制,支持重传、排序不确认、不重传、不排序
状态维护需维护连接状态(含会话信息)无需维护连接状态
数据传输方式面向字节流(连续传输)面向数据报(按报文传输)
资源开销高:需要缓存、控制流、序列号管理等低:无需缓存和控制
适用场景对可靠性要求高,如文件传输、网页浏览对时效性要求高,如视频通话、DNS 查询
传输效率相对较低(因控制机制带来延迟)较高(结构轻便)
数据顺序保证顺序到达

五、 网络字节序(Network Byte Order)

在网络通信中,多字节数据的顺序问题至关重要。就像内存和磁盘文件中存在**大端(Big-Endian)小端(Little-Endian)**之分,网络传输的数据流同样有顺序问题。

5.1 网络中的字节地址定义

  • 【发送主机】:按内存地址从低到高的顺序发送缓冲区中的数据;
  • 【接收主机】:按接收到的字节顺序从低地址开始保存到接收缓冲区。

因此,在网络中,先发出的数据为低地址,后发出的数据为高地址

TCP/IP 协议规定】:网络字节序必须为大端序(高字节在前,低字节在后)

这意味着

  • 不管主机本身是大端机还是小端机;
  • 发送时都要按大端序发送
  • 小端机器需转换为大端,才能发送数据。

5.2 为什么需要网络字节序?

早期不同计算机架构各自采用不同字节序(有的使用大端,有的使用小端),但在单机运行中影响不大。然而,网络通信要 求通信双方对数据格式达成一致

后果:如果发送方是大端,接收方是小端,解析方式不同将导致数据错误。

字节序转换函数(C语言)】:

为实现跨平台通信,系统提供了一套 API 来进行主机字节序和网络字节序的转换:

在这里插入图片描述

这些函数在:

  • 主机为小端时 → 会进行字节序转换;
  • 主机为大端时 → 直接返回原值,无需转换。

六、 socket编程

**Socket(套接字)**是进程间通过网络通信的重要接口,以下是常用 API:

// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,
socklen_t address_len);// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,
socklen_t* address_len);// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);

6.1 sockaddr 结构

Socket 作为通信的端点,可以分为以下几种:

套接字类型描述常用结构体
域间套接字本机内部进程通信(Unix Domain Socket)struct sockaddr_un
原始套接字主要用于网络工具、诊断,绕过传输层直接封装一般自定义
网络套接字用户间网络通信struct sockaddr_in

在这里插入图片描述

虽然不同套接字使用不同的结构体,但系统接口需要统一类型,C语言在设计 Socket 接口时选择统一以 struct sockaddr 类型接收参数

为何不用 void* 统一类型

虽然在现代 C 中 void* 可接受任意指针,但:

  • Socket 接口设计早于 void\* 被纳入标准
  • 一旦接口发布,后续难以修改;
  • 所以统一使用 sockaddr 类型是历史原因+结构设计的平衡选择。

6.2 套接字地址结构的设计与关系

在套接字通信中,不同的通信方式(如本地通信与网络通信)需要不同格式的地址信息。为此,系统定义了多个结构体来封装这些地址:

  • sockaddr_in:用于基于 IP 的网络通信(如 TCP/UDP),包含 IP 地址与端口号;
  • sockaddr_un:用于本地通信(UNIX 域套接字),通过文件路径标识通信端点;

为了统一处理这些不同的地址格式,套接字接口引入了一个通用结构体:

  • sockaddr:通用地址结构体,用作各种具体地址类型的统一“入口”。

在实际使用中,sockaddr 并不会直接存储地址信息,而是作为指针指向具体的地址结构,如 sockaddr_insockaddr_un,并通过强制类型转换进行传递。

这种设计类似于面向对象编程中的“多态”:
sockaddr 相当于一个“基类”,而 sockaddr_insockaddr_un 则是“派生类”。套接字函数统一接受 sockaddr* 类型的参数,通过内部判断其真实类型进行处理。

具体的类型识别依赖于结构体中的前几个字节,尤其是前16位中的地址族字段(如 AF_INETAF_UNIX,以此区分使用的是哪种地址结构。

6.3 套接字地址结构详解:sockaddrsockaddr_insockaddr_un

在使用套接字进行通信时,不同的通信方式(如网络通信、本地通信)需要不同格式的地址结构。为了统一接口、提升兼容性,POSIX 提供了一套通用与专用地址结构的设计体系。

1️⃣ 通用地址结构:struct sockaddr

struct sockaddr {__SOCKADDR_COMMON(sa_); /* 公共部分:包括地址族(sa_family)等 */char sa_data[14];       /* 占位地址数据 */
};
  • sockaddr 是所有地址结构的“父类型”,用于作为套接字函数(如 bindconnect)的参数类型;
  • 实际传入的是具体的地址结构(如 sockaddr_insockaddr_un),通过强制类型转换sockaddr* 使用;
  • 其核心字段是 sa_family,用于标识通信协议族,如 AF_INETAF_UNIX 等。

2️⃣ IPv4 网络通信地址结构:struct sockaddr_in

struct sockaddr_in {__SOCKADDR_COMMON(sin_);     /* 地址族 sin_family,通常为 AF_INET */in_port_t sin_port;          /* 端口号(网络字节序) */struct in_addr sin_addr;     /* IP 地址 */unsigned char sin_zero[sizeof(struct sockaddr) - __SOCKADDR_COMMON_SIZE - sizeof(in_port_t) - sizeof(struct in_addr)];
};
  • 用于 IPv4 网络通信,支持 TCP 和 UDP 协议;
  • 常用于指定远程主机的 IP 地址和端口;
  • sin_zero 仅用于对齐填充,不参与实际通信。

3️⃣ 本地通信(UNIX 域套接字)地址结构:struct sockaddr_un

struct sockaddr_un {__SOCKADDR_COMMON(sun_);  /* 地址族,通常为 AF_UNIX */char sun_path[108];       /* 表示文件路径(本地 socket 文件) */
};
  • 用于本地进程间通信(IPC),不依赖网络协议
  • 通过文件系统路径进行连接,适合同一台主机内的高效通信
  • sun_path 存储通信 socket 对应的路径,通常创建在 /tmp/run 等目录下。

4️⃣ 套接字地址结构的统一接口设计

sockaddr 作为通用地址结构,其首部包含 sa_family 字段(地址族)

不同通信方式使用不同的地址族:

地址族常量描述
AF_INETIPv4 网络通信
AF_INET6IPv6 网络通信
AF_UNIX本地通信(UNIX 域套接字)

通过这种统一设计,Socket API 可以使用同一个函数接口(如 bind、connect)处理不同通信方式。
开发者只需设置好地址结构及 sa_family 字段,套接字函数就能识别并执行对应的逻辑。

场景辅助】:使用 bind 绑定 IPv4 地址

#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>int main() {int sockfd = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(8080);addr.sin_addr.s_addr = INADDR_ANY;//类型强转变,统一接口进行处理bind(sockfd, (struct sockaddr *)&addr, sizeof(addr));printf("IPv4 bind 成功\n");close(sockfd);return 0;
}

6.4 in_addr 结构

定义如下

/* Internet address. */
typedef uint32_t in_addr_t;struct in_addr {in_addr_t s_addr; // 以32位整数表示的IPv4地址
}

在通信过程中,IPv4 地址通常以字符串形式(如 "192.168.1.1")输入,然后通过函数(如 inet_pton)转换为 in_addr_t 类型的数值进行使用。

场景辅助】:

#include <stdio.h>
#include <arpa/inet.h>int main() {struct in_addr addr;inet_pton(AF_INET, "192.168.1.1", &addr);printf("转换后的数值: 0x%x\n", ntohl(addr.s_addr));  // 输出十六进制形式return 0;
}

Socket API 通过 sockaddr 结构体,实现了对多种通信方式(如 IPv4、IPv6、本地 Unix 域套接字)的统一接口。这种设计理念类似于“多态”——即通过一个通用的接口,适配不同类型的地址结构,从而简化了开发者的编程模式,提高了 API 的通用性与扩展性。

七、Socket 接口概览

在网络编程中,Socket 是通信的基础接口,支持 TCP、UDP、Unix 域等通信方式。以下对核心接口及其使用流程进行系统性介绍。

7.1 创建 Socket

int socket(int domain, int type, int protocol);

功能:创建一个套接字,返回其文件描述符,失败返回 -1

参数说明

  • domain:协议族,如 AF_INET(IPv4)、AF_INET6(IPv6)、AF_UNIX(本地通信)。
  • type:套接字类型,如 SOCK_STREAM(TCP)、SOCK_DGRAM(UDP)。
  • protocol:使用的协议,通常设为 0(由系统自动匹配)

🔹示例:

int sockfd = socket(AF_INEF, SOCK_STREAM, 0)//// 创建 TCP 套接字

7.2 绑定地址和端口(bind)【服务器端】

int bind(int sockfd, const struct sockaddr *addr, socklen_t addlen);

功能:将套接字绑定到本地的 IP 地址和端口号。

参数说明

  • sockfd:由 socket() 创建的套接字描述符。
  • addr:指向本地地址结构体(如 sockaddr_in)的指针。
  • addrlen:地址结构体的大小(通常为 sizeof(struct sockaddr_in))。

🔹示例:

struct sockaddr_in addr;
addr.sin_family = AF_INEF;
addr.sin_port = htons(8000);
addr.sin_addr.s_addr = INADDR_ANY;
bind(sockfd,(struct socketaddr *)&addr, sizeof(addr));

7.3 启动监听(listen)

int listen(int sockfd, int backlog);

功能:将套接字设置为监听状态,用于接收客户端连接。

参数说明

  • sockfd:已绑定地址的套接字。
  • backlog:挂起连接队列的最大长度。

🔹示例:

listen(sockfd, 10);  // 最多允许 10 个客户端排队等待连接

7.4 接受连接(accept)

int accept(int sockfd,struct sockaddr* addr,socklen_t * addrlen);

功能:从已完成连接的队列中接受一个连接,返回新的通信套接字。

参数说明

  • sockfd:监听套接字。
  • addr:客户端地址结构体的存储位置。
  • addrlen:地址结构体大小指针,用于返回实际长度。

🔹示例:

struct sockaddr_in client_addr;
socklen_t len = sizeof(client_addr);
int connfd = accept(sockfd,(struct sockaddr*)&client_addr,&len);

7.5 发起连接(connect)

int connent(int sockfd,const struct sockaddr* addr,socklen_t addrlen);

功能:客户端向服务器发起连接请求。

参数说明

  • sockfd:客户端的套接字。
  • addr:服务器地址结构体。
  • addrlen:地址结构体的长度。

🔹示例:

struct sockaddr_in serve_addr;
serv_addr.sin_family = AF_INEF;
serv_addr.sin_port = htons(8080);
inet_pton(AF_INEF, "127.0.0.1",&serve_addr.sin_addr);
connect(sockfd, (strcut sockaddr*)&serv_addr, sizeof(serv_addr));

7.6 设置套接字选项(setsockopt)

int setsockopt(int sockfd, int level, int optname, const void* optval, socklen_t optlen);

功能:设置套接字的选项参数,如端口复用、超时等。

参数说明

  • sockfd:套接字描述符。
  • level:选项所在协议层(如 SOL_SOCKET)。
  • optname:选项名称(如 SO_REUSEADDR)。
  • optval:选项值的指针。
  • optlen:选项值大小。

🔹示例:

int opt = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

7.7 地址转换函数(inet_pton / inet_ntop)

int inet_pton(int af, const char* str, void* dst);
const char *inet_ntop(int af, const void* src, char *dst, socklen_t size);

功能

  • inet_pton:将 IP 字符串转换为网络字节序二进制地址。
  • inet_ntop:将网络地址转换为可读的字符串格式。

参数说明

  • af:地址族(如 AF_INET)。
  • src / dst:源字符串 / 目标结构体。
  • size:输出缓冲区大小。

🔹示例:

struct in_addr addr;
inet_pton(AF_INEF, "127.0.0.1",&addr);char ipstr[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &addr, ipstr, sizeof(ipstr));

7.8 数据传输函数

TCP

ssize_t send(int sockfd, const void *buf, size_t len, int flags);ssize_t recv(int sockfd, void *buf, size_t len, int flags);

功能:在 TCP 套接字上发送/接收数据。

参数说明(通用)

  • sockfd:通信套接字。
  • buf:数据缓冲区。
  • len:数据长度。
  • flags:传输选项,通常为 0

🔹示例

send(sockfd, "Hello", 5, 0);recv(sockfd, buffer, sizeof(buffer), 0);

UDP

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);

🔹示例

sendto(sockfd, "Ping", 4, 0, (struct sockaddr*)&addr, sizeof(addr));recvfrom(sockfd, buffer, sizeof(buffer), 0, NULL, NULL);

7.9 使用建议与注意事项

INADDR_ANY:服务器绑定所有本地地址

addr.sin_addr.s_addr = INADDR_ANY;

允许绑定所有网卡接口,适用于服务端程序监听任意 IP。

[监听套接字 vs 通信套接字]

  • socket()bind()listen() 创建监听套接字
  • accept() 接收到的返回值是通信套接字,用于客户端交互
http://www.dtcms.com/a/360371.html

相关文章:

  • 【MySQL自学】SQL语法全解(上篇)
  • Matlab自学笔记六十六:求解带参数的不等式
  • MySQL服务启动命令手册(Linux+Windows+macOS)(下)
  • 盛最多水的容器:双指针法的巧妙运用(leetcode 11)
  • ARM裸机开发(基础汇编指令)Day02
  • [特殊字符] Rust概述:系统编程的革命者
  • Python轻量化革命:用MicroPython构建边缘智能设备
  • JavaWeb01
  • Linux-驱动积累
  • 浅层与深层语义分析的NLP进化论
  • Trie树(静态数组实现)
  • 云渲染如何重新定义视觉艺术的边界
  • JS接口请求的基本方法
  • FastAPI 核心实战:精通路径参数、查询参数与数据交互
  • 第25章学习笔记|额外的提示、技巧与技术(PowerShell 实战版)
  • 蓓韵安禧活性叶酸源于上市企业生产
  • 网站漏洞早发现:cpolar+Web-Check安全扫描组合解决方案
  • 5w2h构建数据仓库与sow
  • H264几个参数说明
  • 大话 IOT 技术(4) -- 答疑篇
  • [光学原理与应用-355]:ZEMAX - 设置 - 系统检查与系统测试
  • k8s三阶段项目
  • 物理气相沉积(PVD)技术及应用现状和发展趋势
  • FreeRTOS深入理解
  • 数据库索引abc,请问查询哪些字段能命中索引
  • 平滑滤波器(Smooth Filter)的MATLAB与Verilog仿真设计与实现
  • 关于Ctrl+a不能全选的问题
  • 封装哈希表
  • 机器视觉opencv教程(四):图像颜色识别与颜色替换
  • 【开题答辩全过程】以 基于SpringBoot的流浪猫狗领养系统为例,包含答辩的问题和答案