什么是粘包
粘包问题是指当发送两条消息时,比如发送了 ABC 和 DEF,但另一端接收到的却是 ABCD,像这种一次性读取了两条数据的情况就叫做粘包(正常情况应该是一条一条读取的)。
- 发送方每次写入数据 < 套接字缓冲区大小
- 接收方读取套接字缓冲区数据不够及时
什么是半包
半包问题是指,当发送的消息是 ABC 时,另一端却接收到的是 AB 和 C 两条信息,像这种情况就叫做半包。
- 发方每次写入数据 > 套接字缓冲区大小
- 发送的数据大于协议MTU(Maximum Transmission Unit,最大传输单元),必须拆包
从便于理解的角度来看
- 收发
- 一个发送可能被多次接收,多个发送可能被一次接收
- 传输
- 一个发送可能占用多个传输包,多个发哦是哪个可能公用一个传输包
根本原因:TCP是流式协议,消息无边界
UDP有粘包和半包问题么?
UPD是面向消息的,它有边界协议,可以根据消息的格式区分消息的开始和结尾,UDP和TCP两个发送消息就好像一个用桶运水,一个用水管运水,用水管运水的你是没办法区分那部分的水是属于哪一桶的。
解决方案
固定包长数据包(长度边界)
顾名思义:每个包长度都是固定的。举个例子,规定每个协议包的大小是 64 个字节,每次收满 64 个字节,就取出来解析;如果没有满,则暂存在 mem 中,TCP 流继续往后解析。 这种协议实现格式很简单,但是空间利用率可能会低。因为包长度不满足固定字长,剩余的空间就需要用特殊字符填充 (特殊字符需要和真实数据区分开)。 而包内容超过指定字节数,又得分包分片,需要增加额外处理逻辑:
- 发送端需要根据固定长包截断
- 接收端需要组装分片包
指定结束标志数据包(符号边界)
这种解决方案在协议处理上比较常见,即:字节流中遇到特殊的符号值时就认为到一个包的末尾。 例如,我们熟悉的 FTP协议,发邮件的 SMTP 协议,一个命令或者一段数据后面加上"\r\n"(即所谓的 CRLF)表示一个包的结束。对端收到后,解析数据流时,解析到 “\r\n” 就把之前解析组装一个数据包,交给后续的 处理函数 处理。 但是这里会出现一个问题:如果协议数据包内容部分出现了包结束标志字符,就需要对这些字符做转码或者转义操作,以免被接收方错误地当成包结束标志而误解析。
header+content(组合边界)
这种方式先是定义一个Header+Body格式,Header消息头里面定义了一个开始标记+一个内容的长度,这个内容长度就是Body的实际长度,Body里面是消息内容,当接收方接收到数据流时,先根据消息头里的特殊标记来区分消息的开始,获取到消息头里面的内容长度描述时,再根据内容长度描述来截取Body部分。