粘包
所谓的粘包问题主要是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据
所造成的。发送方引起的粘包跟TCP协议本身有关
,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段
,这样接收方就收到了粘包数据。而UDP是面向消息的协议,每个UDP段都是一条消息
,应用程序必须以消息为单位提取数据,不能一次提取任意字节的数据。
粘包问题的解决方案
从本质上解决,在应用层维护消息与消息的边界
- 更复杂的应用层协议
- 用一个包体中绝不会出现的结束标志标识包结束
- 告知包的长度协议头开始固定长度的字节告知后续包长。收方先收包长字节,知道了后续包长后再收
方法三实现
我们自定义一个包体结构。先接收固定的4个字节,从中得知实际数据的长度n,再调用readn读取n个字符
,这样数据包之间有了界定。
struct packet {
int len;
char buf[1024];
};
void do_service(int conn)
{
struct packet recvbuf;
int n;
while (1)
{
memset(&recvbuf, 0, sizeof(recvbuf));
int ret = readn(conn, &recvbuf.len, 4);
if (ret == -1)
ERR_EXIT("read error");
else if (ret < 4) //客户端关闭
{
printf("client close\n");
break;
}
n = ntohl(recvbuf.len);
ret = readn(conn, recvbuf.buf, n);
if (ret == -1)
ERR_EXIT("read error");
if (ret < n) //客户端关闭
{
printf("client close\n");
break;
}
fputs(recvbuf.buf, stdout);
writen(conn, &recvbuf, 4 + n);
}
}
拆包
既然TCP传输容易出现粘包,并且在很多应用中,消息必须使用固定的格式发送。在这种情况下,我们需要拆包,按发送格式解析数据流
。大二参加华为精英挑战赛,德州扑克AI。服务器发送过来的数据流就出现了粘包问题。下面简单地介绍拆包的两种方法。
动态缓冲区暂存
每一个连接动态分配一个缓冲区,同时把此缓冲区和SOCKET关联
。当接收到数据时首先把此段数据存放在缓冲区中。判断缓冲区中数据长度是否够一个包头的长度,如不够,不进行拆包操作。根据包头解析出包体长度
。判断缓存区中除包头外的数据长度是否够一个包体的长度,如不够,不进行拆包操作。取出整个数据包
。不仅从缓冲区中拷贝出数据包,而且要把此数据包从缓存区中删除掉。把此包后面的数据移动到缓冲区的起始地址
。
空间上的消耗
:为每个连接动态分配一个缓冲区需要使用大量的内存时间上的消耗
:三个地方需要拷贝数据
,把数据存放在缓冲区,把完整的数据包从缓冲区取出来,把数据包从缓冲区中删除。
动态缓冲区暂存的优化
TCP协议本身维护了一个缓冲区
,所以我们可以直接利用TCP的缓冲区来缓存我们的数据,不需要为每一个连接分配一个缓冲区了。recv或者wsarecv都有一个参数,用来表示我们要接收多长的数据
。每次根据得到的长度从缓冲区读取相应的数据字节即可。
参考链接
粘包与拆包