C语言Socket编程recv()接收数据长度返回异常值问题排查
问题根因
你的问题核心是TCP字节流协议的短读场景未处理,同时自定义的收全数据函数实现存在逻辑错误,最终导致数据流错位,解析出异常长度值,具体问题点如下:
- 长度头部接收逻辑错误
你调用recv(client_sock, size_message, 4, 0)读取4字节长度头部时,仅判断了返回值为0的对端断开场景,遗漏了两种关键情况:
- 返回值小于0:socket发生网络错误,未正常接收数据
- 返回值大于0但小于4:TCP是无消息边界的字节流协议,单次
recv调用不保证返回你请求的全部字节数,可能只返回1/2/3个字节的头部,剩余的头部字节会被后续读取逻辑当作业务数据处理,而下次读取头部时会读到上一轮未读完的业务数据,直接导致长度解析错误。
recvall函数实现错误
函数中拿到recv的返回值recv_size后完全未做判断和使用,无论实际收到多少字节,都强制按size_to_recv的长度拷贝数据、累加已收计数:
如果recv返回值小于size_to_recv(包括网络波动导致的短读、对端断开、系统错误等场景),就会导致实际接收的业务数据字节数不足,剩余未收的业务数据会被下一轮循环当作长度头部读取,直接出现你遇到的异常超大长度值。
前面几次能正常接收只是因为网络状态稳定,没有触发短读场景,一旦出现拆包/网络波动就会立刻出现错位问题。
修复方案
1. 重写可靠收全数据的通用函数
// 收满size字节才返回,返回<0表示出错,返回0表示对端断开,返回>0等于size表示收全 int recvall(int fd, uint8_t *data, int size) { int cur_size = 0; while(cur_size < size) { int recv_size = recv(fd, data + cur_size, size - cur_size, 0); if (recv_size < 0) { // 非阻塞socket要单独处理EAGAIN/EWOULDBLOCK,这里默认是阻塞socket直接返回错误 return -1; } else if (recv_size == 0) { return 0; } cur_size += recv_size; } return cur_size; }
2. 修改长度头部接收逻辑,使用上面的recvall保证收满4字节
while(1) { uint8_t size_message[4]; int read_size = recvall(client_sock, size_message, 4); if (read_size <= 0) { // 对端断开或者出错,退出循环 return 0; } printf("bytes are %d %d %d %d\n", size_message[0], size_message[1], size_message[2], size_message[3]); uint32_t size = toUInt32(size_message); printf("size is %d\n", size); uint8_t *data = malloc(size); read_size = recvall(client_sock, data, size); if (read_size <= 0) { free(data); return 0; } free(data); }
内容的提问来源于stack exchange,提问作者EdwinC
相关产品推荐
相关产品推荐

