概述
1, 先复习一下粘包/拆包
1.1, 粘包/拆包的含义
TCP是个“流”协议, 并不了解上层业务数据的具体含义, 它会根据TCP缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题。
假设客户端分别发送了两个数据包D1和D2给服务端,可能存在以下4种情况:
(a)服务端分两次读取到了两个独立的数据包,分别是D1和D2,没有粘包和拆包;
(b)服务端一次接收到了两个数据包,D1和D2粘合在一起,被称为TCP粘包;
(c)服务端分两次读取到了两个数据包,第一次读取到了完整的D1包和D2包的部分内容,第二次读取到了D2包的剩余内容,这被称为TCP拆包;
(d)服务端分两次读取到了两个数据包,第一次读取到了D1包的部分内容D1_1,第二次读取到了D1包的剩余内容D1_2和D2包的整包。
1.2, 粘包/拆包的处理
粘包/拆包的解决方法都是在报文结构上做处理,一般有3种方式: 定长报文、报文分隔符 、报文长度域
在NIO下, 框架Netty对于以上3种方式分别有自己的实现: FixedLengthFrameDecoder、DelimiterBasedFrameDecoder 、LengthFieldBasedFrameDecoder
2, 基于长度域的报文在Netty下的解码
对于“长度域”的值, 虽然底层都是以字节的形式传输, 但是在上层数据类型上, 长度域有‘字符串’和‘数字’类型两种.
假设原报文内容是
x=1111,y=2222,z=3333
原报文20个字节, 在对其添加长度域时, Java开发者可能见过下面这种结构, 尤其是在金融/银行开发中
00000020x=1111,y=2222,z=3333
上面报文的长度域就是‘字符串’类型, 对应的整型值为原报文的字节长度, 不足8位左边补0.
对于‘字符串’类型的长度域, 发送方输出流的方式如下:
out.write("00000020".getBytes());//字符串00000020转化为字节
out.write("x=1111,y=2222,z=3333".getBytes());
//后面再说NIO下我们该如何去解码字符串长度域的报文.
接着说NIO下Netty自带的长度域解码器LengthFieldBasedFrameDecoder, 它支持的长度域就是‘数字’类型.
对于‘数字’类型的长度域, 如果约定长度为4, 则其报文结构大抵如下, 不太好刻画, 前面4位是数字20转化的字节.
[0,0,0,20]x=1111,y=2222,z=3333
发送方输出流的方式如下:
out.write(new byte[]{0,0,0,20});//数字20转换为4位字节数组
out.write("x=1111,y=2222,z=3333".getBytes());
然后Netty接收端直接使用LengthFieldBasedFrameDecoder就可以很方便解码
new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4)
如果客户端和服务端都是Netty开发, 大家默认的就是‘数字’类型的长度域, 发送端直接使用Netty自带的LengthFieldPrepender编码器就行了.
但是如果你作为数据接收方的NIO开发者, 而发送方是权威方, 它给的报文的长度域是‘字符串’类型时, 你该怎么处理?
这个时候, 我们可以基于Netty自定义一个解码器, 专门处理字符串类型的长度域, 实现代码如下:
package com.test;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.util.CharsetUtil;
import java.util.List;
/**
* 字符串类型的长度域报文解码器
*
* @Author:tt
* @Description:
* @CreateTime:2019/6/26 下午11:30
*/
public class StringLengthFieldDecoder extends ByteToMessageDecoder {
//长度域的字符串长度,比如:长度字段的长度为8,则报文有100个字节时,长度域值为:00000100
private int lengthFieldSize;
public StringLengthFieldDecoder(int lengthFieldSize) {
this.lengthFieldSize = lengthFieldSize;
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
Object decoded = this.decode(ctx, in);
if (decoded != null) {
out.add(decoded);
}
}
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) {
if (in.readableBytes() < lengthFieldSize) {
return null;
}
//长度域的字符串值
String lengthFieldValStr = in.readBytes(lengthFieldSize).toString(CharsetUtil.UTF_8);
//原报文长度
int frameLength = Integer.parseInt(lengthFieldValStr);
if (in.readableBytes() < frameLength) {
//这一步很重要,回退读索引
in.readerIndex(in.readerIndex() - lengthFieldSize);
return null;
}
return in.readBytes(frameLength);
}
}
转载于:https://my.oschina.net/wangxu3655/blog/3066594
最后
以上就是怕孤独海燕为你收集整理的NIO基于长度域的报文在Netty下的解码的全部内容,希望文章能够帮你解决NIO基于长度域的报文在Netty下的解码所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复