recv函数无法接收全部发送数据的TCP文件传输问题求助
TCP客户端-服务器文件传输循环无法结束的问题排查与修复
核心问题分析
你的代码在处理**二进制文件(如压缩包)**时出现循环无法退出,本质是误用了文本处理函数来处理二进制数据,同时对TCP的字节流传输机制理解有误:
字符串函数的致命缺陷
- 压缩文件是二进制数据,必然包含
\0空字符,strlen(buffer)会在第一个\0处停止计数,导致rcv_inc累加的长度远小于真实接收的字节数,永远达不到size_recv,循环无法终止。 - 普通文本文件极少包含
\0,但偶尔出现特殊字符或文件异常时,也会触发同样的计数错误,这就是你看到5%概率问题的原因。 fputs(buffer, fp)同样会在\0处停止写入,导致文件内容不完整,进一步加剧计数偏差。
- 压缩文件是二进制数据,必然包含
Send_file函数的逻辑错误
fgets是文本读取函数,仅适用于处理换行符分隔的文本,无法正确读取二进制数据中的\0,导致读取内容不全。send(socket, buffer, BUFLEN, 0)直接发送整个缓冲区,但fgets实际读取的字节数可能小于BUFLEN,会把缓冲区里的残留数据(如之前的\0)一起发送,导致接收端收到多余内容,计数彻底混乱。- 用
!feof(fp)作为循环条件,feof仅在读取到文件末尾后才会置位,最后一次读取失败时仍会进入循环,发送空数据。
循环条件的逻辑错误
recv_file中的while( rcv_inc <= size_recv )错误:当rcv_inc等于size_recv时,已经完成所有数据接收,无需再进入循环。
修复后的代码
修复后的Recv_file函数
void recv_file(int socket, ssize_t size_recv, FILE* fp){ char buffer[BUFLEN] = {'\0'}; ssize_t rcv; long rcv_inc = 0; // 循环接收直到完成指定字节数 while( rcv_inc < size_recv ){ // 计算剩余需要接收的字节数,避免最后一次接收过量 ssize_t remaining = size_recv - rcv_inc; ssize_t recv_len = remaining < BUFLEN ? remaining : BUFLEN; if( (rcv = recv(socket, buffer, recv_len, 0)) < 0 ){ fprintf(stderr, "Errore nella ricezione del file:%s\n",strerror(errno)); close(socket); exit(EXIT_FAILURE); } // 用fwrite写入实际接收的字节数,适配二进制数据 fwrite(buffer, 1, rcv, fp); rcv_inc += rcv; fflush(stdout); printf("\rserver> Progresso: %lu / %lu", rcv_inc, size_recv); } // 二进制文件无需额外添加'\0',注释掉错误的收尾代码 // fseek(fp, (size_recv - rcv_inc), SEEK_END); // fputc('\0', fp); }
修复后的Send_file函数
void send_file(int socket, ssize_t size_to_send, FILE* fp){ long bufinc = 0; ssize_t snd; char buffer[BUFLEN] = {'\0'}; // 循环发送直到完成指定字节数 while( bufinc < size_to_send ){ ssize_t remaining = size_to_send - bufinc; ssize_t read_len = remaining < BUFLEN ? remaining : BUFLEN; // 用fread读取二进制数据,获取实际读取的字节数 ssize_t read_bytes = fread(buffer, 1, read_len, fp); if(read_bytes == 0){ fprintf(stderr, "Errore: file terminato prima di inviare tutti i byte.\n"); close(socket); exit(EXIT_FAILURE); } if( (snd = send(socket, buffer, read_bytes, 0)) < 0 ){ fprintf(stderr, "Errore nell'invio del file: %s, inviati %lu B su %lu B totali.\n", strerror(errno), bufinc, size_to_send); close(socket); exit(EXIT_FAILURE); } bufinc += snd; fflush(stdout); printf("\rserver> Progresso: %lu / %lu (%lu)", bufinc, size_to_send, snd); } }
关键注意事项
- 二进制模式打开文件:无论发送还是接收文件,都要用二进制模式打开,比如
fopen("compressed.gz", "rb")和fopen("received.gz", "wb"),避免Windows系统自动转换换行符导致文件大小和内容异常。 - 准确传递文件大小:压缩后的文件大小必须重新计算(用
fseek+ftell),不能直接使用原文件的大小,否则接收端的size_recv会和实际发送的字节数不匹配。 - 依赖recv/send的返回值计数:TCP是字节流协议,
recv和send的返回值是实际处理的字节数,必须用这个值更新进度,绝对不能用字符串函数的结果。
内容的提问来源于stack exchange,提问作者imBrainDD
相关产品推荐
相关产品推荐

