.libevent学习笔记,从阻塞式socket开始
Windows 上的socket API 和 Linux 的 socket API 非常相似但并不完全一样。它们都基于 BSD 套接字Berkeley Sockets模型但由于操作系统平台不同存在一些差异。功能WinsockWindowsBSD/Linux创建套接字socket()socket()绑定地址bind()bind()监听连接listen()listen()接收连接accept()accept()发送数据send()send()接收数据recv()recv()关闭连接closesocket()close()API 名称和参数基本一致所以很多网络编程代码可以在两个平台上少量修改后通用。windows上使用socket api通信时需要先初始化#ifdef _WIN32 // 存储使用winsock时初始化需要的数据 WSADATA wsa_data; // 调用WSAStartup需要传入Winsock 版本号。 WSAStartup(0x0201, wsa_data); #endif头文件功能WinsockLinux引入头文件winsock2.h、ws2tcpip.hsys/socket.h、netinet/in.h、arpa/inet.h、unistd.h 等链接库需链接 Ws2_32.lib不需要额外链接错误处理操作WinsockLinux错误码WSAGetLastError()errno错误码名称比如 WSAECONNRESET比如 ECONNRESET一个简单的阻塞tcp socket客户端程序struct sockaddr_in sin; sin.sin_family AF_INET; sin.sin_port htons(80); inet_pton(AF_INET, 142.250.71.196, sin.sin_addr); int fd socket(AF_INET, SOCK_STREAM, 0); // 选择tcp传输 if (fd 0) { std::cerr socket; return 1; } if (connect(fd, (struct sockaddr*)sin, sizeof(sin))) { std::cerr connect; closesocket(fd); return 1; } const char query[] GET / HTTP/1.0\r\n Host:www.google.com\r\n \r\n; const char* cp query; int n_written, remaining strlen(query); while (remaining 0) { n_written send(fd, cp, remaining, 0); if (n_written 0) { std::cerr send; closesocket(fd); return 1; } remaining - n_written; cp n_written; } char buf[1024]; while (1) { int result recv(fd, buf, sizeof(buf), 0); if (result 0) break; else if (result 0) { std::cerr recv; break; } fwrite(buf, 1, result, stdout); }操作系统的原生 Socket API 本身是协议无关的既支持 TCP 也支持 UDP具体协议类型由开发者在创建 Socket 时通过参数指定。udp类型不需要connect可以直接用sendto指定ip和端口就可以直接发了一个专门表示 IPv4 地址和端口号 的结构体变量sockaddr_inhtons的作用是将 主机字节序的端口号 40713 转换为 网络字节序大端序struct sockaddr_in sin; sin.sin_port htons(40713);什么是字节序字节序指计算机存储多字节数据如16位/32位整数的顺序分为两种小端序Little-Endian低位字节在前常见于x86 CPU。例如40713十六进制 0x9F09在内存中存储为 09 9F低字节 0x09 在前。大端序Big-Endian高位字节在前网络标准、PowerPC等。同一数值存储为 9F 09高字节 0x9F 在前操作系统采用小端序Little-Endian主要是由于历史原因和硬件设计优化其优势体现在数据处理的效率和硬件设计的简化上。我就不复制粘贴了反正都是AI告诉我的TCP 粘包问题TCP 是面向字节流的协议它不保留应用层消息的边界因此会导致粘包Packet Sticking问题。什么是粘包粘包是指发送方多次调用 send() 发送的数据在接收方的一次 recv() 中全部收到导致多条消息“粘”在一起无法区分原始消息边界。示例发送方send(sockfd, Hello, 5, 0); // 发送 Hello send(sockfd, World, 5, 0); // 发送 World接收方char buf[20]; recv(sockfd, buf, sizeof(buf), 0); // 可能收到 HelloWorld粘包粘包的原因 TCP 是字节流协议不维护消息边界TCP 只保证数据按顺序到达不区分 send() 的调用次数。数据可能合并或拆分Nagle 算法TCP 默认会合并小数据包减少网络开销。内核缓冲区机制send() 的数据可能被拆分成多个 TCP 段或合并成一个段发送。接收方缓冲区读取方式recv() 读取的是当前接收缓冲区中的所有可用数据无法自动区分原始消息。粘包的解决方案方法适用场景优点缺点固定长度简单二进制协议解析快无需转义浪费带宽分隔符文本协议如HTTP灵活人类可读需处理转义长度前缀高效二进制协议精准控制无浪费需预定义最大长度TCP 拆包问题TCP 拆包Packet Splitting是指发送方调用一次 send() 发送的数据可能被 TCP 协议栈拆分成多个数据包传输导致接收方需要多次 recv() 才能拼凑出完整消息。