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

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;
}

问题根源分析

  1. 全局缓冲区被频繁覆盖:代码中使用全局变量buffer,receiveAnswer、sendResponse以及performConnection都直接操作这个缓冲区。比如sendResponse发送消息时会清空并覆盖buffer,之后调用receiveAnswer又会再次覆盖,导致performConnection循环中依赖的缓冲区内容被破坏,出现消息拼接错误。
  2. recv返回值未妥善处理:receiveAnswer仅检查了recv是否出错,未处理服务器断开连接(返回0)的情况,也没有利用返回的字节数确保字符串正确终止(虽然用了bzeroNew,但如果recv填满缓冲区,末尾不会自动添加\0,可能导致后续字符串操作异常)。
  3. 消息读取时序混乱:在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 04:10:29