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

C++ TCP Socket收发字符串时多余分隔符'|'的问题排查

C++ Socket收发消息出现多余分隔符'|'问题排查

问题概述

用C++实现Socket客户端与服务器的数据收发,参考实现了sendMsg()和receive()函数保证完整收发,但新客户端连接后发送消息时,常出现多余的分隔符'|'。

现有代码

原收发函数

string receive()
{
    string msg = "";
    char last_character = '%';
    do
    {
        char buffer[2048] = {0};
        read(sockfd, buffer, 2048);
        string s(buffer);
        msg += s;
        last_character = s[s.size() - 1];
    } while (last_character != '|');
    return msg.substr(0, msg.size() - 1);
}

bool sendMsg(string s)
{
    s += '|';
    char buffer[s.size()];
    strcpy(buffer, &s[0]);
    int bytes_total = 0;
    int bytes_left = s.length();
    int bytes_now = 0;
    while (bytes_left > 0)
    {
        bytes_now = send(sockfd, &buffer[bytes_total], bytes_left, 0);
        if (bytes_now == -1)
        {
            err("Error sending message");
            return false;
        }
        assert(bytes_now <= bytes_left);
        bytes_left -= bytes_now;
        bytes_total += bytes_now;
    }

    return true;
}

服务器发送逻辑

usernames[username] = client_socket;
comm.sendMsg(string(GRN) + "Username accepted.\n" + string(NRM));
cout << GRN << username << " connected." << NRM << endl;
comm.sendMsg(readme(username));

usernames是存储客户端socket的unordered_map,readme()返回类似"Welcome to the chatroom, " + username的字符串,GRN/NRM为终端颜色宏。

异常现象

客户端收到的消息中出现多余'|',示例输出:

Enter the IP address of the server: l
Connected to server.
Enter your username: user
Username accepted.
|
+------------------------------------------------------------+
|                                                            |
|   [server]: Welcome to the chatroom, user                  |
|                                                            |
|   [server]: Here are the commands you can use:             |
|                                                            |
|   1. status             : Lists the status of all users    |
|   2. connect [username] : Connect to username to start     |
|                           chatting                         |
|   3. goodbye            : Ends current chatting session    |
|   4. close              : Disconnects the user from the    |
|                           server                           |
|   5. clear              : Clears the chat from the window  |
|   6. Ctrl + C (client)  : Disconnects the client and       |
|                           terminates its chat session      |
|                           if present                       |
|   7. Ctrl + C (server)  : Terminates the server and all the|
|                           clients connected to the server  |
+------------------------------------------------------------+

尝试的修改(未解决问题)

string receive()
{
    string msg = "";
    char last_character = '%';
    while (last_character != '|')
    {
        char buffer[2048] = {0};
        int bytes_read = read(sockfd, buffer, 2048);
        if (bytes_read <= 0)
        {
            err("Error receiving message");
            return "";
        }
        string s(buffer, bytes_read);
        for (int i = 0; i < s.size() - 1; i++)
            if (s[i] == '|')
            {
                s.erase(s.begin() + i);
            }
        msg += s;
        last_character = s[s.size() - 1];
    }
    return msg.substr(0, msg.size() - 1);
}

问题根源与解决方案

核心问题

TCP是流式协议,服务器连续调用sendMsg()的两次数据可能被合并为一个TCP包发送(粘包)。原receive()函数的逻辑是拼接所有读取内容直到最后一个字符为'|',会把前一个消息的结束符'|'和后一个消息的内容拼接在一起,最终返回的消息中保留了多余的'|'。

同时原函数存在两个隐患:

  1. 未正确处理read()的返回值,也未根据实际读取字节数构造字符串,可能导致越界读取栈内存(当read()满2048字节时,buffer末尾无'\0',string s(buffer)会读取栈中随机数据)。
  2. 修改后的函数试图删除中间的'|',但循环逻辑错误(erase()后未调整索引,会漏删部分字符),且未从根本上解决粘包问题。

正确实现

需要维护一个接收缓冲区,每次读取数据后追加到缓冲区,再从缓冲区中提取完整消息(以'|'为分隔符),剩余数据留待下次处理:

改进后的receive()函数(需添加类成员变量string recv_buffer;)

string receive()
{
    char buffer[2048];
    ssize_t bytes_read;

    while (true)
    {
        // 先检查缓冲区中是否已有完整消息
        size_t delimiter_pos = recv_buffer.find('|');
        if (delimiter_pos != string::npos)
        {
            string msg = recv_buffer.substr(0, delimiter_pos);
            recv_buffer = recv_buffer.substr(delimiter_pos + 1);
            return msg;
        }

        // 无完整消息,读取新数据
        bytes_read = read(sockfd, buffer, sizeof(buffer));
        if (bytes_read <= 0)
        {
            err("Error receiving message");
            return "";
        }
        recv_buffer.append(buffer, bytes_read);
    }
}

改进后的sendMsg()函数(替换非标准变长数组)

bool sendMsg(string s)
{
    s += '|';
    const char* buffer = s.data();
    size_t bytes_total = 0;
    size_t bytes_left = s.length();
    ssize_t bytes_now;

    while (bytes_left > 0)
    {
        bytes_now = send(sockfd, buffer + bytes_total, bytes_left, 0);
        if (bytes_now == -1)
        {
            err("Error sending message");
            return false;
        }
        bytes_left -= bytes_now;
        bytes_total += bytes_now;
    }

    return true;
}

结论

问题出在receive()函数未处理TCP粘包场景,导致多余分隔符残留。改用带缓冲区的接收逻辑即可解决,无需排查其他代码(只要服务器端未错误发送多余'|')。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 03:37:37