概念长连接与短连接:
1.长连接
Client方与Server方先建立通讯连接,连接建立后不断开, 然后再进行报文发送和接收。
2.短连接
Client方与Server每进行一次报文收发交易时才进行通讯连接,交易完毕后立即断开连接。此种方式常用于一点对多点
通讯,比如多个Client连接一个Server.
什么时候需要考虑粘包问题?
1:如果利用tcp每次发送数据,就与对方建立连接,然后双方发送完一段数据后,就关闭连接,这样就不会出现粘包问题(因为只有一种包结构,类似于http协议)。关闭连接主要要双方都发送close连接(参考tcp关闭协议)。如:A需要发送一段字符串给B,那么A与B建立连接,然后发送双方都默认好的协议字符如"hello give me sth abour yourself",然后B收到报文后,就将缓冲区数据接收,然后关闭连接,这样粘包问题不用考虑到,因为大家都知道是发送一段字符。
2:如果发送数据无结构,如文件传输,这样发送方只管发送,接收方只管接收存储就ok,也不用考虑粘包
3:如果双方建立连接,需要在连接后一段时间内发送不同结构数据,如连接后,有好几种结构:
1)"hello give me sth about yourself"
2)"Don't give me sth about yourself"
那这样的话,如果发送方连续发送这个两个包出去,接收方一次接收可能会是"hello give me sth abour yourselfDon't give me sth abour yourself" 这样接收方就傻了,到底是要干嘛?不知道,因为协议没有规定这么诡异的字符串,所以要处理把它分包,怎么分也需要双方组织一个比较好的包结构,所以一般可能会在头加一个数据长度之类的包,以确保接收。
粘包在流传输中出现,UDP不会出现粘包,因为它有消息边界(参考Windows 网络编程)
1.发送端引起粘包:
由于上述的效率的大量牺牲,tcp在设计的时候,采用优化算法:Nagle算法,将数据合并成一个大包发送出去,可想而知,这个包到了接收端的接收缓冲区,内容就是多个包链接在一起的。UDP首先禁用了Nagle算法,有消息边界,所以不会出现粘包。
2.接收端引起粘包:
接收端的粘包形成的原因很多,第1种就是nagle算法引起的,包一到接收缓冲区就是粘包。很多人觉得禁用了Nagle算法就好了,但是如果我们的进程太繁忙,很可能出现无法及时取出数据,那么TCP粘包还是会出现,为什么?因为每一个到达的消息全部去掉TCP,IP的头,剩下的数据就是拼接在一起,那么我们还是分不开。
那么为什么UDP为什么不会呢?
在UDP协议的接收端,采用了链式结构来记录每一个到达的UDP包,这样接收端应用程序一次recv只能从socket接收缓冲区中读出一个数据包。也就是说,发送端send了几次,接收端必须recv几次(无论recv时指定了多大的缓冲区)。
how
那么如何避免呢?
1.TCP禁用nagle算法:通过TCP_NODELAY实现,但是可惜这个只能一定程度减少粘包的情况,原因:就是接收端不可能一直保持在兴奋状态一来就取,总会繁忙,并且网络延时,路由方式不同,导致两个包就是一起到的。所以这个方式不好。
2.学UDP那样在socket缓冲区设计一个链式结构来实现?暂时不知道怎么搞呀。
3.在接收端设置,通过该设置方案来实现分隔消息。方案有如下几种:
3.1.通过设置结束符:我们可以在一个消息结尾设置一个结束符来实现消息分隔。但是会出现的问题:1.该分隔符一定不能出现在消息里,不然消息就雪崩式错位。2.这个只适用在简单的消息。
3.2.通过设置长度:这里有的人或许会说,如果长度设置出错,那么缓冲区所有的数据全都错了。但是我们这些一般都是公共函数,不会去变的。
3.2.先发送一个int的消息长度的消息,然后根据这个长度取数据。但是万一出现:体+长度+体。那我们可以通过体的首几个字节设置成非数字。如果4个字节可以转换成int,直接取数据体,但是直接取到数据体,那还得定位到下个表示长度的首地址在哪,感觉好麻烦。。。。以后再考虑吧
3.3.TCP短连接方式:发一次就关闭,这种太脑残了。如果是多个客户端对应一个服务端还可以。否则每次通讯都要3次握手,4次断连,那可以离职了。
3.4.学习把消息长度填写进,这样就可以避免了3.2的方法的那个体+长度+体的错误了。
3.5.设置自己的协议。一般就是协议头(协议头肯定要包含长度,校验等)+协议体。协议体通过keyvalue的方式。一个avpcode对应一个值。为什么要这么设置呢,因为我们业务类型多的时候,可以通过这种方式实现正确的封包和解包。这个还有一个好处就是:如果你的包被人抓下来,然后串改,但是我们的协议只要一个avpcode对不上就可以认定这个包有问题。一定程度防止了被抓包串改的情况。(一般一个业务的avpcode都有几十个)
解决办法:
粘包与分包的处理方法:
我根据现有的一些开源资料做了如下总结(常用的解决方案):
一个是采用分隔符的方式,即我们在封装要传输的数据包的时候,采用固定的符号作为结尾符(数据中不能含结尾符),这样我们接收到数据后,如果出现结尾标识,即人为的将粘包分开,如果一个包中没有出现结尾符,认为出现了分包,则等待下个包中出现后 组合成一个完整的数据包,这种方式适合于文本传输的数据,如采用/r/n之类的分隔符;
另一种是采用在数据包中添加长度的方式,即在数据包中的固定位置封装数据包的长度信息(或可计算数据包总长度的信息),服务器接收到数据后,先是解析包长度,然后根据包长度截取数据包(此种方式常出现于自定义协议中),但是有个小问题就是如果客户端第一个数据包数据长度封装的有错误,那么很可能就会导致后面接收到的所有数据包都解析出错(由于TCP建立连接后流式传输机制),只有客户端关闭连接后重新打开才可以消除此问题,我在处理这个问题的时候对数据长度做了校验,会适时的对接收到的有问题的包进行人为的丢弃处理(客户端有自动重发机制,故而在应用层不会导致数据的不完整性);