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

基于C语言的TCP Socket图片传输问题:上传下载陷死循环

C语言TCP Socket文件传输死循环问题排查与修复

核心问题分析

运算符优先级错误:你的while循环里的赋值与比较语句优先级搞反了,比如bytesread = fread(buff, 1, sizeof(buff), file_fd) > 0,>的优先级高于=,实际执行的是bytesread = (fread(...) > 0),这会把布尔值(0或1)赋值给bytesread,导致后续send的字节数错误,同时循环条件可能一直为真(只要fread返回非0,就赋值1,循环继续),陷入死循环。

TCP流式协议无边界,命令/文件名传输无分隔:客户端连续send命令和文件名,服务器的recv可能一次性读到拼接的内容(比如"UPLOADfilename"),导致命令判断错误,后续逻辑混乱。

传输结束无明确标识:文件传输完成后,没有通知对方结束,比如客户端上传完文件后,服务器的recv会一直阻塞等待数据,陷入死循环;下载时同理,客户端的recv会一直等服务器发数据。

send参数错误:download函数里的send(sock, "DOWNLOAD", strlen("DOWNLOAD"), 8),最后一个flags参数用了8,这是无效值,应该用0。

缺乏错误处理:fopen、send、recv等操作失败时没有做处理,比如文件打不开直接进入循环,会导致后续操作异常。

修复后的代码

client.c 修改版

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>

#define True 1

int upload(int sock, const char* file_name){
    FILE *file_fd;
    char buff[1024];
    int bytesread, bytessent;
  
    // 发送命令,用换行作为分隔符
    send(sock, "UPLOAD\n", strlen("UPLOAD\n"), 0);
    // 发送文件名+换行分隔
    send(sock, file_name, strlen(file_name), 0);
    send(sock, "\n", 1, 0);

    file_fd = fopen(file_name, "rb");
    if (!file_fd) {
        perror("fopen upload file failed");
        return 0;
    }
    
    // 修正运算符优先级,添加括号确保先赋值再比较
    while((bytesread = fread(buff, 1, sizeof(buff), file_fd)) > 0)
    {
        bytessent = send(sock, buff, bytesread, 0);
        if (bytessent < 0) {
            perror("send failed");
            fclose(file_fd);
            return 0;
        }
    }
    fclose(file_fd);
    // 关闭socket写端,通知服务器传输结束
    shutdown(sock, SHUT_WR);
    // 等待服务器确认后再删除文件
    recv(sock, buff, sizeof(buff), 0);
    remove(file_name);
    return True;
}


int download(int sock, const char* file_name){
  
    FILE *file_fd;
    char buff[1024];
    int bytesgot;

    // 修正send的flags参数为0
    send(sock, "DOWNLOAD\n", strlen("DOWNLOAD\n"), 0);
    send(sock, file_name, strlen(file_name), 0);
    send(sock, "\n", 1, 0);

    file_fd = fopen(file_name, "wb");
    if (!file_fd) {
        perror("fopen download file failed");
        return 0;
    }

    // 修正运算符优先级
    while((bytesgot = recv(sock, buff, sizeof(buff), 0)) > 0)
    {
        fwrite(buff, 1, bytesgot, file_fd);
    }
    if (bytesgot < 0) {
        perror("recv failed");
        fclose(file_fd);
        remove(file_name);
        return 0;
    }

    fclose(file_fd);
    // 发送确认给服务器
    send(sock, "OK", 2, 0);
    return True;
}

server.c 修改版

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>

#define True 1

// 辅助函数:读取一行数据,用于分隔命令和文件名
ssize_t read_line(int sock, char *buf, size_t max_len) {
    ssize_t n;
    char c;
    size_t i = 0;
    while (i < max_len - 1) {
        n = recv(sock, &c, 1, 0);
        if (n <= 0) {
            return n;
        }
        if (c == '\n') {
            break;
        }
        buf[i++] = c;
    }
    buf[i] = '\0';
    return i;
}

int main() {
    int clnt_sock; // 假设已完成客户端连接
    char buffer1[1024], filename[256], buffer2[1024];
    int n, bytesread;
    FILE *file_fd;

    while(True){
        // 读取完整命令行
        n = read_line(clnt_sock, buffer1, sizeof(buffer1));
        if (n <= 0) { // 客户端断开连接
            break;
        }
        // 处理上传请求
        if(strncmp(buffer1, "UPLOAD", 6) == 0)
        {
            // 读取完整文件名
            n = read_line(clnt_sock, filename, sizeof(filename));
            if (n <= 0) {
                break;
            }

            file_fd = fopen(filename, "wb");
            if (!file_fd) {
                perror("fopen upload file failed");
                send(clnt_sock, "FAIL", 4, 0);
                continue;
            }

            // 接收文件内容,recv返回0时退出循环
            while((bytesread = recv(clnt_sock, buffer2, sizeof(buffer2), 0)) > 0)
            {
                fwrite(buffer2, 1, bytesread, file_fd);
            }
            if (bytesread < 0) {
                perror("recv failed");
                fclose(file_fd);
                remove(filename);
                send(clnt_sock, "FAIL", 4, 0);
                continue;
            }
            
            fclose(file_fd);
            send(clnt_sock, "OK", 2, 0);
        }

        // 处理下载请求
        else if(strncmp(buffer1, "DOWNLOAD", 8) == 0)
        {
            n = read_line(clnt_sock, filename, sizeof(filename));
            if (n <= 0) {
                break;
            }

            file_fd = fopen(filename, "rb");
            if (!file_fd) {
                perror("fopen download file failed");
                shutdown(clnt_sock, SHUT_WR);
                continue;
            }

            while((bytesread = fread(buffer2, 1, sizeof(buffer2), file_fd)) > 0)
            {
                if (send(clnt_sock, buffer2, bytesread, 0) < 0) {
                    perror("send failed");
                    fclose(file_fd);
                    break;
                }
            }
            fclose(file_fd);
            // 关闭写端通知客户端传输结束
            shutdown(clnt_sock, SHUT_WR);
            // 等待客户端确认
            recv(clnt_sock, buffer2, sizeof(buffer2), 0);
            remove(filename);
        }
    }
    return 0;
}

关键修复点说明

  • 修正运算符优先级:所有变量 = 函数调用 > 0的语句添加括号,确保先完成赋值再进行比较,避免逻辑错误。
  • 添加传输边界处理:用换行符作为命令和文件名的分隔符,通过read_line函数读取完整的命令和文件名,解决TCP流式传输的内容拼接问题。
  • 明确传输结束标识:文件传输完成后调用shutdown(sock, SHUT_WR)关闭socket写端,对方recv会返回0,从而正常退出循环,避免死循环。
  • 修正send参数:将无效的flags参数8改为0,符合socket API规范。
  • 完善错误处理:对fopen、send、recv等操作添加错误检查,及时处理异常情况,避免逻辑崩溃。

内容的提问来源于stack exchange,提问作者JUSTIN JANG

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 04:42:05