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

TCP客户端连接后立即断开求助:卡牌游戏C/S架构移植故障

问题描述

将原有独立卡牌游戏(加载与保存功能异常但不影响连接)移植到TCP服务器/客户端架构中。已将游戏函数复制到cardgamefunctions.h文件,新增模仿原游戏主函数的gameLoop函数,实现了Server.c、cardgamefunctions.h和Client.c三个文件。但客户端连接后会立即断开,多次调试均无法获取反馈。注释掉服务器和客户端中的gameLoop调用及头文件引用后,连接可正常维持,怀疑问题出在gameLoop函数。


服务器文件代码(Server.c)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//#include "cardgamefunctions.h"
#include <signal.h>
#include <sys/wait.h>

#define PORT 12345
#define MAX_CLIENTS 10

void handle_client(int client_socket) {
    char menu[] ="\n=== Card Game Menu ===\n1. Create Account\n2. Login\n3. Start New Game\n4. Save Game\n5. Load Game\n6. Show Score\n7. Exit\nEnter your choice: ";
    
    if (send(client_socket, menu, strlen(menu), 0) <= 0) {
        printf("Failed to send menu to client\n");
        close(client_socket);
        return;
    }
    //gameLoop(client_socket);
    close(client_socket);
}

void sigchld_handler(int signo) {
    while (waitpid(-1, NULL, WNOHANG) > 0);  // Prevent zombie processes
}

int main(){
    signal(SIGCHLD, sigchld_handler);

    int server_socket;
    struct sockaddr_in server_addr, client_addr;
    socklen_t addr_size;

    server_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (server_socket < 0) {
        perror("Socket creation failed");
        exit(EXIT_FAILURE);
    }

    int opt = 1;
    if (setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
        perror("Setsockopt failed");
        exit(EXIT_FAILURE);
    }

    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(PORT);

     if (bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("Bind failed");
        close(server_socket);
        exit(EXIT_FAILURE);
    }

    if (listen(server_socket, MAX_CLIENTS) < 0) {
        perror("Listen failed");
        close(server_socket);
        exit(EXIT_FAILURE);
    }
    printf("Server is listening on port %d\n", PORT);

    while (1) {
        addr_size = sizeof(client_addr);
        int client_socket = accept(server_socket, (struct sockaddr*)&client_addr, &addr_size);
         if (client_socket < 0) {
            perror("Accept failed");
            continue;
        }

        printf("Client connected!\n");
        
        if (fork() == 0) {  // new for each client
            close(server_socket);
            handle_client(client_socket);
            close(client_socket);
            exit(0); 
            }

        else { // Parent process
            close(client_socket); // Parent process doesn't need the client socket
            }
    }
    return 0;
}

头文件中的gameLoop函数(cardgamefunctions.h)

