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; }
修复后的功能说明
- 客户端现在可以正常发送和接收消息,重启后不会出现接收异常的情况
- 服务端可以正确管理客户端连接,断开的客户端会被从数组中移除
- 增加了基本的错误处理和资源泄漏防护
- 替换了不安全的
gets函数,发送消息的长度更加合理
内容的提问来源于stack exchange,提问作者allentando
相关产品推荐
相关产品推荐

