C语言recv函数不稳定:无法完整读取并存储所有传入数据
问题描述
我们正在开发一个与服务器通信的项目,但通信函数无法始终正确读取传入消息。有时缓冲区会出现类似+ Client version acClient: ID 38ail6ii3s8jc的内容,而正确流程应该是服务器发送+ Client version accepted - please send Game-ID to join,我们再发送Client: ID 38ail6ii3s8jc。我们认为问题出在char *receiveAnswer(int sock)函数中。
通信函数相关代码
#include <stdio.h> #include <stdlib.h> #include <sys/socket.h> #include <sys/types.h> #include <string.h> #define BUFFERSIZE 1024 #define bzeroNew(b,len) (memset((b), '\0', (len)), (void) 0) //buffer loeschen #define VERSION "VERSION 3.4\n" #include "functions.h" char buffer[BUFFERSIZE]; int prologEnd = 0; int proof; //liest von Server eine Nachricht ein und speichert sie im buffer ab char *receiveAnswer(int sock) { bzeroNew(buffer, BUFFERSIZE); if(recv(sock, buffer, sizeof(buffer), 0) < 0) { perror("ERROR: Empfangen fehlgeschlagen\n"); } printf("%s", buffer); return buffer; } void sendResponse(int sock, char* message) { bzeroNew(buffer, BUFFERSIZE); strcpy(buffer, message); proof = send(sock, buffer, strlen(buffer), 0); if(proof < 0) { perror("ERROR: Senden fehlgeschlagen\n"); } printf("Client: %s\n", buffer); receiveAnswer(sock); } int performConnection(int sock, char* gameID) { bzeroNew(buffer, BUFFERSIZE); receiveAnswer(sock); while(strncmp(buffer, "+", 1) == 0 && prologEnd == 0) { if(strncmp(buffer, "+ MNM Gameserver", 16) == 0) { receiveAnswer(sock); sendResponse(sock, VERSION); } else if(strncmp(buffer, "+ Client", 8) == 0) { sendResponse(sock, gameID); } else if(strncmp(buffer, "+ PLAYING", 9) == 0) { sendResponse(sock, "PLAYER\n"); receiveAnswer(sock); } else if(strncmp(buffer, "+ YOU", 5) == 0) { receiveAnswer(sock); printf("\n"); prologEnd = 1; } else if(strncmp(buffer, "+ TOTAL", 7) == 0) { receiveAnswer(sock); receiveAnswer(sock); prologEnd = 1; } } bzeroNew(buffer, BUFFERSIZE); return 0; }
main函数代码
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <sys/wait.h> // für Warten auf Kindprozess #include <unistd.h> #include <netdb.h> #include <netinet/in.h> #include <fcntl.h> #include <limits.h> #include <signal.h> // für Shared Memory: #include <sys/ipc.h> #include <sys/shm.h> #include "functions.h" #include "sharedMemory.h" // dublicat, brauchen wir das? #define GAMEKINDNAME "NMMorris" #define HOSTNAME "sysprak.priv.lab.nm.ifi.lmu.de" #define PORTNUMBER 1357 int main (int argc, char *argv[]) { char gamekindname[256] = "NMMorris"; char hostname[256] = "sysprak.priv.lab.nm.ifi.lmu.de"; int portnumber = 1357; char* gameID = argv[2]; char playerNumber[256]; char configFile[256] = "client.conf" ; int fd[2]; // TODO: fd und client_fd vereinen //gameID formatieren char bufferGameID[64]; strcpy(bufferGameID, "ID "); strcat(bufferGameID, gameID); strcpy(gameID, bufferGameID); strcat(gameID, "\n"); int i; char tmp[256]; //Argumente einlesen und an Variablen übergeben for(i = 3; i < 7; i++) { strcpy(tmp, argv[i]); if (strcmp(tmp, "-p") == 0){ strcpy(playerNumber, argv[i+1]); } else if (strcmp(tmp, "-conf") == 0){ strcpy(configFile, argv[i+1]); } } config configMain = readConfig(configFile); strcpy(gamekindname, configMain.gameKind); strcpy(hostname, configMain.hostServerName); portnumber = configMain.portNmbr; printf("\n>>>Config File Data<<<\n"); printf("HostServerName: %s\n", hostname); printf("PortNumber: %d\n", portnumber); printf("GameKind: %s\n\n ", gamekindname); //From here: sockets int sock, client_fd; struct sockaddr_in serv_addr; struct hostent *server; sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) { perror("\nERROR: Socket creation error \n"); return - 1; } //ipAdresse nachschauen server = gethostbyname(hostname); if (server == NULL) { perror("ERROR: no such host\n"); } memset(&serv_addr,0,sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(portnumber); memcpy(&serv_addr.sin_addr.s_addr,server->h_addr,server->h_length); if ((client_fd = connect(sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr))) < 0) { perror("ERROR: Connection Failed \n"); return -1; } printf(">>> Mit Host : %s verbunden <<<\n", hostname); if(performConnection(sock, gameID) != 0) { perror("performConnection Failed\n"); } // TODO: verlagern close(client_fd); return 0; // Shared Memory Segment erstellen int shmid_game = shmget(KEY, sizeof(gameInfo), IPC_CREAT | SHM_R | SHM_W); if (shmid_game == -1) { perror("Error while creating shared memory segment"); exit(EXIT_FAILURE); } else { printf("Creation successful\n"); } int shmid_player = shmget(KEY, sizeof(playerInfo), IPC_CREAT | SHM_R | SHM_W); if (shmid_player == -1) { perror("Error while creating shared memory segment"); exit(EXIT_FAILURE); } else { printf("Creation successful\n"); } // Prozess mit SHM verbinden void* shm_game = shmat(shmid_game, 0, 0); if (shm_game == NULL) { perror("Error while attaching shared memory segment"); exit(EXIT_FAILURE); } else { printf("Attachment successful\n"); } void* shm_player = shmat(shmid_player, 0, 0); if (shm_player == NULL) { perror("Error while attaching shared memory segment"); exit(EXIT_FAILURE); } else { printf("Attachment successful\n"); } // Kindprozess (Connector) erstellen pid_t pid; pid = fork(); if (pid < 0) { fprintf(stderr, "Fehler bei Erstellung des Kindprozesses.\n"); } else if (pid == 0) { // Kindprozess (Connector) close(fd[1]); performConnection(sock, gameID); } else { // Elternprozess (Thinker) close(fd[0]); } return 0; }
问题根源分析
- 全局缓冲区被频繁覆盖:代码中使用全局变量
buffer,receiveAnswer、sendResponse以及performConnection都直接操作这个缓冲区。比如sendResponse发送消息时会清空并覆盖buffer,之后调用receiveAnswer又会再次覆盖,导致performConnection循环中依赖的缓冲区内容被破坏,出现消息拼接错误。 - recv返回值未妥善处理:
receiveAnswer仅检查了recv是否出错,未处理服务器断开连接(返回0)的情况,也没有利用返回的字节数确保字符串正确终止(虽然用了bzeroNew,但如果recv填满缓冲区,末尾不会自动添加\0,可能导致后续字符串操作异常)。 - 消息读取时序混乱:在
performConnection的分支逻辑中,存在重复读取消息的情况。比如+ MNM Gameserver分支中先调用receiveAnswer,然后调用sendResponse(里面又调用receiveAnswer),导致两次读取服务器消息,可能跳过了预期的+ Client version accepted消息,进而触发错误的分支逻辑。
修复方案
1. 替换全局缓冲区为静态局部缓冲区
避免全局变量带来的时序覆盖问题,同时保留函数返回字符串的特性(单线程场景下可用):
char *receiveAnswer(int sock) { static char buffer[BUFFERSIZE]; ssize_t bytes_read; bzeroNew(buffer, BUFFERSIZE); bytes_read = recv(sock, buffer, sizeof(buffer) - 1, 0); // 预留一个字节存'\0' if (bytes_read < 0) { perror("ERROR: Empfangen fehlgeschlagen"); return NULL; } else if (bytes_read == 0) { printf("Server hat die Verbindung geschlossen\n"); return NULL; } buffer[bytes_read] = '\0'; // 确保字符串正确终止 printf("%s", buffer); return buffer; }
2. 修改sendResponse使用局部缓冲区发送
避免发送操作覆盖接收缓冲区:
void sendResponse(int sock, char* message) { char send_buf[BUFFERSIZE]; ssize_t bytes_sent; bzeroNew(send_buf, BUFFERSIZE); strcpy(send_buf, message); bytes_sent = send(sock, send_buf, strlen(send_buf), 0); if (bytes_sent < 0) { perror("ERROR: Senden fehlgeschlagen"); } printf("Client: %s\n", send_buf); receiveAnswer(sock); // 读取服务器响应,使用静态局部缓冲区不会干扰之前的消息 }
3. 梳理performConnection的消息读取逻辑
确保每次循环只读取一次服务器消息,避免重复读取或遗漏:
int performConnection(int sock, char* gameID) { char *current_msg; prologEnd = 0; while (prologEnd == 0) { current_msg = receiveAnswer(sock); if (current_msg == NULL) break; // 连接断开或读取失败,退出循环 if (strncmp(current_msg, "+", 1) != 0) continue; if (strncmp(current_msg, "+ MNM Gameserver", 16) == 0) { sendResponse(sock, VERSION); } else if (strncmp(current_msg, "+ Client", 8) == 0) { sendResponse(sock, gameID); } else if (strncmp(current_msg, "+ PLAYING", 9) == 0) { sendResponse(sock, "PLAYER\n"); } else if (strncmp(current_msg, "+ YOU", 5) == 0) { printf("\n"); prologEnd = 1; } else if (strncmp(current_msg, "+ TOTAL", 7) == 0) { // 按需求读取后续两条消息 receiveAnswer(sock); receiveAnswer(sock); prologEnd = 1; } } return 0; }
内容的提问来源于stack exchange,提问作者Enno Mühlbauer
相关产品推荐
相关产品推荐

