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
相关产品推荐
相关产品推荐

