有参考价值的文章:
调用send发送网络数据包一定会立马发送出去吗?_程序猿Ricky的日常干货的博客-CSDN博客_socket 立即发送
设置发送缓冲区大小,Nagle算法(避免等缓冲区满了才发送):
TCP数据发送问题整理_谢白羽的博客-CSDN博客_tcp发送
TCP粘包和拆包产生的原因的解决办法,以及计网面经:
计算机网络面经之TCP协议(二)_梅见西梅的博客-CSDN博客
粘包问题:
渔溪大王的博客_CSDN博客-java,java web,计算机网络知识领域博主
一、产生原因
网络通信方式主要有两种:TCP
与UDP
。 UDP
是基于报文传输的,发送几次Write()
,接收端就会用几次Read()
,每次读取一个报文,报文间不合并,多余缓冲区的报文会丢弃。TCP
是基于数据流传输的,Write()
和Read()
的次数不固定,报文间会以随机的方式合并,这就需要在接收时处理粘包了。
通过上面的分析,我们可以发现,粘包只可能出现在流式传输中。 其粘包原因可能是下面两种情况:
发送端需要等缓冲区才能发送数据,造成发送时就粘包;
接收端未及时接收缓冲区的数据,多包一起接收,造成粘包;
这个图我解释一下,就是说你的应用程序是两个数据分开发送的,但是实际上系统是等你的发送缓冲区满了再发送。这里,如果你的数据1长度大于缓冲区长度,他就会分两次发送出去。相反如果小于的话,就先不发,等着满了再发。上面链接有一个帖子说明了这个系统发送的问题。
一定要注意,接收的时候是缓冲区有多少,就尽量给多少给应用层
发生TCP粘包或拆包有很多原因,现列出常见的几点:
1、要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包。
2、待发送数据大于MSS(最大报文长度),TCP在传输前将进行拆包。
3、要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包。
4、接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包。
粘包/拆包的解决办法
解决问题的关键在于如何给每个数据包添加边界信息,常用的方法有如下几个:
1、发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。
2、发送端将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。
3、可以在数据包之间设置边界,如添加特殊符号,这样,接收端通过这个边界就可以将不同的数据包拆分开。(这一条程序上是怎么实现的呢)
二、解决方法
为了避免粘包,我们一般可以采取以下三种措施:
发送端粘包,可以通过程序设置
push
指令,不等缓冲区满就立即发送数据(默认情况是等缓冲区满后再发送)。这种方法对于通信的传输效率会降低,有时也不是百分百能可靠。接收端粘包,可以通过优化程序、精简进程工作量、提高进程优先级等措施,使其及时接收数据。这种方法,你可以发现,有时候也是无法优化的,实现起来会比较难。
采用自定义包头结构,人为控制多次合并,来避免粘包问题。这是常用的做法。
这里我针对项目中遇到的问题主要是接收数据时存在粘包问题,进行分析。TCP
在接收数据时有四种情况:
先接收到
data1
、再接收data2
,这是我们所期待的;先接收到
data1
的部分数据,再接收到data1
的余下部分以及data2
的全部;先接收到
data1
的全部数据和data2
的部分数据,再接收到data2
的部分数据;一次性接收
data1
与data2
的全部数据。
对于后三种情况,我们都是要进行粘包处理的。这里我将客户端接收小车数据的防粘包的处理方法代码示例出来,主要是定义了一个缓冲区,将从socket
中读取的数据放入缓冲区,再从缓冲区中按固定结构去解析数据。
粘包拆包问题是处于⽹络⽐较底层的问题,在数据链路层、⽹络层以及传输层都有可能发⽣;
TCP会发生粘包问题;TCP⽆消息保护边界,需要在接收端处理消息边界问题,也就是我们所说的粘包、拆包问题;
UDP不会发生粘包问题;UDP具有保护消息边界,在每个UDP包中就有了消息头(UDP长度、源端口、目的端口、校验和)。
粘包问题:粘包问题指,当发送方发送了数据包 `消息1 - ABC` 和 `消息2 - DEF` 时,但接收方接收到的数据包却是 `消息 - ABCDEF`,像这种一次性读取了两条数据包的数据粘连在一起的情况就叫做粘包(正常情况应该是一条一条读取的)。
拆包问题:
TCP 是面向连接的传输协议,TCP 传输的数据是以流的形式,而流数据是没有明确的开始结尾边界,所以 TCP 也没办法判断哪一段流属于一个消息;
TCP 协议是流式协议;所谓流式协议,即协议的内容是像流水一样的字节流,内容与内容之间没有明确的分界标志,需要认为手动地去给这些协议划分边界。
粘包主要原因:
发送方每次写入数据 < 接收方套接字(Socket)缓冲区大小; - 接收方读取套接字(Socket)缓冲区数据不够及时。
拆包问题
发送方每次写入数据 > 接收方套接字(Socket)缓冲区大小;
发送的数据大于协议的 MTU (Maximum Transmission Unit,最大传输单元),既TCP报⽂⻓度-TCP头部⻓度>MSS时发生拆包问题。
解决方案
方案一: 设置定⻓消息,服务端每次读取既定⻓度的内容作为⼀条完整消息(固定缓冲区大小);
固定缓冲区大小的实现方案,只需要控制服务器端和客户端发送和接收字节的(数组)长度相同即可。
方式二: 使⽤⾃定义协议+编解码器(封装请求协议);
将请求的数据封装为两部分:数据头+数据正文,在数据头中存储数据正文的大小,当读取的数据小于数据头中的大小时,继续读取数据,直到读取的数据长度等于数据头中的长度时才停止。
方案三: 设置消息边界,服务端从⽹络流中按消息编辑分离出消息内容(特殊字符结尾,按行读取)。
屏蔽Nagle算法(不推荐)
效果:可以看到粘包现象变少了,但没能有效解决该现象
int on = 1;
int nErr = setsockopt(TCP_Client_Sockfd, IPPROTO_TCP, TCP_NODELAY, (void *)&on, sizeof(on));
if(nErr==-1)
{
printf("Set error code: %d\n",nErr);
return 0;
}else{
printf("Set Successfully\n");
}