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

C语言TCP套接字编程:read()返回-1问题求助

TCP文件传输程序问题排查与修复

问题描述

编写基于TCP流式套接字的服务器与客户端程序,实现文件名、文件大小及文件内容的传输。当前程序可成功发送文件名长度、文件名和文件大小,但传输实际文件数据时,客户端已读取并发送全部数据,服务器却仅循环一次且read()返回-1,调试时读写数据量不稳定。

原始代码

服务器代码

#include <string.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>

//function declaration
int readfile(FILE *inputFile, FILE *outputFile);


//main function
int main(int argc, char *argv[]){

    typedef char* bufferPtr;

    int sd; /* socket descriptor */
    int connected_sd; /* socket descriptor */
    int rc; /* return code from recvfrom */
    struct sockaddr_in server_address;
    struct sockaddr_in from_address;
    socklen_t fromLength;

    char nameBuffer[100];
    unsigned char fileBuffer[1000];
    int portNumber;
    int ACK = 0;

    FILE * outputFile;

    //function to make sure port number is given
    if (argc < 2){
        printf ("Usage is: server <portNumber>\n");
        exit (1);
    }

    //needed for server/socket configuration
    portNumber = atoi(argv[1]);
    sd = socket (AF_INET, SOCK_STREAM, 0);
    fromLength = sizeof(struct sockaddr_in);
    server_address.sin_family = AF_INET;
    server_address.sin_port = htons(portNumber);
    server_address.sin_addr.s_addr = INADDR_ANY;
    rc = bind (sd, (struct sockaddr *)&server_address, sizeof(server_address));

    //function to make sure it binds properly
    if (rc < 0){
        perror("bind");
        exit(1);
    }

    //loop to contniue listening for a connection
    do{
        //listen for connection and connect
        listen (sd, 5);
        connected_sd = accept (sd, (struct sockaddr *) &from_address, &fromLength);
        memset(nameBuffer, 0, 100);

        //variables needed for file name size and the file size 
        int fileNameSize;
        int fileSize;
        int* fileNameSizePtr = &fileNameSize;
        int* fileSizePtr = &fileSize;


        rc = read(connected_sd, fileNameSizePtr, sizeof(int));
        printf("Read %d bytes to get the size of the file name\n", rc);

        fileNameSize = ntohs(fileNameSize);

        // variables for reading filename from network
        int totalBytesRead = 0;
        char * nameBuffPtr = nameBuffer;

        //read filename
        while(totalBytesRead < fileNameSize){
            rc = read(connected_sd, nameBuffPtr, fileNameSize-totalBytesRead);
        if(rc == -1){
        if(errno == EINTR){
            printf("interrupted signal\n");
            break;
        }
        else if(errno == EAGAIN || errno == EWOULDBLOCK){
            printf("no data to read\n");
            break;
        }
        }
            if(rc < 0){
                exit(1);
            }
            totalBytesRead += rc;
            nameBuffPtr += rc;
        }
        printf("Received file name %s\n", nameBuffer);

        //read the size of the file and convert to proper endianness
        rc = read(connected_sd, fileSizePtr, sizeof(int));

        //reset total bytes read for file data reading
        totalBytesRead = 0;
        int numberOfBytes;
        unsigned char * fileBufferPtr = fileBuffer;

    if((outputFile = fopen(nameBuffer, "wb")) ==NULL){
                exit(1);
        }

        while(totalBytesRead < fileSize){
        printf("%d\n", fileSize);
            rc = read(connected_sd, fileBufferPtr, fileSize);
        if(rc == -1){
            if(errno == EINTR){
                printf("interrupted signal\n");
            }
            if(errno == EAGAIN || errno == EWOULDBLOCK){
                printf("no data\n");}}
            numberOfBytes = rc;
            printf("read %d bytes from network\n", rc);
            
            if(rc < 0){
                exit(1);
            }

            
            rc = fwrite(&fileBuffer, 1, numberOfBytes, outputFile);

            fileBufferPtr += rc;
            totalBytesRead += rc;
            printf("Progress: %d bytes\n", totalBytesRead);

            if(totalBytesRead == fileSize){
                printf("All data received\n");
                    ACK = ntohs(1);
                    rc = write(connected_sd, &ACK, sizeof(int));
            }
        }

    }while(1);

    return 0;
}

客户端代码

#include <string.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#define BUFFSIZE 1000

int readfile(FILE *inputFile, int socket);
int getFileLength(FILE* fp);

