Netty(3)
TCP粘包和拆包
TCP是个"流"协议,所谓流,就是没有界限的一串数据。类似于自来水管,其间是没有分界线的。但一般通讯程序开发是需要定义一个个相互独立的数据包的,比如用于登陆的数据包,用于注销的数据包。由于TCP"流"的特性以及网络状况,在进行数据传输时会出现以下几种情况。
假设我们连续调用两次send分别发送两段数据data1和data2,在接收端有以下几种接收情况(当然不止这几种情况,这里只列出了有代表性的情况)。
A.先接收到data1,然后接收到data2。
B.先接收到data1的部分数据,然后接收到data1余下的部分以及data2的全部。
C.先接收到了data1的全部数据和data2的部分数据,然后接收到了data2的余下的数据。
D.一次性接收到了data1和data2的全部数据。
粘包出现的原因
即B、C、D情况:
1.由Nagle算法造成的发送端的粘包:Nagle算法是一种改善网络传输效率的算法。简单的说,当我们提交一段数据给TCP发送时,TCP并不立刻发送此段数据,而是等待一小段时间,看看在等待期间是否还有要发送的数据,若有则会一次把这两段数据发送出去。
2.接收端接收不及时造成的接收端粘包:TCP会把接收到的数据存在自己的缓冲区中,然后通知应用层取数据。当应用层由于某些原因不能及时的把TCP的数据取出来,就会造成TCP缓冲区中存放了几段数据.。
面向流的通信无消息保护边界(UDP协议有消息保护边界)。
**消息保护边界:**就是指传输协议把数据当作一条独立的消息在网上传输,接收端只能接收独立的消息。也就是说存在保护消息边界,接收端一次只能接收发送端发出的一个数据包。而面向流则是指无保护消息保护边界的,如果发送端连续发送数据,接收端有可能在一次接收动作中,会接收两个或者更多的数据包。
什么时候需要考虑粘包问题?
1、如果利用tcp每次发送数据,就与对方建立连接,然后双方发送完一段数据后,就关闭连接,这样就不会出现粘包问题(因为只有一种包结构,类似于http协议)。关闭连接主要要双方都发送close连接(参考tcp关闭协议)。如:A需要发送一段字符串给B,那么A与B建立连接,然后发送双方都默认好的协议字符如"hello give me sth abour yourself",然后B收到报文后,就将缓冲区数据接收,然后关闭连接,这样粘包问题不用考虑到,因为大家都知道是发送一段字符。
2、如果发送数据无结构,如文件传输,这样发送方只管发送,接收方只管接收存储就ok,也不用考虑粘包
3、如果双方建立连接,需要在连接后一段时间内发送不同结构数据,如连接后,有好几种结构:
-
“hello give me sth abour yourself”
-
“Don’t give me sth abour yourself”
那这样的话,如果发送方连续发送这个两个包出去,接收方一次接收可能会是"hello give me sth abour yourselfDon’t give me sth abour yourself" 这样接收方就傻了,到底是要干嘛?不知道,因为协议没有规定这么诡异的字符串,所以要处理把它分包,怎么分也需要双方组织一个比较好的包结构,所以一般可能会在头加一个数据长度之类的包,以确保接收。
解决方法
- 定义业务传输头,在头里面描述了开始标识符,再加数据长度,如0xAA + 数据长度,发送和接收端都通过固定格式进行读取处理
- 明确传输协议,如采用XML段或JSON格式进行传输,在接收完成后再进行业务处理
- 自定义某种格式,如Redis的协议,主要用于多次业务交互
心跳机制
心跳机制实现方式有2种:
- 使用 TCP 协议层面的 keepalive 机制。
- 在应用层上实现自定义的心跳机制。
一般的心跳检测机制都是定时向服务端请求或服务端定时向客户端发送数据以确认连接是否存活,简单来说就是保持长连接时使用。
这种情况一般存在两个问题:
一、请求会占用通道的资源,如果判断连接是否属于空闲;
二、定时任务维护成本;
Netty提供了IdleStateHandler 来进行心跳检测。
int readerIdleTimeSeconds //读超时时间
int writerIdleTimeSeconds //写超时时间
int allIdleTimeSecond //所有事件超时时间
TimeUnit unit //超时时间单位