TCP协议产生的粘包问题

粘包

所谓的粘包问题主要是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。发送方引起的粘包跟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都有一个参数,用来表示我们要接收多长的数据。每次根据得到的长度从缓冲区读取相应的数据字节即可。

参考链接
粘包与拆包