int main(int argc, char *argv[]){

    int sd;
    int portNumber;
    int rc = 0;
    char serverIP[29];
    struct sockaddr_in server_address;

    int fileNameSize;
    int fileSize;
    char fileName[100];
    char done[] = "DONE";
    FILE *inputFile;


    if (argc < 3){
        printf ("usage is client <ipaddr> <port>\n");
        exit(1);
    }

    sd = socket(AF_INET, SOCK_STREAM, 0);
    portNumber = atoi(argv[2]);
    strcpy(serverIP, argv[1]);
    server_address.sin_family = AF_INET;
    server_address.sin_port = htons(portNumber);
    server_address.sin_addr.s_addr = inet_addr(serverIP);

    if(connect(sd, (struct sockaddr *)&server_address, sizeof(struct sockaddr_in)) < 0) {
        close(sd);
        perror("error connecting stream socket");
        exit(1);
    }

    //loop until user enters "DONE"
    do{

        //ask for file name
        printf("Enter the filename: \n");
        scanf("%s", fileName);

        //calculate the size of the file name and send it to server
        fileNameSize = strlen(fileName);
        fileNameSize = ntohs(fileNameSize);
        rc = write(sd, &fileNameSize, sizeof(fileNameSize));
        
        //wrtie the name of the file to the server
        int bytesToWrite = ntohs (fileNameSize);
        rc = write(sd, &fileName, bytesToWrite);

        printf("wrote %d bytes to send the filename\n", rc);

        //open file to be read
        if((inputFile = fopen(fileName, "rb")) == NULL){
            printf("Error opening the file %s\n", fileName);
            exit(1);
        }

        fileSize = getFileLength(inputFile);
        printf("%D\n", fileSize);

        //fileSize = ntohs (fileSize);
        rc = write(sd, &fileSize, sizeof(int));
        printf("%d\n", rc);

        readfile(inputFile, sd);


    }while(strcmp(fileName, done) != 0);

    return 0;
}


int readfile(FILE *inputFile, int socket){
    unsigned char buffer [BUFFSIZE];
    int numberOfBytes;
    int rc;
    int totalBytesRead=0, totalBytesWritten=0;

    numberOfBytes = fread(buffer, 1, BUFFSIZE, inputFile);

    while (numberOfBytes > 0){
        totalBytesRead += numberOfBytes;
        
        rc = write (socket, buffer, numberOfBytes);
        if (numberOfBytes != rc){
            perror ("writing to server");
            exit(1); }
        totalBytesWritten += rc; 
        printf("%d\n", totalBytesWritten);
        numberOfBytes = fread(buffer, 1, BUFFSIZE, inputFile);
    }
    printf ("read %d bytes, and wrote %d bytes\n", totalBytesRead,
totalBytesWritten);
return (0); 
}

int getFileLength(FILE* fp)
{
    fseek(fp, 0, SEEK_END);
    int len = (int) ftell(fp);
    fseek(fp, 0, SEEK_SET);
    return len;
}

关键问题与修复

1. 服务器端文件读取缓冲区溢出

服务器读取文件数据时,read(connected_sd, fileBufferPtr, fileSize)直接传递整个文件大小作为读取长度,但fileBuffer只有1000字节,会导致缓冲区溢出,触发未定义行为。
修复:每次读取的长度取剩余需要读取的字节数和缓冲区容量的较小值:

rc = read(connected_sd, fileBufferPtr, 
          (fileSize - totalBytesRead) > sizeof(fileBuffer) ? 
          sizeof(fileBuffer) : (fileSize - totalBytesRead));

2. 字节序处理错误

  • 客户端发送fileNameSize时误用ntohs()(网络转主机),正确应该用htons()(主机转网络);
  • 客户端发送fileSize未做字节序转换,且服务器端接收后也未转换,跨平台会导致fileSize值错误。
    修复:
    客户端:
fileNameSize = htons(strlen(fileName));
rc = write(sd, &fileNameSize, sizeof(fileNameSize));

// ...

fileSize = htonl(getFileLength(inputFile));
rc = write(sd, &fileSize, sizeof(int));

服务器端:

rc = read(connected_sd, fileSizePtr, sizeof(int));
fileSize = ntohl(fileSize); // 新增字节序转换

3. fwrite参数错误

服务器端fwrite(&fileBuffer, 1, numberOfBytes, outputFile)传递了数组地址&fileBuffer,但数组名本身就是指针,会导致写入错误数据。
修复:

rc = fwrite(fileBuffer, 1, numberOfBytes, outputFile);

4. 文件缓冲区指针更新错误

服务器端fileBufferPtr += rc;使用fwrite的返回值更新指针,逻辑上应该用read得到的numberOfBytes,避免异常情况下的偏移错误。
修复:

fileBufferPtr += numberOfBytes;
totalBytesRead += numberOfBytes;

5. read错误处理逻辑缺陷

当read遇到EINTR或EAGAIN时直接break退出循环,应该继续尝试读取。
修复:

while(totalBytesRead < fileNameSize){
    rc = read(connected_sd, nameBuffPtr, fileNameSize-totalBytesRead);
    if(rc == -1){
        if(errno == EINTR){
            printf("interrupted signal, retry...\n");
            continue; // 继续循环重试
        }
        else if(errno == EAGAIN || errno == EWOULDBLOCK){
            printf("no data to read, retry...\n");
            continue; // 继续循环重试
        }
        else {
            perror("read filename");
            exit(1);
        }
    }
    // ... 其他逻辑
}

文件数据读取的循环也做同样修改。

6. 客户端打印fileSize格式符错误

客户端printf("%D\n", fileSize);用了错误的格式符%D,应该用%d。
修复:

printf("%d\n", fileSize);

修复后效果

修复上述问题后,服务器可以正确循环读取客户端发送的全部文件数据,不会出现read()返回-1的异常,文件内容可以完整传输并写入。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 17:15:01