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()函数的逻辑是拼接所有读取内容直到最后一个字符为'|',会把前一个消息的结束符'|'和后一个消息的内容拼接在一起,最终返回的消息中保留了多余的'|'。
同时原函数存在两个隐患:
- 未正确处理
read()的返回值,也未根据实际读取字节数构造字符串,可能导致越界读取栈内存(当read()满2048字节时,buffer末尾无'\0',string s(buffer)会读取栈中随机数据)。 - 修改后的函数试图删除中间的'|',但循环逻辑错误(
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
相关产品推荐
相关产品推荐

