TCP粘包/拆包问题
在业务上可能会存在一个完整的包被TCP拆分为多个包进行发送,也可能把多个小的包装封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题
图解:
解决方案:
- 消息定长,例如每个报文的大小为固定长度200字节,如果不够,空位补空格; (2)在包尾增加回车换行符进行分割,例如FTP协议 (3)将消息分为消息头和消息体,消息头中包含表示消息总长度(或者消息体长月 的字段,通常设计思路为消息头的第一个字段使用 int32来表示消息的总长度; (4)更复杂的应用层协议
利用Line based frameDecoder解决TCP粘包问题例子
Server端:
在handler之前增加了两个解码器
对比之前的string body:
Client端:
此时拿到的msg消息已经是解码成字符串后的应答消息了
也就是说,只需要将支持半包解码的handler添加到channelpipeline中即可
解码器原理分析:LineBasedFramcDecoder的工作原理是它依次遍历 ByteBuf 中的可读字节,判断看是 否有“\n”或者“\r\n”,如果有,就以此位置为结束位置,从可读索引到结束位置区间的 字节就组成了一行;stringDecoder的功能非常简单,就是将接收到的对象转换成字符串,然后继续调用后 面的Handler。LineBasedFrameDecoder+StringDecoder 组合就是按行切换的文本解码器, 它被设计用来支持TCP的粘包和拆包。
分隔符和定长解码器的应用
DeimiterBasedFrameDecoder 应用开发:
通过对DelimiterBasedFrameDecoder的使用,我们可以自动完成以分隔符作为码流结束标识的消息的解码
FixedLengthFrameDecoder 应用开发 :
FixedLengthFrameDecoder 是固定长度解码器,它能够按照指定的长度对消息进行自动解码
还可以利用telnet进行测试,具体见书本
解编码技术
Java对象可以通过序列化技术作为字节数组写入文件,可以上传网络
Java序列化的缺点:
无法跨语言、序列化后的码流太大、序列化性能太低
私有协议栈开发
利用netty的NIO TCP协议来开发私有协议
所谓协议的开发,就是利用netty来对协议功能和一些模块进行规定
一个netty节点可以作为服务端也可以作为客户端
协议栈功能描述:(1)基于Netty的NIO通信框架,提供高性能的异步通信能力;
- 提供消息的编解码框架,可以实现 POJO的序列化和反序列化;
(3)提供基于IP地址的白名单接入认证机制;
(4)链路的有效性校验机制;
(5)链路的断连重连机制。
协议应当具有上述功能
通信模型:
心跳检测机制:双方之间的心跳采用 Ping-Pong机制,当链路处于空闲状态时,客 ,户端主动发送Ping 消息给服务端,服务端接收到Ping 消息后发送应答消息Pong给客户端, 如果客户端连续发送N条 Ping 消息都没有接收到服务端返回的Pong 消息,说明链路已经 ,挂死或者对方处于异常状态,客户端主动关闭连接,间隔周期 T后发起重连操作,直到重 连成功。
消息定义:
Netty协议的编解码规范:
链路的建立:
客户端与服务端链路建立成功之后,由客户端发送握手请求消息,握手请求消息的定 义如下: (1)消息头的 type 字段值为3; (2)可选附件为个数为0; (3)消息体为空; (4)握手消息的长度为22个字节。
服务端接收到客户端的握手请求消息之后,如果 IP 校验通过,返回握手成功应答 息给客户端,应用层链路建立成功。握手应答消息定义如下。(1)消息头的 type 字段值为4: (2)可选附件个数为0; (3)消息体为byte类型的结果,“0”表示认证成功;“-1”表示认证失败。 链路建立成功之后,客户端和服务端就可以互相发送业务消息了。
链路的关闭:
一般不需要主动关闭,但在如下情况下需要关闭:宕机重启、读写过程发生IO异常、心跳消息读写发生IO异常、心跳超时、编码异常
协议可靠性设计:心跳机制、重连机制(客户端发起重连操作,首次断联时客户端等待INTERVAL事件之后再发起重连)、重复登陆保护(在缓存的地址中查看客户端是否已经登录)、消息缓存重发(无论客户端还是服务端,当发生链路中断之后,在链路恢复之前,缓存在消息队列中 ,待发送的消息不能丢失,等链路恢复之后,重新发送这些消息,保证链路中断期间消息不 丢失)
消息头header类定义:
消息编码类:
消息解码类:
握手和安全认证:
客户端部分
服务端握手接入和安全认证代码:
首先根据客户端的源地址(/127.0.0.1:12088)进行重复 登录判断,如果客户端已经登录成功,拒绝重复登录,以防止由于客户端重复登录导致的句 柄泄漏。随后通过 ChanneIHandlerContext 的Channel 接口获取客户端的 InetSocketAddress 地址,从中取得发送方的源地址信息,通过源地址进行白名单校验,校验通过握手成功, 否则握手失败。最后通过 buildResponse 构造握手应答消息返回给客户端