怎么解决TCP网络传输「粘包」问题?

TCP协议的流式特性可能导致粘包问题,接收端无法直接确定数据包边界。解决粘包常用方法包括:固定包长、以指定字符为结束标志、包头+包体格式。包头+包体格式中,包头包含包体大小信息,通过解包流程判断包头、获取包体大小并处理包。解包处理需包含判断和校验,确保数据的正确解析和处理。
摘要由CSDN通过智能技术生成

一、TCP 协议是流式协议

很多读者从接触网络知识以来,应该听说过这句话:TCP 协议是流式协议。那么这句话到底是什么意思呢?所谓流式协议,即协议的内容是像流水一样的字节流,内容与内容之间没有明确的分界标志,需要我们人为地去给这些协议划分边界。

举个例子,A 与 B 进行 TCP 通信,A 先后给 B 发送了一个 100 字节和 200 字节的数据包,那么 B 是如何收到呢?B 可能先收到 100 字节,再收到 200 字节;也可能先收到 50 字节,再收到 250 字节;或者先收到 100 字节,再收到 100 字节,再收到 100 字节;或者先收到 20 字节,再收到 20 字节,再收到 60 字节,再收到 100 字节,再收到 50 字节,再收到 50 字节......

不知道读者看出规律没有?规律就是 A 一共给 B 发送了 300 字节,B 可能以一次或者多次任意形式的总数为 300 字节收到。假设 A 给 B 发送两个大小分别是 100 字节和 200 字节的数据包,作为发送方的 A 来说,A 是知道如何划分这两个数据包的界限的,但是对于 B 来说,如果不人为规定多少字节作为一个数据包,B 每次是不知道应该把收到的数据中多少字节作为一个有效的数据包的,而规定每次把多少数据当成一个包就是协议格式定义的内容之一。

经常会有新手写出类似下面这样的代码:

发送端:

//...省略创建socket,建立连接等部分不相关的逻辑...
char buf[] = "the quick brown fox jumps over a lazy dog.";
int n = send(socket, buf, strlen(buf), 0);
//...省略出错处理逻辑...

接收端:

//省略创建socket,建立连接等部分不相关的逻辑...
char recvBuf[50] = { 0 };
int n = recv(socket, recvBuf, 50, 0);
//省略出错处理逻辑...
printf("recvBuf: %s", recvBuf);

为了专注问题本身的讨论,我这里省略掉了建立连接和错误处理的逻辑。上述代码中发送端给接收端发送了一串字符”the quick brown fox jumps over a lazy dog.“,接收端收到后将其打印出来。

类似这样的代码在本机一般会工作的很好,接收端也如期打印出来预料的字符串,但是一放到局域网或者公网环境就出问题了,即接收端可能打印出来字符串并不完整;如果发送端连续多次发送字符串,接收端会打印出来的字符串不完整或出现乱码。不完整的原因很好理解,即对端某次收到的数据小于完整字符串的长度,recvBuf 数组开始被清空成 \0,收到部分字符串后,该字符串的末尾仍然是 \0,printf 函数寻找以 \0 为结束标志的字符结束输出;乱码的原因是如果某次收入的数据不仅包含一个完整的字符串,还包含下一个字符串部分内容,那么 recvBuf 数组将会被填满,printf 函数输出时仍然会寻找以 \0 为结束标志的字符结束输出,这样读取的内存就越界了,一直找到 \0 为止,而越界后的内存可能是一些不可读字符,显示出来后就乱码了。

我举这个例子的目的是希望你能对“ TCP 协议是流式协议”有一个直观的认识。正因为如此,我们需要人为地在发送端和接收端规定每一次的字节流边界,以便接收端知道从什么位置取出多少字节来当成一个数据包去解析,这是我们设计网络通信协议格式要做的工作之一。

二、如何解决粘包问题

网络通信程序实际开发中,或者技术面试时,面试官通常会问的比较多的一个问题是:网络通信时,如何解决粘包?

有的面试官可能会这么问:网络通信时,如何解决粘包、丢包或者包乱序问题?这个问题其实是面试官在考察面试者的网络基础知识,如果是 TCP 协议,在大多数场景下,是不存在丢包和包乱序问题的,TCP 通信是可靠通信方式,TCP 协议栈通过序列号和包重传确认机制保证数据包的有序和一定被正确发到目的地;如果是 UDP 协议,如果不能接受少量丢包,那就要自己在 UDP 的基础上实现类似 TCP 这种有序和可靠传输机制了(例如 RTP 协议、RUDP 协议)。所以,问题拆解后,只剩下如何解决粘包的问题。

先来解释一下什么是粘包,所谓粘包就是连续给对端发送两个或者两个以上的数据包,对端在一次收取中收到的数据包数量可能大于 1 个,当大于 1 个时,可能是几个(包括一个)包加上某个包的部分,或者干脆就是几个完整的包在一起。当然,也可能收到的数据只是一个包的部分,这种情况一般也叫半包。

粘包示意图如下:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值