一、套接字基本概念套接字Socket是计算机网络中用于进程间通信的一种机制通常用于不同主机之间的数据传输。它抽象了底层网络协议的细节为应用程序提供统一的接口。套接字是IP地址与端口号的组合用于唯一标识网络中的一个通信端点。二、套接字工作原理套接字通过绑定特定的IP地址和端口号实现数据的发送和接收。通信双方各自创建一个套接字并通过网络协议如TCP或UDP建立连接。数据通过套接字进行传输发送方将数据写入套接字接收方从套接字读取数据。三、协议认识-UDP/TCPUDP和TCP特点特性TCP传输控制协议UDP用户数据报协议连接性面向连接三次握手建立连接四次挥手断开无连接无需握手直接发数据可靠性可靠传输保证数据不丢、不重、有序到达不可靠传输不保证送达可能丢包 / 乱序有序性保证数据按发送顺序到达有序列号机制不保证有序先发的可能后到重传机制丢包会重传超时重传、快速重传无重传丢包就丢了不处理流量控制有滑动窗口机制避免发送方发太快压垮接收方无发送方只管发不管接收方能不能处理拥塞控制有慢启动、拥塞避免等适配网络拥堵无网络再堵也照常发数据边界无流式数据比如发 2 次 10 字节接收可能一次收 20 字节有数据报边界发 1 次 10 字节接收只能收 10 字节开销高头部 20~60 字节加握手 / 重传 / 控流等逻辑低头部仅 8 字节无额外控制逻辑速度慢可靠性和控流导致延迟高快无额外开销延迟低适用场景对可靠性要求高的场景对速度 / 实时性要求高的场景前置addrsock结构体struct sockaddr { unsigned short sa_family; // 地址族如 AF_INET、AF_INET6 char sa_data[14]; // 协议特定的地址信息 }; /* sa_family指定地址族常见值包括 AF_INETIPv4 地址。 AF_INET6IPv6 地址。 AF_UNIXUnix 域套接字。 sa_data存储具体的地址和端口信息格式由协议族决定。 */sockaddr是网络编程中用于表示套接字地址的通用结构体定义在sys/socket.h头文件中。它通常用于存储 IP 地址和端口信息支持多种协议族如 IPv4、IPv6 等。结构体常见派生类sockaddr_inIPv4struct sockaddr_in { short sin_family; // 地址族AF_INET unsigned short sin_port; // 端口号网络字节序 struct in_addr sin_addr; // IPv4 地址 char sin_zero[8]; // 填充字段未使用 };sockaddr_in6IPv6struct sockaddr_in6 { unsigned short sin6_family; // 地址族AF_INET6 unsigned short sin6_port; // 端口号网络字节序 unsigned long sin6_flowinfo; // 流信息 struct in6_addr sin6_addr; // IPv6 地址 unsigned int sin6_scope_id; // 作用域 ID };注意事项字节序端口和 IP 地址需转换为网络字节序大端。地址族匹配确保sa_family与使用的套接字类型一致。通用性sockaddr设计为通用结构体实际使用时应优先选择具体的派生结构体。UDP相关接口认识UDP 作为无连接、轻量级的传输层协议其编程接口相比 TCP 更简洁核心就是socket()、bind()、sendto()、recvfrom()四个接口。1. 字节序转换核心接口htons/htonl/ntohs/ntohl功能定位网络协议规定所有传输的多字节数据如端口、32 位 IP 整数必须使用网络字节序大端序而不同机器的本地字节序可能是小端主流或大端因此在绑定 / 指定地址时必须通过这些接口完成本地序 ↔ 网络序的转换否则会出现端口 / IP 解析错误。接口说明uint16_t htons(uint16_t hostshort)Host to Network Short将 16 位本地序端口号转为网络序UDP/TCP 端口都是 16 位必用uint32_t htonl(uint32_t hostlong)Host to Network Long将 32 位本地序 IP 整数转为网络序如INADDR_ANY转换uint16_t ntohs(uint16_t netshort)Network to Host Short将 16 位网络序端口号转回本地序接收数据后解析端口时用uint32_t ntohl(uint32_t netlong)Network to Host Long将 32 位网络序 IP 整数转回本地序极少用通常用inet_ntop直接转字符串。核心使用场景绑定端口local_addr.sin_port htons(8080);指定目标端口server_addr.sin_port htons(8080);绑定所有网卡local_addr.sin_addr.s_addr htonl(INADDR_ANY);解析接收的客户端端口ntohs(client_addr.sin_port);2.int socket(int domain, int type, int protocol);功能定位这是网络编程的第一步作用是创建一个套接字描述符简称 sockfd相当于给程序分配一个专属的 “通信管道”后续所有的发送、接收操作都要基于这个 fd 完成。参数解读domain指定通信使用的地址族决定网络协议版本。日常开发中最常用的是AF_INET对应 IPv4 协议如果需要支持 IPv6 则用AF_INET6。type指定套接字的通信类型UDP 必须用SOCK_DGRAM数据报类型这个参数直接决定了通信是无连接、有数据边界的 UDP 模式如果填SOCK_STREAM则是 TCP 模式。protocol指定具体的传输协议通常填 0 即可 —— 系统会根据前面的type参数自动匹配对应的协议比如SOCK_DGRAM对应 UDPSOCK_STREAM对应 TCP无需手动指定IPPROTO_UDP或IPPROTO_TCP。返回值成功时返回一个非负整数就是套接字 fd可理解为 “管道编号”失败时返回 -1此时可以用perror(socket)打印具体的错误原因比如权限不足、参数填错等。3.int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);功能定位把创建好的套接字 fd 绑定到本地的 IP 地址和端口号上。对 UDP 来说服务端必须调用 bind 绑定固定端口比如 8080否则客户端无法找到对应的服务端客户端则可选调用 —— 如果不绑定系统会在发送数据时自动分配一个随机端口。参数解读sockfd要绑定的套接字 fd必须是socket()调用成功返回的有效 fd且未被关闭。addr指向存储本地地址信息的结构体虽然参数类型是通用的struct sockaddr但实际开发中我们会用更贴合 IPv4 的struct sockaddr_in结构体传参时强转为struct sockaddr*即可。这个结构体需要提前初始化包括地址族和socket()的domain一致、端口号必须转网络序、IP 地址通常填INADDR_ANY表示绑定本机所有网卡一般不进行手动绑定特定IP。addrlen地址结构体的长度直接填sizeof(struct sockaddr_in)就能满足 IPv4 场景的需求。4.ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);功能定位UDP 最核心的发送接口作用是把缓冲区里的数据发送到指定的目标地址对方的 IP 端口。因为 UDP 是无连接的所以每次发送都需要明确指定目标地址这也是它和 TCP 的send()最核心的区别。参数解读sockfd发送数据用的套接字 fd需是有效且未关闭的。buf指向要发送的数据缓冲区比如存储字符串、字节数组的内存地址这个参数是只读的函数不会修改缓冲区内容。len要发送的数据长度单位是字节比如发送字符串时可用strlen(buf)发送字节数组则填数组的实际长度。flags发送标志位日常开发中填 0 即可表示阻塞发送直到数据发出或出错如果需要非阻塞发送可填MSG_DONTWAIT但新手建议先掌握默认的阻塞模式。dest_addr指向目标地址的结构体和bind()的addr类似需提前初始化对方的 IP 地址和端口号端口同样要转网络序。addrlen目标地址结构体的长度填sizeof(struct sockaddr_in)即可。返回值成功时返回实际发送的字节数UDP 特性要么发送全部数据要么失败不会出现发送部分数据的情况失败时返回 -1常见错误有网络不可达、目标地址错误、fd 无效等。5.ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);功能定位UDP 核心的接收接口作用是从套接字中读取接收到的数据同时获取发送方的地址IP 端口—— 这也是 UDP 无连接特性的体现接收数据时才知道数据是谁发的。参数解读sockfd接收数据用的套接字 fd需是有效且绑定了端口的服务端必须 bind客户端若未 bind 则系统自动分配端口。buf指向存储接收数据的缓冲区函数会把接收到的数据写入这个缓冲区需提前分配足够的内存比如定义char buf[1024]。len缓冲区的最大长度避免数据溢出比如sizeof(buf)。flags接收标志位默认填 0阻塞接收直到有数据到来MSG_DONTWAIT同样用于非阻塞接收。src_addr指向存储发送方地址的结构体调用后会自动填充对方的 IP 和端口方便后续回复数据。addrlen传入传出参数调用前要初始化为地址结构体的长度比如sizeof(struct sockaddr_in)函数会根据实际地址长度修改这个值因此必须传指针。返回值成功时返回实际接收的字节数失败时返回 -1常见错误有 fd 被关闭、缓冲区过小但不会溢出只会截断数据等如果是非阻塞模式无数据时也会返回 -1需结合errno判断。UDP套接字创建流程-client-server通信实现创建UDP套接字实现网络通信的步骤简单并且公式化server端创建套接字-绑定端口-循环收取数据#include stdio.h #include string.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h #define PORT 8888 #define BUF_LEN 1024 int main() { // 1. 创建UDP套接字socket(AF_INET, SOCK_DGRAM, 0) int sockfd socket(AF_INET, SOCK_DGRAM, 0); // 2. 绑定地址bind(sockfd, addr, sizeof(addr)) struct sockaddr_in serv_addr; memset(serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family AF_INET; serv_addr.sin_addr.s_addr htonl(INADDR_ANY); // 网络序转换htonl() serv_addr.sin_port htons(PORT); // 网络序转换htons() bind(sockfd, (struct sockaddr*)serv_addr, sizeof(serv_addr)); // 3. 接收数据recvfrom(sockfd, buf, len, 0, cli_addr, len) char buf[BUF_LEN]; struct sockaddr_in cli_addr; socklen_t cli_len sizeof(cli_addr); while (1) { int n recvfrom(sockfd, buf, BUF_LEN, 0, (struct sockaddr*)cli_addr, cli_len); buf[n] \0; printf(收到客户端[%s:%d]%s\n, inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port), buf); } close(sockfd); return 0; }client端创建套接字-指定服务端IP-发送数据#include stdio.h #include string.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h #define SERV_IP 127.0.0.1 #define SERV_PORT 8888 #define BUF_LEN 1024 int main() { // 1. 创建UDP套接字socket(AF_INET, SOCK_DGRAM, 0) int sockfd socket(AF_INET, SOCK_DGRAM, 0); // 2. 配置服务端地址 struct sockaddr_in serv_addr; memset(serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family AF_INET; serv_addr.sin_addr.s_addr inet_addr(SERV_IP); serv_addr.sin_port htons(SERV_PORT); // 网络序转换htons() // 3. 发送数据sendto(sockfd, buf, len, 0, serv_addr, sizeof(addr)) char buf[BUF_LEN] Hello UDP Server!; sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr*)serv_addr, sizeof(serv_addr)); printf(已发送%s\n, buf); close(sockfd); return 0; }TCP相关接口认识TCP 作为面向连接、可靠的传输层协议其编程接口相比 UDP 更复杂核心新增接口包含 listen ()、accept ()、connect ()、send ()、recv ()基础的 socket ()、bind () 及字节序转换接口与 UDP 通用不再重复。1.int listen(int sockfd, int backlog);功能定位服务端调用将已绑定的套接字从 “主动态” 转为 “被动态”使其监听客户端的连接请求。TCP 是面向连接的协议服务端必须先监听端口才能接收客户端的连接。参数解读sockfd已绑定地址的套接字 fd由 socket () 创建、bind () 绑定backlog指定内核为该套接字维护的 “未完成连接队列 已完成连接队列” 的最大长度如 5、10超出的连接请求会被拒绝新手填 5 即可满足基础场景。返回值成功返回 0失败返回 -1可通过 perror (listen) 排查错误如 fd 未绑定、参数非法。2.int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);功能定位服务端阻塞等待客户端的连接请求当有客户端发起连接三次握手完成会创建一个新的套接字 fd用于与该客户端通信原监听 fd 继续监听其他客户端。参数解读sockfd监听状态的套接字 fdlisten () 调用后的 fdaddr指向存储客户端地址信息的结构体调用后会自动填充客户端的 IP 和端口可传 NULL 表示不关心客户端地址addrlen传入传出参数调用前初始化为地址结构体长度sizeof (struct sockaddr_in)调用后会更新为实际地址长度必须传指针。返回值成功返回新的通信套接字 fd用于和该客户端收发数据失败返回 -1如 fd 未监听、被信号中断。3.int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);功能定位客户端调用主动向服务端发起 TCP 连接触发三次握手。只有连接建立成功后才能通过该套接字收发数据。参数解读sockfd客户端创建的套接字 fdsocket () 返回addr指向服务端地址结构体需初始化服务端的 IP 和端口端口转网络序addrlen服务端地址结构体的长度sizeof (struct sockaddr_in)。返回值成功返回 0三次握手完成连接建立失败返回 -1如服务端未监听、网络不可达、端口错误。4.ssize_t send(int sockfd, const void *buf, size_t len, int flags);功能定位TCP 核心发送接口向已建立连接的对端发送数据。因 TCP 是流式协议send () 可能返回小于 len 的值如内核缓冲区满需循环发送确保数据全部发出。参数解读sockfd已建立连接的通信套接字 fd客户端 connect () 成功后的 fd、服务端 accept () 返回的新 fdbuf要发送的数据缓冲区只读len要发送的数据长度字节flags默认填 0阻塞发送与 UDP sendto () 的 flags 用法一致如 MSG_DONTWAIT 为非阻塞。返回值成功返回实际发送的字节数可能小于 len失败返回 -1如连接断开、fd 无效。5.ssize_t recv(int sockfd, void *buf, size_t len, int flags);功能定位TCP 核心接收接口从已建立连接的套接字中读取数据。因 TCP 是流式无边界协议recv () 读取的字节数可能小于缓冲区长度需循环读取直到获取完整数据。参数解读sockfd已建立连接的通信套接字 fdbuf存储接收数据的缓冲区len缓冲区最大长度避免溢出flags默认填 0阻塞接收无数据时会阻塞等待。返回值成功返回实际接收的字节数返回 0 表示对端关闭连接四次挥手完成失败返回 -1如连接中断、fd 无效。TCP套接字创建流程-client-server通信实现TCP 通信需遵循 “服务端监听→客户端连接→双向收发” 的固定流程核心是三次握手建立连接、基于新 fd 通信。server 端创建套接字→绑定端口→监听端口→接受连接→循环接收数据#include stdio.h #include string.h #include unistd.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h #define PORT 8888 #define BUF_LEN 1024 int main() { // 创建TCP套接字 int listen_fd socket(AF_INET, SOCK_STREAM, 0); // 绑定地址 struct sockaddr_in serv_addr; memset(serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family AF_INET; serv_addr.sin_addr.s_addr htonl(INADDR_ANY); serv_addr.sin_port htons(PORT); bind(listen_fd, (struct sockaddr*)serv_addr, sizeof(serv_addr)); // 监听端口 listen(listen_fd, 5); // 接受连接 struct sockaddr_in cli_addr; socklen_t cli_len sizeof(cli_addr); int conn_fd accept(listen_fd, (struct sockaddr*)cli_addr, cli_len); printf(客户端[%s:%d]已连接\n, inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port)); // 接收数据 char buf[BUF_LEN]; while (1) { int n recv(conn_fd, buf, BUF_LEN-1, 0); if (n 0) { printf(客户端断开连接\n); break; } buf[n] \0; printf(收到客户端%s\n, buf); } close(conn_fd); close(listen_fd); return 0; }client 端创建套接字→连接服务端→发送数据#include stdio.h #include string.h #include unistd.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h #define SERV_IP 127.0.0.1 #define SERV_PORT 8888 #define BUF_LEN 1024 int main() { // 创建TCP套接字 int sockfd socket(AF_INET, SOCK_STREAM, 0); // 连接服务端 struct sockaddr_in serv_addr; memset(serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family AF_INET; serv_addr.sin_addr.s_addr inet_addr(SERV_IP); serv_addr.sin_port htons(SERV_PORT); connect(sockfd, (struct sockaddr*)serv_addr, sizeof(serv_addr)); // 发送数据 char buf[BUF_LEN] Hello TCP Server!; send(sockfd, buf, strlen(buf), 0); printf(已发送%s\n, buf); close(sockfd); return 0; }
【Linux】网络编程基础—套接字
一、套接字基本概念套接字Socket是计算机网络中用于进程间通信的一种机制通常用于不同主机之间的数据传输。它抽象了底层网络协议的细节为应用程序提供统一的接口。套接字是IP地址与端口号的组合用于唯一标识网络中的一个通信端点。二、套接字工作原理套接字通过绑定特定的IP地址和端口号实现数据的发送和接收。通信双方各自创建一个套接字并通过网络协议如TCP或UDP建立连接。数据通过套接字进行传输发送方将数据写入套接字接收方从套接字读取数据。三、协议认识-UDP/TCPUDP和TCP特点特性TCP传输控制协议UDP用户数据报协议连接性面向连接三次握手建立连接四次挥手断开无连接无需握手直接发数据可靠性可靠传输保证数据不丢、不重、有序到达不可靠传输不保证送达可能丢包 / 乱序有序性保证数据按发送顺序到达有序列号机制不保证有序先发的可能后到重传机制丢包会重传超时重传、快速重传无重传丢包就丢了不处理流量控制有滑动窗口机制避免发送方发太快压垮接收方无发送方只管发不管接收方能不能处理拥塞控制有慢启动、拥塞避免等适配网络拥堵无网络再堵也照常发数据边界无流式数据比如发 2 次 10 字节接收可能一次收 20 字节有数据报边界发 1 次 10 字节接收只能收 10 字节开销高头部 20~60 字节加握手 / 重传 / 控流等逻辑低头部仅 8 字节无额外控制逻辑速度慢可靠性和控流导致延迟高快无额外开销延迟低适用场景对可靠性要求高的场景对速度 / 实时性要求高的场景前置addrsock结构体struct sockaddr { unsigned short sa_family; // 地址族如 AF_INET、AF_INET6 char sa_data[14]; // 协议特定的地址信息 }; /* sa_family指定地址族常见值包括 AF_INETIPv4 地址。 AF_INET6IPv6 地址。 AF_UNIXUnix 域套接字。 sa_data存储具体的地址和端口信息格式由协议族决定。 */sockaddr是网络编程中用于表示套接字地址的通用结构体定义在sys/socket.h头文件中。它通常用于存储 IP 地址和端口信息支持多种协议族如 IPv4、IPv6 等。结构体常见派生类sockaddr_inIPv4struct sockaddr_in { short sin_family; // 地址族AF_INET unsigned short sin_port; // 端口号网络字节序 struct in_addr sin_addr; // IPv4 地址 char sin_zero[8]; // 填充字段未使用 };sockaddr_in6IPv6struct sockaddr_in6 { unsigned short sin6_family; // 地址族AF_INET6 unsigned short sin6_port; // 端口号网络字节序 unsigned long sin6_flowinfo; // 流信息 struct in6_addr sin6_addr; // IPv6 地址 unsigned int sin6_scope_id; // 作用域 ID };注意事项字节序端口和 IP 地址需转换为网络字节序大端。地址族匹配确保sa_family与使用的套接字类型一致。通用性sockaddr设计为通用结构体实际使用时应优先选择具体的派生结构体。UDP相关接口认识UDP 作为无连接、轻量级的传输层协议其编程接口相比 TCP 更简洁核心就是socket()、bind()、sendto()、recvfrom()四个接口。1. 字节序转换核心接口htons/htonl/ntohs/ntohl功能定位网络协议规定所有传输的多字节数据如端口、32 位 IP 整数必须使用网络字节序大端序而不同机器的本地字节序可能是小端主流或大端因此在绑定 / 指定地址时必须通过这些接口完成本地序 ↔ 网络序的转换否则会出现端口 / IP 解析错误。接口说明uint16_t htons(uint16_t hostshort)Host to Network Short将 16 位本地序端口号转为网络序UDP/TCP 端口都是 16 位必用uint32_t htonl(uint32_t hostlong)Host to Network Long将 32 位本地序 IP 整数转为网络序如INADDR_ANY转换uint16_t ntohs(uint16_t netshort)Network to Host Short将 16 位网络序端口号转回本地序接收数据后解析端口时用uint32_t ntohl(uint32_t netlong)Network to Host Long将 32 位网络序 IP 整数转回本地序极少用通常用inet_ntop直接转字符串。核心使用场景绑定端口local_addr.sin_port htons(8080);指定目标端口server_addr.sin_port htons(8080);绑定所有网卡local_addr.sin_addr.s_addr htonl(INADDR_ANY);解析接收的客户端端口ntohs(client_addr.sin_port);2.int socket(int domain, int type, int protocol);功能定位这是网络编程的第一步作用是创建一个套接字描述符简称 sockfd相当于给程序分配一个专属的 “通信管道”后续所有的发送、接收操作都要基于这个 fd 完成。参数解读domain指定通信使用的地址族决定网络协议版本。日常开发中最常用的是AF_INET对应 IPv4 协议如果需要支持 IPv6 则用AF_INET6。type指定套接字的通信类型UDP 必须用SOCK_DGRAM数据报类型这个参数直接决定了通信是无连接、有数据边界的 UDP 模式如果填SOCK_STREAM则是 TCP 模式。protocol指定具体的传输协议通常填 0 即可 —— 系统会根据前面的type参数自动匹配对应的协议比如SOCK_DGRAM对应 UDPSOCK_STREAM对应 TCP无需手动指定IPPROTO_UDP或IPPROTO_TCP。返回值成功时返回一个非负整数就是套接字 fd可理解为 “管道编号”失败时返回 -1此时可以用perror(socket)打印具体的错误原因比如权限不足、参数填错等。3.int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);功能定位把创建好的套接字 fd 绑定到本地的 IP 地址和端口号上。对 UDP 来说服务端必须调用 bind 绑定固定端口比如 8080否则客户端无法找到对应的服务端客户端则可选调用 —— 如果不绑定系统会在发送数据时自动分配一个随机端口。参数解读sockfd要绑定的套接字 fd必须是socket()调用成功返回的有效 fd且未被关闭。addr指向存储本地地址信息的结构体虽然参数类型是通用的struct sockaddr但实际开发中我们会用更贴合 IPv4 的struct sockaddr_in结构体传参时强转为struct sockaddr*即可。这个结构体需要提前初始化包括地址族和socket()的domain一致、端口号必须转网络序、IP 地址通常填INADDR_ANY表示绑定本机所有网卡一般不进行手动绑定特定IP。addrlen地址结构体的长度直接填sizeof(struct sockaddr_in)就能满足 IPv4 场景的需求。4.ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);功能定位UDP 最核心的发送接口作用是把缓冲区里的数据发送到指定的目标地址对方的 IP 端口。因为 UDP 是无连接的所以每次发送都需要明确指定目标地址这也是它和 TCP 的send()最核心的区别。参数解读sockfd发送数据用的套接字 fd需是有效且未关闭的。buf指向要发送的数据缓冲区比如存储字符串、字节数组的内存地址这个参数是只读的函数不会修改缓冲区内容。len要发送的数据长度单位是字节比如发送字符串时可用strlen(buf)发送字节数组则填数组的实际长度。flags发送标志位日常开发中填 0 即可表示阻塞发送直到数据发出或出错如果需要非阻塞发送可填MSG_DONTWAIT但新手建议先掌握默认的阻塞模式。dest_addr指向目标地址的结构体和bind()的addr类似需提前初始化对方的 IP 地址和端口号端口同样要转网络序。addrlen目标地址结构体的长度填sizeof(struct sockaddr_in)即可。返回值成功时返回实际发送的字节数UDP 特性要么发送全部数据要么失败不会出现发送部分数据的情况失败时返回 -1常见错误有网络不可达、目标地址错误、fd 无效等。5.ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);功能定位UDP 核心的接收接口作用是从套接字中读取接收到的数据同时获取发送方的地址IP 端口—— 这也是 UDP 无连接特性的体现接收数据时才知道数据是谁发的。参数解读sockfd接收数据用的套接字 fd需是有效且绑定了端口的服务端必须 bind客户端若未 bind 则系统自动分配端口。buf指向存储接收数据的缓冲区函数会把接收到的数据写入这个缓冲区需提前分配足够的内存比如定义char buf[1024]。len缓冲区的最大长度避免数据溢出比如sizeof(buf)。flags接收标志位默认填 0阻塞接收直到有数据到来MSG_DONTWAIT同样用于非阻塞接收。src_addr指向存储发送方地址的结构体调用后会自动填充对方的 IP 和端口方便后续回复数据。addrlen传入传出参数调用前要初始化为地址结构体的长度比如sizeof(struct sockaddr_in)函数会根据实际地址长度修改这个值因此必须传指针。返回值成功时返回实际接收的字节数失败时返回 -1常见错误有 fd 被关闭、缓冲区过小但不会溢出只会截断数据等如果是非阻塞模式无数据时也会返回 -1需结合errno判断。UDP套接字创建流程-client-server通信实现创建UDP套接字实现网络通信的步骤简单并且公式化server端创建套接字-绑定端口-循环收取数据#include stdio.h #include string.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h #define PORT 8888 #define BUF_LEN 1024 int main() { // 1. 创建UDP套接字socket(AF_INET, SOCK_DGRAM, 0) int sockfd socket(AF_INET, SOCK_DGRAM, 0); // 2. 绑定地址bind(sockfd, addr, sizeof(addr)) struct sockaddr_in serv_addr; memset(serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family AF_INET; serv_addr.sin_addr.s_addr htonl(INADDR_ANY); // 网络序转换htonl() serv_addr.sin_port htons(PORT); // 网络序转换htons() bind(sockfd, (struct sockaddr*)serv_addr, sizeof(serv_addr)); // 3. 接收数据recvfrom(sockfd, buf, len, 0, cli_addr, len) char buf[BUF_LEN]; struct sockaddr_in cli_addr; socklen_t cli_len sizeof(cli_addr); while (1) { int n recvfrom(sockfd, buf, BUF_LEN, 0, (struct sockaddr*)cli_addr, cli_len); buf[n] \0; printf(收到客户端[%s:%d]%s\n, inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port), buf); } close(sockfd); return 0; }client端创建套接字-指定服务端IP-发送数据#include stdio.h #include string.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h #define SERV_IP 127.0.0.1 #define SERV_PORT 8888 #define BUF_LEN 1024 int main() { // 1. 创建UDP套接字socket(AF_INET, SOCK_DGRAM, 0) int sockfd socket(AF_INET, SOCK_DGRAM, 0); // 2. 配置服务端地址 struct sockaddr_in serv_addr; memset(serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family AF_INET; serv_addr.sin_addr.s_addr inet_addr(SERV_IP); serv_addr.sin_port htons(SERV_PORT); // 网络序转换htons() // 3. 发送数据sendto(sockfd, buf, len, 0, serv_addr, sizeof(addr)) char buf[BUF_LEN] Hello UDP Server!; sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr*)serv_addr, sizeof(serv_addr)); printf(已发送%s\n, buf); close(sockfd); return 0; }TCP相关接口认识TCP 作为面向连接、可靠的传输层协议其编程接口相比 UDP 更复杂核心新增接口包含 listen ()、accept ()、connect ()、send ()、recv ()基础的 socket ()、bind () 及字节序转换接口与 UDP 通用不再重复。1.int listen(int sockfd, int backlog);功能定位服务端调用将已绑定的套接字从 “主动态” 转为 “被动态”使其监听客户端的连接请求。TCP 是面向连接的协议服务端必须先监听端口才能接收客户端的连接。参数解读sockfd已绑定地址的套接字 fd由 socket () 创建、bind () 绑定backlog指定内核为该套接字维护的 “未完成连接队列 已完成连接队列” 的最大长度如 5、10超出的连接请求会被拒绝新手填 5 即可满足基础场景。返回值成功返回 0失败返回 -1可通过 perror (listen) 排查错误如 fd 未绑定、参数非法。2.int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);功能定位服务端阻塞等待客户端的连接请求当有客户端发起连接三次握手完成会创建一个新的套接字 fd用于与该客户端通信原监听 fd 继续监听其他客户端。参数解读sockfd监听状态的套接字 fdlisten () 调用后的 fdaddr指向存储客户端地址信息的结构体调用后会自动填充客户端的 IP 和端口可传 NULL 表示不关心客户端地址addrlen传入传出参数调用前初始化为地址结构体长度sizeof (struct sockaddr_in)调用后会更新为实际地址长度必须传指针。返回值成功返回新的通信套接字 fd用于和该客户端收发数据失败返回 -1如 fd 未监听、被信号中断。3.int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);功能定位客户端调用主动向服务端发起 TCP 连接触发三次握手。只有连接建立成功后才能通过该套接字收发数据。参数解读sockfd客户端创建的套接字 fdsocket () 返回addr指向服务端地址结构体需初始化服务端的 IP 和端口端口转网络序addrlen服务端地址结构体的长度sizeof (struct sockaddr_in)。返回值成功返回 0三次握手完成连接建立失败返回 -1如服务端未监听、网络不可达、端口错误。4.ssize_t send(int sockfd, const void *buf, size_t len, int flags);功能定位TCP 核心发送接口向已建立连接的对端发送数据。因 TCP 是流式协议send () 可能返回小于 len 的值如内核缓冲区满需循环发送确保数据全部发出。参数解读sockfd已建立连接的通信套接字 fd客户端 connect () 成功后的 fd、服务端 accept () 返回的新 fdbuf要发送的数据缓冲区只读len要发送的数据长度字节flags默认填 0阻塞发送与 UDP sendto () 的 flags 用法一致如 MSG_DONTWAIT 为非阻塞。返回值成功返回实际发送的字节数可能小于 len失败返回 -1如连接断开、fd 无效。5.ssize_t recv(int sockfd, void *buf, size_t len, int flags);功能定位TCP 核心接收接口从已建立连接的套接字中读取数据。因 TCP 是流式无边界协议recv () 读取的字节数可能小于缓冲区长度需循环读取直到获取完整数据。参数解读sockfd已建立连接的通信套接字 fdbuf存储接收数据的缓冲区len缓冲区最大长度避免溢出flags默认填 0阻塞接收无数据时会阻塞等待。返回值成功返回实际接收的字节数返回 0 表示对端关闭连接四次挥手完成失败返回 -1如连接中断、fd 无效。TCP套接字创建流程-client-server通信实现TCP 通信需遵循 “服务端监听→客户端连接→双向收发” 的固定流程核心是三次握手建立连接、基于新 fd 通信。server 端创建套接字→绑定端口→监听端口→接受连接→循环接收数据#include stdio.h #include string.h #include unistd.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h #define PORT 8888 #define BUF_LEN 1024 int main() { // 创建TCP套接字 int listen_fd socket(AF_INET, SOCK_STREAM, 0); // 绑定地址 struct sockaddr_in serv_addr; memset(serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family AF_INET; serv_addr.sin_addr.s_addr htonl(INADDR_ANY); serv_addr.sin_port htons(PORT); bind(listen_fd, (struct sockaddr*)serv_addr, sizeof(serv_addr)); // 监听端口 listen(listen_fd, 5); // 接受连接 struct sockaddr_in cli_addr; socklen_t cli_len sizeof(cli_addr); int conn_fd accept(listen_fd, (struct sockaddr*)cli_addr, cli_len); printf(客户端[%s:%d]已连接\n, inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port)); // 接收数据 char buf[BUF_LEN]; while (1) { int n recv(conn_fd, buf, BUF_LEN-1, 0); if (n 0) { printf(客户端断开连接\n); break; } buf[n] \0; printf(收到客户端%s\n, buf); } close(conn_fd); close(listen_fd); return 0; }client 端创建套接字→连接服务端→发送数据#include stdio.h #include string.h #include unistd.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h #define SERV_IP 127.0.0.1 #define SERV_PORT 8888 #define BUF_LEN 1024 int main() { // 创建TCP套接字 int sockfd socket(AF_INET, SOCK_STREAM, 0); // 连接服务端 struct sockaddr_in serv_addr; memset(serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family AF_INET; serv_addr.sin_addr.s_addr inet_addr(SERV_IP); serv_addr.sin_port htons(SERV_PORT); connect(sockfd, (struct sockaddr*)serv_addr, sizeof(serv_addr)); // 发送数据 char buf[BUF_LEN] Hello TCP Server!; send(sockfd, buf, strlen(buf), 0); printf(已发送%s\n, buf); close(sockfd); return 0; }