0.libevent学习笔记,从阻塞式socket开始
本文看着这个链接去学的
https://libevent.org/libevent-book/
本文大量借助chatgpt,腾讯混元等网站,难免有错误,如果有问题欢迎提出,初衷仅为本人学习记录使用,我把我碰到的知识尽量记录下来,目前所有程序都是在windows上写的
Windows 上的socket API 和 Linux 的 socket API 非常相似,但并不完全一样。它们都基于 BSD 套接字(Berkeley Sockets)模型,但由于操作系统平台不同,存在一些差异。
功能 | Winsock(Windows) | BSD/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
头文件
功能 | Winsock | Linux |
---|---|---|
引入头文件 | <winsock2.h>、<ws2tcpip.h> | <sys/socket.h>、<netinet/in.h>、<arpa/inet.h>、<unistd.h> 等 |
链接库 | 需链接 Ws2_32.lib | 不需要额外链接 |
错误处理
操作 | Winsock | Linux |
---|---|---|
错误码 | 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_in,htons的作用是将 主机字节序的端口号 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() 才能拼凑出完整消息。
啥是拆包
拆包是指一个完整的应用层消息被 TCP 分成多个数据包发送,接收方需要多次读取才能还原原始数据。
示例
发送方:
send(sockfd, "HelloWorld", 10, 0); // 发送 10 字节
接收方:
char buf[20];
recv(sockfd, buf, 5, 0); // 第一次收到 "Hello"
recv(sockfd, buf + 5, 5, 0); // 第二次收到 "World"
posted on 2025-05-24 16:27 sleepy2con 阅读(98) 评论(0) 收藏 举报