C语言UDP Socket多线程文件传输:服务端无法识别success消息
UDP Socket文件传输校验和匹配失败问题排查
问题概述
用C语言实现基于UDP Socket的服务端与客户端程序,支持服务端向客户端发送图片文件、多线程处理、校验和传输错误检测。文件传输功能正常,但校验和正确(传输成功)时,客户端发送"success"消息,服务端收到的是"success"附加二进制内容的消息,导致strcmp匹配失败,循环无法退出,同时多会话功能受此问题影响。
服务端代码(Server.c)
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <unistd.h> #include <sys/socket.h> #include <pthread.h> #include <unistd.h> #define PORT 8888 #define BUF_SIZE 1254 #define MAX_CLIENTS 100 typedef struct { struct sockaddr_in address; int socket; int id; } Client; Client clients[MAX_CLIENTS]; pthread_t threads[MAX_CLIENTS]; int clientCount = 0; pthread_mutex_t mutex; unsigned short calculateChecksum(FILE *file) { fseek(file, 0, SEEK_SET); unsigned short checksum = 0; int ch; while ((ch = fgetc(file)) != EOF) { checksum += (unsigned char)ch; } fseek(file, 0, SEEK_SET); return checksum; } void *handleClient(void *arg) { int id = *((int *)arg); ssize_t bytesRead; // video file FILE *file; char *filename = "image.jpg"; // open video file file = fopen(filename, "rb"); if (file == NULL) { perror("File opening failed"); exit(EXIT_FAILURE); } char buf[BUF_SIZE]; ssize_t bytes_read; // check file size fseek(file, 0, SEEK_END); size_t file_size = ftell(file); fseek(file, 0, SEEK_SET); // define loop count size_t loopcount = file_size / BUF_SIZE; // what is the residue size_t residue = file_size % BUF_SIZE; char buffer[BUF_SIZE]; int i=0; pthread_mutex_lock(&mutex); strcpy(buffer,"retry"); while (1) { //problem printf("received message: %s\n", buffer); if (!strcmp(buffer, "success")) { break; } else if(!strcmp(buffer, "retry")) { printf("sending videofile\n"); // data sending for (int i = 0; i < loopcount; i++) { size_t bytesread = fread(buffer, 1, BUF_SIZE, file); sendto(clients[id].socket, buffer, BUF_SIZE, 0, (struct sockaddr *)&clients[id].address, sizeof(clients[id].address)); } // residue data sending size_t bytesread = fread(buffer, 1, residue, file); sendto(clients[id].socket, buffer, residue, 0, (struct sockaddr *)&clients[id].address, sizeof(clients[id].address)); printf("sending video file completed\n"); char fileFinMessage[] = "fin"; if (sendto(clients[id].socket, fileFinMessage, sizeof(fileFinMessage), 0, (struct sockaddr *)&clients[id].address, sizeof(clients[id].address)) < 0) { perror("sendto() failed"); exit(EXIT_FAILURE); } // sending checksum unsigned short checksum = calculateChecksum(file); sprintf(buffer, "%d", checksum); printf("checksum is %d \n", checksum); sendto(clients[id].socket, buffer, BUF_SIZE, 0, (struct sockaddr *)&clients[id].address, sizeof(clients[id].address)); } //is data transfer has not error? if(recvfrom(clients[id].socket, buffer, sizeof(buffer), 0, NULL, NULL)==-1){ perror("recvfrom() failed"); exit(EXIT_FAILURE); } } fclose(file); pthread_mutex_lock(&mutex); return 0; } int main() { int serv_sock; struct sockaddr_in serv_adr; char message[BUF_SIZE]; char buf[BUF_SIZE]; socklen_t clnt_adr_sz = sizeof(struct sockaddr_in); ssize_t str_len; // Create socket if ((serv_sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { perror("Socket creation failed"); exit(EXIT_FAILURE); } // socket address memset(&serv_adr, 0, sizeof(serv_adr)); serv_adr.sin_family = AF_INET; serv_adr.sin_addr.s_addr = INADDR_ANY; serv_adr.sin_port = htons(PORT); // binding socket if (bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1) { perror("bind() error"); exit(1); } // mutex Init pthread_mutex_init(&mutex, NULL); printf("waiting for client...\n"); // Create a thraed for each client while (1) { // receive message from client str_len = recvfrom(serv_sock, message, BUF_SIZE, 0, (struct sockaddr *)&clients[clientCount].address, &clnt_adr_sz); if (str_len < 0) { perror("recvfrom error"); exit(1); } // print message message[str_len] = '\0'; printf("client %d say: %s\n", clientCount + 1, message); // Add client to the list clients[clientCount].socket = serv_sock; clients[clientCount].id = clientCount; clientCount++; // send a message to the client sprintf(message, "hello i am server, you are Client %d\n", clientCount); if (sendto(clients[clientCount - 1].socket, message, strlen(message), 0, (struct sockaddr *)&clients[clientCount - 1].address, sizeof(clients[clientCount - 1].address)) < 0) { perror("sendto error"); exit(1); } // create thread for client pthread_create(&threads[clientCount+1], NULL, handleClient, (void *)&clients[clientCount+1].id); pthread_join(threads[clientCount+1], NULL); } close(serv_sock); pthread_mutex_destroy(&mutex); return 0; }
客户端代码(Client.c)
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <unistd.h> #include <sys/socket.h> #define BUFSIZE 1254 #define PORT 8888 unsigned short calculateChecksum(FILE *file) { fseek(file, 0, SEEK_SET); unsigned short checksum = 0; int ch; while ((ch = fgetc(file)) != EOF) { checksum += (unsigned char)ch; } fseek(file, 0, SEEK_SET); return checksum; } void fileRecive(int sock, struct sockaddr_in *serv_addr, socklen_t adr_sz) { FILE *file = fopen("received_image.jpg", "wb"); if (file == NULL) { perror("File opening failed"); exit(EXIT_FAILURE); } char message[BUFSIZE]; size_t nbyte = BUFSIZE; while (1) { nbyte = recvfrom(sock, message, nbyte, 0, (struct sockaddr *)serv_addr, &adr_sz); if (!strcmp(message, "fin")) { printf("File received successfully.\n"); break; } fwrite(message, nbyte, 1, file); } fclose(file); } int main() { int sock; char message[BUFSIZE]; int str_len; socklen_t adr_sz; struct sockaddr_in serv_addr, client_addr; sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock == -1) { printf("socket() error\n"); exit(1); } memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); serv_addr.sin_port = htons(PORT); char hello_message[] = "hello i am client"; sendto(sock, hello_message, strlen(hello_message), 0, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); adr_sz = sizeof(client_addr); str_len = recvfrom(sock, message, BUFSIZE, 0, (struct sockaddr *)&client_addr, &adr_sz); message[str_len] = '\0'; printf("Server say: %s\n", message); fileRecive(sock, &serv_addr, adr_sz); FILE *file2; char *filename = "received_image.jpg"; if (file2 == NULL) { perror("File opening failed"); exit(EXIT_FAILURE); } // checksum 검사 recvfrom(sock, message, BUFSIZE, 0, (struct sockaddr *)&client_addr, &adr_sz); while (1) { file2 = fopen(filename, "rb"); printf("received checksum = %s\n", message); unsigned short checksum = calculateChecksum(file2); printf("calcualte checksum = %d\n", checksum); char checksumCheck[BUFSIZE]; sprintf(checksumCheck, "%d", checksum); if (!strcmp(message, checksumCheck)) { printf("checksum correct\n"); //problem sprintf(message, "success"); printf("%s\n", message); sendto(sock, message, strlen(message), 0, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); fclose(file2); break; } else { printf("checksum incorrect"); printf("\n"); char retry_message[BUFSIZE] = "retry"; sendto(sock, retry_message, strlen(retry_message), 0, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); fileRecive(sock, &serv_addr, adr_sz); } } close(sock); return 0; }
服务端异常输出
waiting for client... client 1 say: hello i am client received message: retry sending videofile sending video file completed checksum is 7317 received message: successn;-�L=n��="��}�K+�#S�l����'A6=�MH�dC+ܐ�D�0)j��<YF�%0�;�j��'R�n ��W�|�l�6WL�.ʜ�ȫP��#�P��!<����2��PBB�9Cq5^�M`n�6D��Z��5HM�- sbW&�����#iT���_ȝٶ�b�z�*l���d7vȆ�Cs��s�5�ڎɾɘfX�&�̘���/�C": �r4��d&��ۈ�yq�2�+6xѡt;A���%A*bI��5y�5AO?�ND�N��c\�2ƹ1��ف��nbI�JOcس\I��xVlic%�lr{Ѥ�!L��q�}����%ثF݉��ϧ��BT8D����0�؆���'��m[��H��#s��Hod�541��,��O��
问题根源与修复方案
1. 核心问题:字符串未正确终止
服务端在recvfrom接收客户端消息后,没有手动添加字符串结束符'\0'。由于之前buffer存储过二进制文件内容,残留的二进制数据会被当成字符串的一部分,导致strcmp匹配"success"时失败。
修复代码(服务端handleClient函数中):
// 替换原有的recvfrom代码 ssize_t recv_len = recvfrom(clients[id].socket, buffer, sizeof(buffer)-1, 0, NULL, NULL); if(recv_len == -1){ perror("recvfrom() failed"); exit(EXIT_FAILURE); } buffer[recv_len] = '\0'; // 强制添加字符串结束符,截断残留数据
注意使用sizeof(buffer)-1作为接收长度,避免写入'\0'时越界。
2. 校验和发送冗余数据
服务端发送校验和时,使用BUF_SIZE作为发送长度,导致客户端接收的message缓冲区中残留之前的二进制数据。应发送校验和字符串的实际长度。
修复代码(服务端发送校验和部分):
sprintf(buffer, "%d", checksum); printf("checksum is %d \n", checksum); // 发送实际字符串长度(包含'\0') sendto(clients[id].socket, buffer, strlen(buffer)+1, 0, (struct sockaddr *)&clients[id].address, sizeof(clients[id].address));
3. 多线程处理逻辑错误
- 线程创建时索引越界:
threads[clientCount+1]和clients[clientCount+1].id会超出数组范围,应使用clientCount-1(因为clientCount刚完成自增)。 pthread_join放在主循环中会阻塞主线程,导致无法同时处理多个客户端,需移除该行,后续可通过线程回收机制处理结束的线程。
修复代码(服务端main函数中):
// 替换原线程创建代码 pthread_create(&threads[clientCount-1], NULL, handleClient, (void *)&clients[clientCount-1].id); // 注释掉pthread_join,避免阻塞主线程 // pthread_join(threads[clientCount-1], NULL);
4. 客户端"fin"判断存在越界风险
客户端fileRecive函数中使用strcmp(message, "fin")判断文件传输结束,但message存储二进制数据时可能没有'\0',会导致strcmp越界访问内存。
修复代码(客户端fileRecive函数中):
nbyte = recvfrom(sock, message, nbyte, 0, (struct sockaddr *)serv_addr, &adr_sz); // 比较前3个字节判断是否为"fin",同时确保接收长度足够 if (nbyte >= 3 && strncmp(message, "fin", 3) == 0) { printf("File received successfully.\n"); break; }
5. 客户端野指针错误
客户端中FILE *file2;未初始化就进行if(file2 == NULL)判断,属于野指针操作,会导致错误的文件状态判断。
修复代码(客户端main函数中):
// 直接初始化file2,再判断是否打开成功 FILE *file2 = fopen(filename, "rb"); if (file2 == NULL) { perror("File opening failed"); exit(EXIT_FAILURE); }
内容的提问来源于stack exchange,提问作者user24723398
相关产品推荐
相关产品推荐

