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

Windows10+C(VS2015)多客户端聊天程序消息接收异常求助

多客户端聊天程序问题排查与修复方案

我仔细检查了你的服务端和客户端代码,发现了几个关键问题,这些问题直接导致了你重启客户端后出现的消息接收异常,甚至还存在一些潜在的稳定性问题。下面逐个分析并给出修复方案:

一、客户端代码的核心问题(导致接收线程无法运行)

这是你当前遇到的主要问题根源:

  • 未初始化互斥量且锁逻辑错误:客户端中sending_message线程一开始就调用WaitForSingleObject(hMutex, INFINITE),但main函数里根本没有创建这个互斥量(hMutex = CreateMutex(NULL, FALSE, NULL);缺失)。更严重的是,发送线程拿到锁后直接进入死循环,永远不调用ReleaseMutex,导致receiving_message线程永远无法获取锁,完全无法执行消息接收逻辑。
  • main函数参数错误:main函数的第二个参数应该是char *argv[],而不是int *argv[],这会导致编译警告甚至运行异常。
  • recv返回值处理不全:接收线程只判断了recv != -1,但当客户端正常断开时recv会返回0,此时应该退出循环,避免无意义的循环占用CPU。
  • gets函数不安全且发送长度不合理:gets函数存在缓冲区溢出风险,建议替换为fgets;发送消息时固定用1024长度会发送多余的垃圾数据,应该用实际消息长度(包含终止符)。

二、服务端代码的关键问题(导致客户端管理混乱)

  • 线程参数传递错误:在main函数的循环中,你把局部变量socket_of_client的地址传给client_management线程,但下一次accept会覆盖这个变量的值,导致线程拿到的socket可能是无效的或者是其他客户端的socket。正确的做法是为每个客户端的socket分配独立的内存空间。
  • 客户端断开后的数组清理逻辑错误:当客户端断开时,你在移除数组元素的循环中使用了break,导致只移动了一个元素,后面的元素没有正确前移,数组中会留下无效的socket,后续发送消息时会出错。
  • recv返回值处理不全:同样没有处理recv返回0的情况(客户端正常断开),导致线程不会进入清理逻辑。
  • 发送消息长度不合理:sending_message函数固定发送1024字节,应该用实际接收到的消息长度strLen,避免发送垃圾数据。
  • 临界区管理不严谨:counting_clients++应该放在互斥量的保护范围内,当前代码是在释放互斥量后执行,会导致多线程下的计数错误。

修复后的完整代码

修复后的服务端代码

#include <stdio.h>
#include <Windows.h>

unsigned WINAPI client_management(void *arg);
void sending_message(char *message, int length_of_message);

int counting_clients = 0;
SOCKET socket_of_clients[1024];
HANDLE hMutex;

int main(int argc, char *argv[]) {
    WSADATA wsaData;
    HANDLE client_management_thread;
    struct sockaddr_in address_of_server;
    struct sockaddr_in address_of_client;
    int size_of_address_of_client = sizeof(address_of_client);

    if (WSAStartup(MAKEWORD(2, 2), &wsaData) == SOCKET_ERROR) {
        printf("WSAStartup failed\n\n");
        return 1;
    }

    hMutex = CreateMutex(NULL, FALSE, NULL);
    if (hMutex == NULL) {
        printf("CreateMutex failed\n\n");
        WSACleanup();
        return 1;
    }

    SOCKET socket_of_server = socket(AF_INET, SOCK_STREAM, 0);
    if (socket_of_server == INVALID_SOCKET) {
        printf("socket creation failed\n\n");
        CloseHandle(hMutex);
        WSACleanup();
        return 1;
    }

    memset(&address_of_server, 0, sizeof(address_of_server));
    address_of_server.sin_family = AF_INET;
    address_of_server.sin_addr.s_addr = htonl(INADDR_ANY);
    address_of_server.sin_port = htons(10000);

    if (bind(socket_of_server, (struct sockaddr*)&address_of_server, sizeof(address_of_server)) == SOCKET_ERROR) {
        printf("bind failed\n\n");
        closesocket(socket_of_server);
        CloseHandle(hMutex);
        WSACleanup();
        return 1;
    }

    if (listen(socket_of_server, 5) == SOCKET_ERROR) {
        printf("listen failed\n\n");
        closesocket(socket_of_server);
        CloseHandle(hMutex);
        WSACleanup();
        return 1;
    }

    printf("waiting for clients...\n\n");

    while (1) {
        SOCKET socket_of_client = accept(socket_of_server, (struct sockaddr*)&address_of_client, &size_of_address_of_client);
        if (socket_of_client == INVALID_SOCKET) {
            printf("accept failed\n\n");
            continue;
        }

        printf("client connected\n\n");

        // 为每个客户端的socket分配独立内存,避免线程参数被覆盖
        SOCKET *pSocket = (SOCKET*)malloc(sizeof(SOCKET));
        if (pSocket == NULL) {
            printf("malloc failed, closing client socket\n\n");
            closesocket(socket_of_client);
            continue;
        }
        *pSocket = socket_of_client;

        WaitForSingleObject(hMutex, INFINITE);
        socket_of_clients[counting_clients] = socket_of_client;
        counting_clients++;
        printf("total clients: %d\n\n", counting_clients);
        ReleaseMutex(hMutex);

        client_management_thread = (HANDLE)_beginthreadex(NULL, 0, client_management, (void*)pSocket, 0, NULL);
        if (client_management_thread == NULL) {
            printf("_beginthreadex failed, closing client socket\n\n");
            closesocket(socket_of_client);
            free(pSocket);
            // 回退计数
            WaitForSingleObject(hMutex, INFINITE);
            counting_clients--;
            ReleaseMutex(hMutex);
        } else {
            CloseHandle(client_management_thread); // 关闭线程句柄,避免资源泄漏
        }
    }

    closesocket(socket_of_server);
    CloseHandle(hMutex);
    WSACleanup();
    return 0;
}

