使用TCP通信时的粘包问题与解决

假设基于TCP/IP的网络环境已经创建好,当服务端在接收数据时,虽然TCP协议可以保证传输数据的可靠性,但是它的传输方式为流式套接字,
客户端在发送数据时对一次性发送的大小不可控,可能会一次性发送第一个完整的数据包和第二个数据包的一半即:
client:send(12)send(34)send(56)
sever: recv(123)recv(456)
解决思路:为每个数据包设置结束标志,使用队列的形式进行数据包缓冲处理
截取的代码如下:
while(1)
{
memset(p_recv_buf, 0, sizeof(recv_buf) - pre_pos);
read_bytes = read(events[index].data.fd, p_recv_buf, sizeof(recv_buf) - pre_pos);
if(read_bytes < 0)
{
perror("fail to recv");
return -1;
}
else if (0 == read_bytes)
{
printf("client disconnect\n");
epoll_ctl(epfd, EPOLL_CTL_DEL, events[index].data.fd, NULL);
close(events[index].data.fd);
continue;
}
else
{
//遍历接收的有效数据区
for(buf_index = 0; buf_index < read_bytes; buf_index ++)
{
//约定数据发送的结束标志(此处为'!')
if('!' == *p_recv_buf)
{
//当遇到结束标志时,回溯到该数据包的起始位置,打包并通过队列的方式进行缓存,然后探寻下一个数据包的结束位置
/* send msg */
memset(&msg, 0, sizeof(msg));
msg.msg_type = 100;
memcpy(msg.msg_buf, p_start, p_recv_buf - p_start + 1);
if(msgsnd(msg_id, &msg, sizeof(msg) - sizeof(long), 0) < 0)
{
perror("fail to send msg");
}
//没发送一个完整数据包时记录该包的结束位置
cur_pos = buf_index + 1;
//记录下一个数据包的起始位置
p_start = p_recv_buf + 1;
//此处不同的输入习惯处理不同
#if 0
while('\n' == *p_start)
{
p_start += 2;
cur_pos += 2;
}
#endif
}
p_recv_buf ++;
}
//记录未找到的结束标志的数据包的位置
if(cur_pos < read_bytes)
{
if(cur_pos != 0)
{
//将不完整的数据重定位到接收缓冲区的起始位置
memmove(recv_buf, recv_buf + pre_pos + cur_pos, read_bytes - cur_pos);
//改变下次接收时保存数据的起始位置
p_recv_buf = recv_buf + (read_bytes - cur_pos);
//记录重定位数据后有效数据的结束位置
pre_pos = read_bytes - cur_pos;
cur_pos = 0;
}
}
else
{
//若缓冲区中无不完整数据包,各记录值重新初始化
p_recv_buf = read_buf;
pre_pos = 0;
read_count = 0;
}
//每次遍历接收缓冲区结束时将数据包的起始位置更新至缓冲区的起始处
p_start = read_buf;
}
}

上述代码使用的队列是进程间的通讯之消息队列,新开一个进程接收消息并执行消息中的任务;也可以使用数据结构中的队列保存消息,然后创建一个线程从队列中读取消息并执行消息中的任务。
(完)

--------------------------------------------------------------------------------------------------------------------------------
由于水平有限,文档中若发现有错误的地方,希望大家及时指出,谢谢!

猜你喜欢

转载自blog.csdn.net/qq_41605726/article/details/80290924