TCP粘包解决方案
黏包的在客户端发送频率低的情况下粘包不明显,下面是原来的服务器中epoll一个事件可读的回调函数recvdata(event_infor *infor)
,当调用这个函数然后recv()
函数读缓冲区的数据,指定大小为BUFF_MAX-1
,而BUFF_MAX
我设置为1024,一般单次接收数据小于这个值,所以基本上就是读取了所有缓冲区数据,接收过程:收到数据->epoll可读->调用回调函数->读取缓冲区所有数据->空缓冲区等待下一次接收。在处理客户端发来的聊天消息时还没有发生粘包现象,处理文件传输时一直粘包,问题分析是客户端高速循环调用send()
发送数据,也就是说服务器其实recv()
多条数据,比如说发送{"code":1}
和{"code":2}
服务器读一次可能是{"code":1}{"co
。
原服务器代码:
void Server::recvdata(event_infor *infor) {
int n = recv(infor->fd, infor->buff, BUFF_MAX - 1, 0);
if (n > 0) { //收到的数据
infor->buff[n] = '\0';
LOG(INFO) << infor->ip << " 发来一条消息: " << infor->buff;
_messageQueue->push(infor->buff, infor);
} else if (n == 0) {
LOG(INFO) << "fd: " << infor->fd << " 连接关闭";
close(infor->fd);
infor->status = false;
} else {
close(infor->fd);
infor->status = false;
LOG(INFO) << "fd: " << infor->fd << " 连接关闭";
}
bzero(infor->buff, BUFF_MAX - 1);
}
解决办法见下:
//结构体定义
struct MessageStruct{
int jsonLen;
char json[0]; //不能换成char *json
};
客户端代码:
string data = "{\"code\":0,\"data\":{\"passwd\":\"123456\",\"name\":\"daimiaopeng\"}}";
int len = sizeof(MessageStruct) + data.size();
MessageStruct *messageStruct = static_cast<struct MessageStruct *>(malloc(len));
messageStruct->jsonLen = data.size();
strncpy(messageStruct->json, data.c_str(), data.size());
//也可以用memcpy(),这里是string->char* 而不是char*->char*
for (int i = 0; i < 1000; i++) {
send(sockfd, messageStruct, len, 0);
}
free(messageStruct);
改进后的服务器代码:
void Server::recvdata(event_infor *infor) {
MessageStruct messageStruct;
//读取结构体
int n = recv(infor->fd, &messageStruct, sizeof(MessageStruct), 0);
if (n > 0) { //收到的数据
//获取结构体中的json长度字段
char *jsonData = static_cast<char *>(malloc(messageStruct.jsonLen));
//再读json数据
if (jsonData== nullptr)
return;
recv(infor->fd, jsonData, messageStruct.jsonLen, 0);
_messageQueue->push(string(jsonData, messageStruct.jsonLen), infor);
free(jsonData);
} else if (n == 0) {
LOG(INFO) << "fd: " << infor->fd << " 连接关闭";
close(infor->fd);
infor->status = false;
} else {
close(infor->fd);
infor->status = false;
LOG(INFO) << "fd: " << infor->fd << " 连接关闭";
}
}
总的来说是通过定义一个结构体然后二进制发送,读messageStruct
的大小数据,sizeof(messageStruct)
大小就是int
类型的大小,为4,实际上后面还跟了数据,char json[0]
不占用空间,而char *json
是占用8个字节的,所以不能用char *json
代替char json[0]
这里是个坑注意一下,转换完成读取jsonLen的值,再读jsonLen大小的数据这样就完成分包了。这里比较难理解,建议网上多看几篇通过结构体解决粘包的文章,结合本文的代码理解。