netty编码解码及粘包拆包处理
编码解码器
当你通过Netty发送或者接受一个消息的时候,就将会发生一次数据转换。入站消息会被解码:从字节转换为另一种格式(比如java对象);如果是出站消息,它会被编码成字节。
Netty提供了一系列实用的编码解码器,他们都实现了ChannelInboundHadnler或者ChannelOutboundHandler接口。在这些类中,channelRead方法已经被重写了。以入站为例,对于每个从入站Channel读取的消息,这个方法会被调用。随后,它将调用由已知解码器所提供的decode()方法进行解码,并将已经解码的字节转发给ChannelPipeline中的下一个ChannelInboundHandler。
如果要实现高效的编解码可以用protobuf,但是protobuf需要维护大量的proto文件比较麻烦,现在一般可以使用protostuff。
protostuff是一个基于protobuf实现的序列化方法,它较于protobuf最明显的好处是,在几乎不损耗性能的情况下做到了不用我们写.proto文件来实现序列化。
粘包拆包
TCP是一个流协议,就是没有界限的一长串二进制数据。TCP作为传输层协议并不不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行数据包的划分,所以在业务上认为是一个完整的包,可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题。面向流的通信是无消息保护边界的。
<!-- 引入netty包-->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.35.Final</version>
</dependency>
<!--使用protostuff实现序列化-->
<dependency>
<groupId>com.dyuproject.protostuff</groupId>
<artifactId>protostuff-api</artifactId>
<version>1.0.10</version>
</dependency>
<dependency>
<groupId>com.dyuproject.protostuff</groupId>
<artifactId>protostuff-core</artifactId>
<version>1.0.10</version>
</dependency>
<dependency>
<groupId>com.dyuproject.protostuff</groupId>
<artifactId>protostuff-runtime</artifactId>
<version>1.0.10</version>
</dependency>
使用LengthFieldBasedFrameDecoder作为拆包器
具体可参考这篇文章 netty源码分析之LengthFieldBasedFrameDecoder
拆包器
public class PackageSplitUtil extends LengthFieldBasedFrameDecoder {
public PackageSplitUtil() {
super(Integer.MAX_VALUE, Constant.LENGTH_OFFECT, Constant.LENGTH_BYTES_COUNT, 0, Constant.LENGTH_BYTES_COUNT);
}
@Override
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception{
return super.decode(ctx, in);
}
}
编码器
public class MyNettyEncoder extends MessageToByteEncoder<NettyMessage> {
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, NettyMessage msg, ByteBuf out) throws Exception {
byte[] bytes = ProtostuffUtil.serializer(msg);
// 获取消息的长度
int length = bytes.length;
ByteBuf buf = Unpooled.buffer(Constant.HEAD_LEAGTH + length);
// 写入协议版本
buf.writeShort(Constant.PROTOCOL_VERSION);
// 写入消息长度
buf.writeShort(length);
// 写入消息体
buf.writeBytes(bytes);
out.writeBytes(buf);
}
}
解码器
public class MyNettyDecoder extends ByteToMessageDecoder {
private static Logger log = LoggerFactory.getLogger(MyNettyDecoder.class);
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
// 读取传送过来的消息的长度。
int length = in.readUnsignedShort();
// 长度如果小于0,非法数据,关闭连接
if (length < 0) {
ctx.close();
}
// 读到的消息体长度如果小于传送过来的消息长度
if (length > in.readableBytes()) {
log.info("读到的消息长度小于传送过来的消息长度!");
return;
}
ByteBuf frame = Unpooled.buffer(length);
in.readBytes(frame);
try {
byte[] inByte = frame.array();
// 字节转成对象
NettyMessage msg = ProtostuffUtil.deserializer(inByte, NettyMessage.class);
if (msg != null) {
// 获取业务消息头
out.add(msg);
}
} catch (Exception e) {
log.error("====" + ctx.channel().remoteAddress() + ",decode failed====");
}
}
}
服务端使用
public class NettyServer {
private static Logger log = LoggerFactory.getLogger(MyNettyDecoder.class);
private static Channel serverChannel;
private static final NettyServer nettyServer = new NettyServer();
public static NettyServer getInstance(){ return nettyServer; }
private NettyServer(){
start();
}
public static void closeServer(){
if (nettyServer != null){
serverChannel.close();
serverChannel = null;
}
}
private static void start(){
//创建线程处理连接请求
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
//创建线程和客户端进行交互
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//创建服务端的启动对象
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChannelInitializer<SocketChannel>() {//创建通道初始化对象,设置初始化参数
@Override
protected void initChannel(SocketChannel ch) throws Exception {
log.info("[ 客户端 ]" + ch.remoteAddress() + " 已连接");
//对workGroup的socketChannel设置处理器
ChannelPipeline pipeline = ch.pipeline();
//心跳机制
pipeline.addLast(new IdleStateHandler(Constant.HEART_TIMEOUT, 0, 0, TimeUnit.SECONDS));
pipeline.addLast(new PackageSplitUtil());
//解码器
pipeline.addLast(new MyNettyEncoder());
//编码器
pipeline.addLast(new MyNettyDecoder());
//自定义的处理器
pipeline.addLast(new NettyServerHandler());
}
});
ChannelFuture channelFuture = bootstrap.bind(Constant.NETTY_SERVER_PORT).sync();
serverChannel = channelFuture.channel();
log.info("====netty服务器已启动成功==== 端口:" + Constant.NETTY_SERVER_PORT);
//对通道关闭进行监听,closeFuture是异步操作,监听通道关闭
serverChannel.closeFuture().addListener((ChannelFutureListener) future -> {
log.info("====优雅的关闭了EventLoopGroup====");
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
});
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
}
}
}
Constant部分参数
/**
* 约定的包头长度
*/
public static int HEAD_LEAGTH = 2;
/**
* 约定的版本号
*/
public static int PROTOCOL_VERSION = 1;
/**
* 约定的长度偏移(2字节)
*/
public static int LENGTH_OFFECT = 2;
/**
* 约定的长度字节数(short类型)
*/
public static int LENGTH_BYTES_COUNT = 2;
/**
* 端口
*/
public static int NETTY_SERVER_PORT = 9000 ;