C++ TCP Socket多客户端聊天程序回显异常求助
问题分析与修复方案
看起来你这个多客户端聊天程序的问题主要出在字符串处理和Socket接收/发送的长度参数错误上,咱们一步步来解决:
核心错误点
- 未初始化缓冲区导致
strlen返回随机值:不管是服务器的st还是客户端的recvM,在调用recv时都用了strlen(缓冲区)作为接收长度,但这些缓冲区一开始是未初始化的,内存里是随机数据,strlen会一直读到第一个'\0'才停止,导致接收长度完全不可控——这就是为什么一开始偶尔正常,后来就出现无尽空格的原因,因为缓冲区里残留了之前的数据,或者strlen返回了一个错误的长度,导致接收了一堆空字符。 - 发送长度计算错误:客户端发送时用了
strlen(st)+2,实际上strlen(st)已经包含了fgets读取的有效字符(包括换行),加1就足够包含终止符'\0',加2会多发送一个字节的垃圾数据。 - 未正确处理客户端断开后的资源清理:服务器在客户端断开后没有从
MyClients数组中移除该客户端,后续循环发送时会向已关闭的Socket发送数据,可能引发异常。
修正后的服务器端代码
#include "PracticalSocket.h" #include <stdio.h> #include <process.h> #include <Windows.h> #include <cstring> // 确保memset、strlen等字符串函数正常工作 using namespace std; #pragma comment(lib,"ws2_32.lib") TCPSocket* MyClients[20]; int ClientCount = 0; void connectCLient(void* pValue) { int nI, Flag; char st[1024]; TCPSocket* pServerClient = (TCPSocket*)pValue; // 将新客户端加入数组(正式环境需加锁保证线程安全,测试阶段可跳过) MyClients[ClientCount] = pServerClient; ClientCount++; try { while (true) { // 每次接收前清空缓冲区,避免残留旧数据 memset(st, 0, sizeof(st)); // 使用缓冲区总长度作为接收参数,留1字节给终止符 Flag = pServerClient->recv(st, sizeof(st) - 1); if (Flag > 0) { // 接收到有效数据 st[Flag] = '\0'; // 手动添加字符串终止符,recv不会自动添加 printf("Received: %s\n", st); // 转发消息给所有其他客户端 for (nI = 0; nI < ClientCount; nI++) { if (MyClients[nI] == pServerClient) continue; // 跳过发送者自身 MyClients[nI]->send(st, Flag + 1); // 发送实际长度+1(包含终止符) } } else if (Flag == 0) { printf("One client disconnected\n"); break; // 客户端正常断开,退出循环 } else { printf("Receive error from client\n"); break; // 接收出错,退出循环 } } } catch (...) { puts("Exception: one client left"); } // 客户端断开后,从数组中移除并清理资源 for (nI = 0; nI < ClientCount; nI++) { if (MyClients[nI] == pServerClient) { // 后面的客户端前移覆盖当前位置 for (int j = nI; j < ClientCount - 1; j++) { MyClients[j] = MyClients[j+1]; } ClientCount--; break; } } delete pServerClient; // 释放Socket资源 } int main(int argc, char* argv[]) { TCPServerSocket* pServer = new TCPServerSocket(8546); int nClientCounter = 0; printf("Start TCP Server ... on Port %d\n", 8546); try { while(true) { printf("Wait for new TCP Clients ... \n"); TCPSocket* pClient = pServer->accept(); _beginthread(connectCLient, 0, (void*)pClient); printf("Client %d Connected ... \n", ++nClientCounter); } } catch(...) { puts("Server exception occurred"); } delete pServer; return 0; }
修正后的客户端代码
#include "PracticalSocket.h" #include <stdio.h> #include <process.h> #include <Windows.h> #include <cstring> using namespace std; #pragma comment (lib, "ws2_32.lib") void ReciveMessages(void * pValue ){ char recvM[1024]; TCPSocket* pClient = (TCPSocket*)pValue; while(true){ memset(recvM, 0, sizeof(recvM)); // 接收前清空缓冲区 int recvLen = pClient->recv(recvM, sizeof(recvM) - 1); if (recvLen > 0) { recvM[recvLen] = '\0'; // 手动添加终止符 printf("Received from server: %s\n", recvM); } else if (recvLen == 0) { printf("Server disconnected\n"); break; } else { printf("Receive error\n"); break; } } } int main(int argc, char* argv[]) { try { TCPSocket * cClient = new TCPSocket(); cClient->connect("127.0.0.1", 8546); _beginthread(ReciveMessages, 0, (void*)cClient); char st[1024]; while(true) { printf("Press Text -->"); memset(st, 0, sizeof(st)); // 读取输入前清空缓冲区 fgets(st, sizeof(st), stdin); // 可选:去掉fgets读取的换行符,避免消息末尾带换行 st[strcspn(st, "\n")] = '\0'; cClient->send(st, strlen(st) + 1); // 发送长度为有效字符+1(包含终止符) } } catch(...) { printf("Socket Error..!"); system("pause"); } return 0; }
关键修改说明
- 缓冲区初始化:每次调用
recv前用memset清空缓冲区,彻底避免旧数据残留。 - 接收长度参数修正:用
sizeof(缓冲区)-1作为recv的长度,预留1字节手动添加'\0',保证字符串正确终止。 - 手动添加终止符:
recv不会自动为接收的数据添加字符串终止符,必须手动添加,否则printf会读取到缓冲区里的垃圾数据(比如空格)。 - 发送长度修正:客户端发送时用
strlen(st)+1,只发送必要的终止符,避免多余垃圾数据干扰。 - 客户端断开处理:服务器在客户端断开后从数组中移除并释放Socket资源,避免向无效Socket发送数据引发异常。
内容的提问来源于stack exchange,提问作者israel
相关产品推荐
相关产品推荐