void gameLoop()
{
    int gameInProgress = 0;
    GameState game;
    game.player.id = NULL;
    int choice;

    do
    {
        displayMenu();
        int bytes_received = recv(client_socket, &choice, sizeof(choice), 0);
        if (bytes_received <= 0) {
            printf("Client disconnected.\n");
            break;
        }

        switch (choice) {

            case 1: createAccount(&game.player); 
            break;
        
            case 2: login(&game.player);
            break;

            case 3: 
            if (game.player.id == NULL) {
                const char *message = "You need to log in or create an account first to start a new game.\n";
                send(client_socket, message, strlen(message), 0);
            break;
            }
            {
            int answer;
            const char *newGamePrompt = "Do you want to start a new game?\n1.Yes\n2. No\n";
            send(client_socket, newGamePrompt, strlen(newGamePrompt), 0);

            if (recv(client_socket, &answer, sizeof(answer), 0) <= 0) {
                printf("Client disconnected.\n");
                break;
            }
            
            if(answer != 1 && answer != 2)
            {
                send(client_socket, "Invalid input :( Please choose either 1 or 2.\n", 35, 0);
                fflush(stdin);  // Clear input buffer
            }

            else if (answer == 1) {
                DeckSize = DECK_SIZE;
                freeGame(&game);
                startNewGame(&game);
                gameInProgress = 1;
                send(client_socket, "A new game has started ^.^\n", 25, 0);}

            else if (answer == 2) {
                send(client_socket,"Okay, returning to menu! \n", 20, 0);
            }
            break;
        }
            case 4: 
                if (game.player.id == NULL){
                    send(client_socket, "You need to log in or create an account to play & save a game!\n", 30, 0);
                    break;}

                saveGameState(&game, "savefile.txt"); 
                send(client_socket, "Game saved successfully.\n", 25, 0);
                break;

            case 5: 
            freeGame(&game); //diwxnw ta prohgoumenws loaded dedomena
            loadGameState(&game, "savefile.txt"); 
            gameInProgress= 1;
            send(client_socket, "Game loaded successfully.\n", 25, 0);
            break;

            case 6: 
            if (game.player.id == NULL)
            {
                send(client_socket, "You need to log in or create an account first.\n", 48, 0);
            }
            else { char scoreMessage[100];
                    snprintf(scoreMessage, sizeof(scoreMessage), "Player Score: %d\n", game.player.score);
                    send(client_socket, scoreMessage, strlen(scoreMessage), 0);
            }
            break;

            case 7:  
            send(client_socket, "Exiting game... Bye bye! ^w^\n", 25, 0);
            freeGame(&game);
            gameInProgress = 0;
            return;

            default: 
                send(client_socket, "Invalid choice. Please enter a number between 1 and 7.\n", 26, 0);
                break;
        }  
        if (gameInProgress == 1){
                while (game.player.deckSize > 0 && game.computer.deckSize > 0) {
                    count_round(&game);

                    // Player's turn to play a card
                    Card p1Card = playCard(game.player.deck, &game.player.deckSize, game.player.deckSize);
                    // Computer's turn to play a card
                    Card p2Card = playCard2(game.computer.deck, &game.computer.deckSize);

                    // Compare the cards and update the scores
                    compareRanks(&game, p1Card, p2Card);

                    send (client_socket, "Do you want to continue? (1. Yes / 2. Save and Exit):", 39, 0);
                    
                    int cont;
                    if (recv(client_socket, &cont, sizeof(cont), 0) <= 0) {
                        printf("Client disconnected.\n");
                        break;
                    }
                    
                    if (cont == 1){continue;}
                        
                    else if (cont == 2) {
                        saveGameState(&game, "savefile.txt");
                        send(client_socket, "Game saved. Returning to menu...\n", 31, 0);
                        gameInProgress = 0;  // Stop the game
                        break;
                        }
                
                    else {
                    const char *invalidInput = "Invalid choice. Please enter 1 or 2.\n";
                    send(client_socket, invalidInput, strlen(invalidInput), 0);
                    }
                }

                if (game.player.deckSize == 0 || game.computer.deckSize == 0) {
                    char gameOverMessage[256];
                    snprintf(gameOverMessage, sizeof(gameOverMessage), "Game over! Final Score - You: %d, Computer: %d\n", game.player.score, game.computer.score);
                    send(client_socket, gameOverMessage, strlen(gameOverMessage), 0);
                    gameInProgress = 0;
                }
            }   
        }
    while (choice != 7);

    freeGame(&game);
    close(client_socket);
}

客户端文件代码(Client.c)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//#include "cardgamefunctions.h"

#define PORT 12345

