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

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

关键修改说明

  1. 缓冲区初始化:每次调用recv前用memset清空缓冲区,彻底避免旧数据残留。
  2. 接收长度参数修正:用sizeof(缓冲区)-1作为recv的长度,预留1字节手动添加'\0',保证字符串正确终止。
  3. 手动添加终止符:recv不会自动为接收的数据添加字符串终止符,必须手动添加,否则printf会读取到缓冲区里的垃圾数据(比如空格)。
  4. 发送长度修正:客户端发送时用strlen(st)+1,只发送必要的终止符,避免多余垃圾数据干扰。
  5. 客户端断开处理:服务器在客户端断开后从数组中移除并释放Socket资源,避免向无效Socket发送数据引发异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 04:19:00