unsigned WINAPI client_management(void *arg) {
    SOCKET socket_of_client = *((SOCKET*)arg);
    free(arg); // 释放之前分配的内存

    int strLen = 0, i;
    char message[1024];

    while ((strLen = recv(socket_of_client, message, 1023, 0)) > 0) { // 留一个字节给终止符
        message[strLen] = '\0'; // 确保字符串以终止符结尾
        printf("received message: %s\n\n", message);
        sending_message(message, strLen + 1); // 发送包含终止符的完整消息
    }

    // 处理客户端断开(recv返回0或错误)
    printf("client disconnected\n\n");

    WaitForSingleObject(hMutex, INFINITE);
    for (i = 0; i < counting_clients; i++) {
        if (socket_of_client == socket_of_clients[i]) {
            // 将后面的元素全部前移
            for (; i < counting_clients - 1; i++) {
                socket_of_clients[i] = socket_of_clients[i + 1];
            }
            break;
        }
    }
    counting_clients--;
    printf("total clients: %d\n\n", counting_clients);
    ReleaseMutex(hMutex);

    closesocket(socket_of_client);
    return 0;
}

void sending_message(char *message, int length_of_message) {
    int i;
    WaitForSingleObject(hMutex, INFINITE);
    for (i = 0; i < counting_clients; i++) {
        send(socket_of_clients[i], message, length_of_message, 0);
        // 可以在这里检查send的返回值,处理发送失败的情况
    }
    ReleaseMutex(hMutex);
}

修复后的客户端代码

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <stdio.h>
#include <Windows.h>
#include <string.h>

unsigned WINAPI sending_message(void *arg);
unsigned WINAPI receiving_message(void *arg);

char message[1024];
HANDLE hMutex;

int main(int argc, char *argv[]) { // 修正参数类型
    WSADATA wsaData;
    HANDLE sending_message_thread;
    HANDLE receiving_message_thread;
    struct sockaddr_in address_of_server;

    if (WSAStartup(MAKEWORD(2, 2), &wsaData) == SOCKET_ERROR) {
        printf("WSAStartup failed\n");
        return 1;
    }

    // 初始化互斥量
    hMutex = CreateMutex(NULL, FALSE, NULL);
    if (hMutex == NULL) {
        printf("CreateMutex failed\n");
        WSACleanup();
        return 1;
    }

    SOCKET socket_of_client = socket(AF_INET, SOCK_STREAM, 0);
    if (socket_of_client == INVALID_SOCKET) {
        printf("socket creation failed\n");
        CloseHandle(hMutex);
        WSACleanup();
        return 1;
    }

    memset(&address_of_server, 0, sizeof(address_of_server));
    address_of_server.sin_family = AF_INET;
    address_of_server.sin_addr.s_addr = inet_addr("192.168.25.62");
    address_of_server.sin_port = htons(10000);

    if (connect(socket_of_client, (struct sockaddr*)&address_of_server, sizeof(address_of_server)) == SOCKET_ERROR) {
        printf("connect failed\n\n");
        closesocket(socket_of_client);
        CloseHandle(hMutex);
        WSACleanup();
        return 1;
    } else {
        printf("connected to server\n\n");
    }

    sending_message_thread = (HANDLE)_beginthreadex(NULL, 0, sending_message, (void*)&socket_of_client, 0, NULL);
    receiving_message_thread = (HANDLE)_beginthreadex(NULL, 0, receiving_message, (void*)&socket_of_client, 0, NULL);

    // 等待线程结束(这里可以根据需求修改,比如等待用户输入退出命令)
    WaitForSingleObject(sending_message_thread, INFINITE);
    WaitForSingleObject(receiving_message_thread, INFINITE);

    closesocket(socket_of_client);
    CloseHandle(hMutex);
    CloseHandle(sending_message_thread);
    CloseHandle(receiving_message_thread);
    WSACleanup();
    return 0;
}

unsigned WINAPI sending_message(void *arg) {
    SOCKET socket_of_client = *((SOCKET*)arg);

    while (1) {
        WaitForSingleObject(hMutex, INFINITE);
        fgets(message, 1024, stdin); // 替换gets为fgets,更安全
        // 去掉fgets读取的换行符
        message[strcspn(message, "\n")] = '\0';
        ReleaseMutex(hMutex);

        if (strcmp(message, "exit") == 0) { // 增加退出命令
            break;
        }

        int sendLen = strlen(message) + 1;
        send(socket_of_client, message, sendLen, 0);
    }

    return 0;
}

unsigned WINAPI receiving_message(void *arg) {
    SOCKET socket_of_client = *((SOCKET*)arg);
    int recvLen;

    while (1) {
        recvLen = recv(socket_of_client, message, 1023, 0);
        if (recvLen <= 0) { // 处理断开或错误
            printf("disconnected from server\n");
            break;
        }
        message[recvLen] = '\0';
        WaitForSingleObject(hMutex, INFINITE);
        printf("from server: %s\n\n", message);
        ReleaseMutex(hMutex);
    }

    return 0;
}

修复后的功能说明

  1. 客户端现在可以正常发送和接收消息,重启后不会出现接收异常的情况
  2. 服务端可以正确管理客户端连接,断开的客户端会被从数组中移除
  3. 增加了基本的错误处理和资源泄漏防护
  4. 替换了不安全的gets函数,发送消息的长度更加合理

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 09:09:12