int main() {
    int client_socket;
    struct sockaddr_in server_addr;
    char buffer[1024];
    int choice;

    client_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (client_socket < 0) {
        perror("Socket creation failed");
        exit(EXIT_FAILURE);
    }

    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr.s_addr = INADDR_ANY;

    if (connect(client_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("Connection to server failed");
        close(client_socket);
        exit(EXIT_FAILURE);
    }
    printf("Connected to server!\n");
    client_socket = client_socket;

    while(1) {
        memset(buffer, 0, sizeof(buffer));

        int bytes_received = recv(client_socket, buffer, sizeof(buffer), 0);
            if (bytes_received <= 0) {
            printf("Error receiving data from server. bytes_received: %d\n", bytes_received);
            close(client_socket);
            break;
            }
        if (bytes_received == 0) {
            printf("Server closed connection.\n");
            break;
        }
        
        printf("%s", buffer);
        
        scanf("%d", &choice);

        if (send(client_socket, &choice, sizeof(choice), 0) <= 0) {
            printf("Failed to send choice.\n");
            break;
        }

        memset(buffer, 0, sizeof(buffer));
        bytes_received = recv(client_socket, buffer, sizeof(buffer), 0);
        if (bytes_received <= 0) {
            printf("Server disconnected :c.\n");
            break;
        }

        printf("Server: %s\n", buffer);

        if (choice == 7 || strncmp(buffer, "exit", 4) == 0) {
            printf("Exiting...\n");
            break; // Exit the loop and close the connection
        }
    }

    close(client_socket);
    return 0;
}

问题根源及修复方案

核心问题点

  1. gameLoop函数未接收客户端套接字参数:当前gameLoop()无参数,但调用时需要传入client_socket,函数内部直接使用未定义的client_socket变量,触发未定义行为导致程序崩溃,进而关闭客户端连接。
  2. 重复发送菜单:handle_client已发送菜单,gameLoop内部又调用displayMenu()(推测该函数也会发送菜单),导致客户端接收重复数据,协议逻辑混乱。
  3. 通信协议不匹配:客户端每次发送选择后都会等待服务器响应,但服务器部分操作(如创建账号、登录)未发送任何反馈,导致客户端阻塞在recv操作,最终超时断开。
  4. 无效操作:服务器端调用fflush(stdin),但服务器进程无终端输入,该操作无效且可能引发未定义行为。
  5. 结构体未初始化:GameState game局部变量未完整初始化,仅设置game.player.id = NULL可能导致其他字段出现垃圾值。

修复步骤

1. 修正gameLoop函数的参数传递

修改函数定义,接收客户端套接字作为参数,并初始化结构体:

void gameLoop(int client_socket) {
    int gameInProgress = 0;
    GameState game;
    memset(&game, 0, sizeof(GameState)); // 完整初始化游戏状态结构体
    game.player.id = NULL;
    int choice;

    do {
        // 移除displayMenu()调用,避免重复发送菜单
        int bytes_received = recv(client_socket, &choice, sizeof(choice), 0);
        if (bytes_received <= 0) {
            printf("Client disconnected.\n");
            break;
        }

        switch (choice) {
            case 1: 
                createAccount(&game.player);
                // 添加操作结果反馈
                send(client_socket, "Account created successfully.\n", 29, 0);
                break;
            case 2: 
                if (login(&game.player)) { // 假设login返回登录是否成功
                    send(client_socket, "Login successful.\n", 18, 0);
                } else {
                    send(client_socket, "Login failed. Invalid credentials.\n", 34, 0);
                }
                break;
            // 其他case补充缺失的响应,确保客户端每次操作都能收到反馈
            case 7:  
                send(client_socket, "Exiting game... Bye bye! ^w^\n", 28, 0);
                freeGame(&game);
                gameInProgress = 0;
                return;
            // 移除fflush(stdin)调用
        }
        // 游戏进行中的逻辑保持不变
    } while (choice != 7);

    freeGame(&game);
    close(client_socket);
}

2. 修正服务器handle_client函数

恢复gameLoop调用并传递正确参数,移除重复的套接字关闭操作:

void handle_client(int client_socket) {
    char menu[] ="\n=== Card Game Menu ===\n1. Create Account\n2. Login\n3. Start New Game\n4. Save Game\n5. Load Game\n6. Show Score\n7. Exit\nEnter your choice: ";
    
    if (send(client_socket, menu, strlen(menu), 0) <= 0) {
        printf("Failed to send menu to client\n");
        close(client_socket);
        return;
    }
    gameLoop(client_socket); // 恢复调用并传入client_socket
    // 移除close(client_socket),因为gameLoop最后会关闭套接字
}

3. 修正客户端通信逻辑

调整接收逻辑,匹配服务器的发送节奏,避免不必要的阻塞:

while(1) {
    memset(buffer, 0, sizeof(buffer));

    int bytes_received = recv(client_socket, buffer, sizeof(buffer), 0);
    if (bytes_received <= 0) {
        printf("Error receiving data from server. bytes_received: %d\n", bytes_received);
        close(client_socket);
        break;
    }
    printf("%s", buffer);

    // 收到退出提示直接退出
    if (strstr(buffer, "Exiting game") != NULL) {
        printf("Exiting...\n");
        break;
    }

    // 仅在需要输入选择时调用scanf
    if (strstr(buffer, "Enter your choice") != NULL || 
        strstr(buffer, "Do you want to continue") != NULL ||
        strstr(buffer, "Do you want to start a new game") != NULL) {
        scanf("%d", &choice);
        if (send(client_socket, &choice, sizeof(choice), 0) <= 0) {
            printf("Failed to send choice.\n");
            break;
        }
    }
}

4. 其他注意事项

  • 账号数据需存储在服务器端共享存储(文件或数据库),而非本地文件,确保多客户端数据隔离。
  • 实现循环收发逻辑,处理recv/send返回值小于请求长度的情况,保证数据完整传输。
  • 检查createAccount、login等函数的错误处理逻辑,确保异常情况能反馈给客户端。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 20:30:52