1. Linux网络编程基础与核心概念在Linux环境下进行网络编程本质上是利用操作系统提供的系统调用接口System Call来实现不同主机间的进程通信。与Windows平台的Winsock API不同Linux网络编程遵循POSIX标准采用更统一的文件描述符File Descriptor模型来处理网络连接。1.1 套接字Socket基础模型Linux网络编程的核心是套接字接口它抽象了网络通信的端点。根据通信域的不同主要分为两种类型AF_INETIPv4网络协议族AF_INET6IPv6网络协议族每种协议族又包含多种套接字类型// 流式套接字TCP int sockfd socket(AF_INET, SOCK_STREAM, 0); // 数据报套接字UDP int sockfd socket(AF_INET, SOCK_DGRAM, 0);实际开发中最容易混淆的是socket()的第三个参数protocol。对于大多数情况设置为0即可系统会根据前两个参数自动选择默认协议如TCP或UDP。1.2 字节序与地址转换网络编程中必须处理的主机字节序Host Byte Order与网络字节序Network Byte Order问题。Linux提供以下关键转换函数#include arpa/inet.h // 32位地址转换 uint32_t htonl(uint32_t hostlong); // 主机到网络(long) uint32_t ntohl(uint32_t netlong); // 网络到主机(long) // 16位端口转换 uint16_t htons(uint16_t hostshort); uint16_t ntohs(uint16_t netshort);实际项目中经常需要处理IP地址的字符串表示与二进制形式转换// 点分十进制转二进制 int inet_pton(int af, const char *src, void *dst); // 二进制转点分十进制 const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);2. TCP网络编程核心API详解2.1 服务端标准流程典型的TCP服务端需要依次调用以下API// 1. 创建套接字 int listen_fd socket(AF_INET, SOCK_STREAM, 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 htonl(INADDR_ANY); serv_addr.sin_port htons(8080); bind(listen_fd, (struct sockaddr*)serv_addr, sizeof(serv_addr)); // 3. 开始监听 listen(listen_fd, 10); // 第二个参数是backlog队列长度 // 4. 接受连接 struct sockaddr_in cli_addr; socklen_t cli_len sizeof(cli_addr); int conn_fd accept(listen_fd, (struct sockaddr*)cli_addr, cli_len);关键细节backlog参数的实际效果与Linux内核版本有关。现代内核中它表示已完成连接队列ESTABLISHED状态的最大长度而非早期版本中的半连接队列。2.2 客户端连接流程TCP客户端相对简单但需要注意错误处理// 1. 创建套接字 int sockfd socket(AF_INET, SOCK_STREAM, 0); // 2. 设置服务端地址 struct sockaddr_in serv_addr; memset(serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family AF_INET; serv_addr.sin_port htons(8080); inet_pton(AF_INET, 127.0.0.1, serv_addr.sin_addr); // 3. 发起连接 if (connect(sockfd, (struct sockaddr*)serv_addr, sizeof(serv_addr)) 0) { perror(connect failed); close(sockfd); return -1; }2.3 数据传输API连接建立后主要使用以下函数进行数据传输// 发送数据 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);实际开发中需要特别注意返回值可能小于请求的长度需要循环发送/接收flags参数常用值MSG_WAITALL阻塞直到读取完整数据MSG_DONTWAIT非阻塞模式MSG_PEEK查看数据但不从缓冲区移除3. UDP网络编程关键API3.1 基本通信模式UDP是无连接的协议服务端和客户端界限模糊// 创建UDP套接字 int sockfd socket(AF_INET, SOCK_DGRAM, 0); // 绑定地址服务端通常需要 bind(sockfd, (struct sockaddr*)serv_addr, sizeof(serv_addr)); // 发送数据 sendto(sockfd, buf, len, flags, (struct sockaddr*)dest_addr, addrlen); // 接收数据 recvfrom(sockfd, buf, len, flags, (struct sockaddr*)src_addr, addrlen);3.2 连接模式UDPUDP也可以通过connect()建立伪连接connect(sockfd, (struct sockaddr*)serv_addr, sizeof(serv_addr)); // 之后可以使用send()/recv()而非sendto()/recvfrom()这种模式的优势避免每次发送都指定地址可以接收异步错误如ICMP端口不可达性能略高于每次指定地址4. 高级网络编程技术4.1 IO多路复用Linux提供三种主要的IO多路复用机制select()int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);缺点文件描述符数量受限FD_SETSIZE通常为1024需要每次重新设置fd_setpoll()int poll(struct pollfd *fds, nfds_t nfds, int timeout);使用pollfd数组没有数量限制性能与select类似epoll()Linux特有int epoll_create(int size); int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);高性能适合大量连接支持边缘触发(ET)和水平触发(LT)模式4.2 非阻塞IO设置套接字为非阻塞模式int flags fcntl(sockfd, F_GETFL, 0); fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);非阻塞IO的典型处理模式while (1) { ssize_t n recv(sockfd, buf, sizeof(buf), 0); if (n 0) { // 处理数据 } else if (n 0) { // 连接关闭 break; } else { if (errno EAGAIN || errno EWOULDBLOCK) { // 没有数据可读稍后重试 usleep(1000); continue; } else { // 真实错误 perror(recv error); break; } } }4.3 套接字选项常用套接字选项设置// 设置地址重用避免TIME_WAIT状态导致的绑定失败 int opt 1; setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, opt, sizeof(opt)); // 设置接收超时 struct timeval tv; tv.tv_sec 5; tv.tv_usec 0; setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, tv, sizeof(tv)); // 获取发送缓冲区大小 int sndbuf; socklen_t len sizeof(sndbuf); getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, sndbuf, len);5. 实战经验与常见问题5.1 TCP粘包处理TCP是字节流协议没有消息边界。常见解决方案固定长度每条消息长度相同// 发送方 char msg[100]; memset(msg, 0, sizeof(msg)); strncpy(msg, hello, sizeof(msg)-1); send(sockfd, msg, sizeof(msg), 0); // 接收方 char buf[100]; recv(sockfd, buf, sizeof(buf), MSG_WAITALL);分隔符使用特殊字符如\n分隔消息// 发送方 send(sockfd, hello\n, 6, 0); // 接收方 // 需要自己实现按分隔符读取的逻辑长度前缀在消息前添加长度字段// 发送方 uint32_t len htonl(strlen(hello)); send(sockfd, len, sizeof(len), 0); send(sockfd, hello, strlen(hello), 0); // 接收方 uint32_t len; recv(sockfd, len, sizeof(len), MSG_WAITALL); len ntohl(len); char *buf malloc(len 1); recv(sockfd, buf, len, MSG_WAITALL); buf[len] \0;5.2 连接管理要点正确关闭连接// 优雅关闭发送FIN shutdown(sockfd, SHUT_WR); // 完全关闭 close(sockfd);处理RST异常对方异常关闭时可能收到RST后续操作会触发SIGPIPE信号默认终止进程解决方案// 忽略SIGPIPE signal(SIGPIPE, SIG_IGN); // 或者设置MSG_NOSIGNAL标志 send(sockfd, buf, len, MSG_NOSIGNAL);5.3 性能优化技巧缓冲区设置// 增大发送/接收缓冲区 int buf_size 1024 * 1024; setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, buf_size, sizeof(buf_size)); setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, buf_size, sizeof(buf_size));Nagle算法控制// 禁用Nagle算法适合实时性要求高的场景 int opt 1; setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, opt, sizeof(opt));多线程与连接池每个连接使用独立线程处理或者使用线程池IO多路复用注意线程安全和对共享资源的访问控制6. 调试与错误处理6.1 常见错误码EAGAIN/EWOULDBLOCK非阻塞操作暂时无法完成ECONNREFUSED连接被拒绝ETIMEDOUT连接超时ENETUNREACH网络不可达EINTR系统调用被信号中断6.2 网络诊断工具netstat查看网络状态netstat -antp # 查看所有TCP连接ss更现代的socket统计工具ss -tulnp # 查看监听端口tcpdump抓包分析tcpdump -i eth0 port 8080 -w capture.pcapstrace跟踪系统调用strace -f -e tracenetwork ./server6.3 日志记录建议完善的网络程序应记录连接建立/断开事件数据传输统计字节数、速率等异常情况和错误码重要状态变更示例日志格式[2023-08-20 14:30:45] INFO Client 192.168.1.100:54321 connected [2023-08-20 14:30:46] DEBUG Received 128 bytes from 192.168.1.100 [2023-08-20 14:30:47] ERROR send() failed: Connection reset by peer (104)
阅读完成 · 觉得有帮助?