You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

recv函数无法接收全部发送数据的TCP文件传输问题求助

TCP客户端-服务器文件传输循环无法结束的问题排查与修复

核心问题分析

你的代码在处理**二进制文件(如压缩包)**时出现循环无法退出,本质是误用了文本处理函数来处理二进制数据,同时对TCP的字节流传输机制理解有误:

  1. 字符串函数的致命缺陷

    • 压缩文件是二进制数据,必然包含\0空字符,strlen(buffer)会在第一个\0处停止计数,导致rcv_inc累加的长度远小于真实接收的字节数,永远达不到size_recv,循环无法终止。
    • 普通文本文件极少包含\0,但偶尔出现特殊字符或文件异常时,也会触发同样的计数错误,这就是你看到5%概率问题的原因。
    • fputs(buffer, fp)同样会在\0处停止写入,导致文件内容不完整,进一步加剧计数偏差。
  2. Send_file函数的逻辑错误

    • fgets是文本读取函数,仅适用于处理换行符分隔的文本,无法正确读取二进制数据中的\0,导致读取内容不全。
    • send(socket, buffer, BUFLEN, 0)直接发送整个缓冲区,但fgets实际读取的字节数可能小于BUFLEN,会把缓冲区里的残留数据(如之前的\0)一起发送,导致接收端收到多余内容,计数彻底混乱。
    • 用!feof(fp)作为循环条件,feof仅在读取到文件末尾后才会置位,最后一次读取失败时仍会进入循环,发送空数据。
  3. 循环条件的逻辑错误

    • 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 05:45:39