目录
一、粘包 & 半包
1、现象分析
粘包
半包
二、解决方案
1、短连接
2、定长解码器
3、分隔符
4、长度字段解码器
三、协议设计与解析
1、HTTP
2、自定义协议
自定义协议要素
@Sharable
一、粘包 & 半包
1、现象分析
因为tcp是用二进制流进行传输的,所以用tcp来传输就会有这种现象。
tcp是可靠的传输,每次发送必须要接收方收到ack才能确定信息被收到不然要重新发送,但是每次都要接收到才能发送下一条就太慢了,为了解决这个问题,tcp有了滑动窗口这个概念。
引入了滑动窗口,窗口大小即决定了无需等待应答而可以继续发送的数据最大值
比如这里滑动窗口的大小为4,也就是说可以有4个消息发送可以不用收到ack,当第五个要发送的时候,如果前面都没有响应就要等待了。响应了滑动窗口就会向下移动。接收方也会维护一个窗口,落到窗口内的数据才会被接收
粘包
现象:发送的是abc def,接收到的是abcdef
原因:
- 应用层:接收方ByteBuf设置太大,Netty默认1024
- 滑动窗口:假设发送方256bytes表示一个完整报文,但由于接收放处理不及时且窗口大小足够大,这256bytes字节就会缓冲在接收方的滑动窗口中,当滑动窗口缓冲了多个报文就会粘包
- Nagle算法:会造成粘包
半包
现象:发送abcdef,接收到abc def
原因:
- 应用层:接受放ByteBuf小于实践发送数据量
- 滑动窗口:假设接收方的窗口剩128bytes,发送方的报文大小是256bytes,这时放不下了,只能先发前128bytes,等待ack才能发送剩余部分,就造成了半包
- MSS限制:当发送数据超过MSS限制后,会将数据切分后发送,就会造成半包
本质是因为TCP的流式协议,消息无边界
二、解决方案
1、短连接
我们每次建立连接发送完一个消息之后,我们就跟服务器断开发送-1表示结束连接,那么服务器知道断开了就知道这个是一个消息了,变相解决了粘包问题。
但是短连接不能解决半包问题,当ByteBuf过小消息过大,还是会出现分开的半包问题
2、定长解码器
Netty提供了FixedLengthFrameDecoder,固定长度的解码器,用每个消息长度是固定的来解决这种问题。如果固定的消息长度是10,那么每个消息都会是长度为10的如果一个消息的长度没有到10会填充到10发送,但是这种如果消息只有2个字节,但是却要填充8字节再发送就造成浪费
3、分隔符
Netty提供了两个用分隔符来解决的实现。
LineBasedFrameDecoder:换行符来做分隔符,注意用这个的时候要传入一个最大长度限制,如果超过这个长度依然没有换行符,就会抛出异常。
DelimiterBasedFrameDecoder:自定义分隔符,跟上面的差不多,构造方法多传入一个ByteBuf类型的分隔符来做分割。
在使用的时候服务端很简单就直接价格解码器就行,客户端发送的时候每个消息后加上换行符。但是用这种方式会带来效率的问题,因为他需要一个个遍历来寻找有没有换行符,而且可能有别人通过转移字符等方式而已拆包。
4、长度字段解码器
Netty提供了LengthFieldBasedFrameDecoder来解决。
他里面有几个参数:
- 第一个就是限制这个消息内容的长度,如果超过就抛出异常的
- 第二个参数是长度字段偏移量,告诉长度字段多少位,然后就可以直接读取消息有多长
- 第三个参数就是长度字段为基准,还有几个字节是内容,因为他可能还有其他字段,头部什么的还没那么快到内容
- 第四个参数是从头部剥离几个字节:就是会把从头开始去掉几个字节,一般去掉这些内容长度什么这些,也可以不剥离
三、协议设计与解析
1、HTTP
Netty提供了编解码处理器HttpServerCodec
他继承了CombinedChannelDuplexHandler<HttpRequestDecoder,HttpResponseEncoder>所以可以实现请求的解码和响应的编码
我们的http请求到达netty的解码器默认解析为两份:
- defaultHttpRequest包含请求头请求行
- LastHttpContent是请求体
如图,我们要先添加个serverCodec编解码器,下面在搞个处理器泛型是HttpRequest用来过滤实现只关注http请求,然后里面内容是接受到之后限制,设置头部长度为length,然后写出去,ctx的write后往前找出站处理器执行,那个serviceCodec又是入站又是出站所以会执行编码响应回去
2、自定义协议
自定义协议要素
- 魔术:用来第一时间判定是否是无效数据包
- 版本号:可以支持协议的升级
- 序列化算法:消息正文到底采用哪种序列化反序列化,可以由此扩展,json、protobuf、hessian、jdk
- 指令类型,是登录、注册、单聊、群聊跟业务相关
- 正文长度
- 消息正文
这里我们要准备用自定义协议做聊天室的应用,我们定义了很多消息的类型,我们现在要为所有这些定义的消息类型做自定义的编解码操作。MessageCodec就是我们自定义的编解码器,我们要继承ByteTOMessge方法,泛型传要转换的类型,这里我们直接传各个消息类的抽象类。encode方法是用来编码的,代码如下:
解码的代码:
解码我们就一个个去读取出来就行,几个字节是什么就根据编码规则一个个去读
还要注意:我们发送消息的时候要提前加上帧解码器,为了防止半包问题,有可能他超过了那解码的时候就会出问题,如果数据分开传输了到帧解码器,他发现数据不完整还没到,他会等数据完整了才放给下面处理区继续执行,不然直接分包了。
@Sharable
我们发现我们每次用handler都是new的新创建一个,我们能不能用一个handler让多个线程共享使用呢,是可以的但不是所有handler都可以,比如帧解码器这种他就是多个线程可能会相互影响,因为他要记录状态,但是logginghandler又可以,因为他只是负责打印,所以我们要具体handler来分析。netty提供了注解@Sharable来告诉我们,我们点进handler就可以看到如果加了这个注解,说明他是可以被多个线程共享的,不会有线程安全问题,我们自定义的也是不会出现安全问题的,所以也可以提取出来共享。