C语言TCP Socket通信客户端偶现无法读取服务端发送数据问题
问题根因
1. 核心原因:TCP是字节流协议,不存在内置消息边界
你客户端读取文件名的代码调用了read(sock, buffer, sizeof(buffer)),会一次性读取内核缓冲区中最多1024字节的数据:
- 如果服务端发送的文件名和后续文件内容被合并到同一个TCP段,或者在第一次read触发前已经全部到达客户端内核缓冲区,这次read会把文件名+所有文件内容全部读到buffer里
- 你只提取了buffer开头的文件名使用,剩下的文件内容没有做任何处理,直接被后续的memset覆盖丢弃
- 后面的while循环调用read时已经没有剩余数据可读取,直接返回0结束,所以你看不到任何内容打印
这就是问题复现概率随机的原因:只有当TCP分包刚好把文件名单独作为一个段发送时,第一次read只会读取到文件名,后续内容才会被while循环读到。
2. 其他次要问题
- 服务端发送完所有文件内容后,没有主动关闭连接套接字,客户端不会收到EOF通知,极端场景下会一直阻塞在read调用上
- 缺少必要的错误校验:比如服务端
fopen打开源文件没有判断返回值,如果源文件不存在会直接无数据发送
修复方案
快速修复(适配现有逻辑)
- 服务端发完数据后主动关闭连接,在
fclose(f)后添加如下代码:
// 发送FIN包告知客户端数据已全部发送完成 shutdown(new_socket, SHUT_WR); close(new_socket); close(sock);
- 客户端读取文件名后,先处理buffer中剩余的文件内容,修改读取文件名的代码段:
// Get file name from server ssize_t read_len = read(sock, buffer, sizeof(buffer)); if(read_len == -1){ perror("Could not read filename from server"); exit(EXIT_FAILURE); } // 计算文件名长度,提取后续剩余内容 int fname_total_len = strlen(buffer) + 1; ssize_t remain_content_len = read_len - fname_total_len; if (remain_content_len > 0) { // 把第一次读到的剩余内容先写入文件 printf("Read returned %zd\n", remain_content_len); printf("Received %s", buffer + fname_total_len); fputs(buffer + fname_total_len, f); } memset(buffer, 0, sizeof(buffer));
规范实现方案(彻底解决边界问题)
提前约定通信格式,避免多读少读问题:
- 服务端先发送4字节整数,表示文件名的字节长度
- 再发送对应长度的文件名内容
- 再发送4字节整数,表示文件的总字节大小
- 最后发送对应长度的文件内容
- 客户端严格按照约定的长度读取对应数据,不会出现跨包读取的问题
内容的提问来源于stack exchange,提问作者Rush
相关产品推荐
相关产品推荐

