Netty——MessagePack编解码
MessagePack编解码#
这是一个二进制序列化框架,将用它来改造之前的TimeServer。
添加依赖#
<dependency>
<groupId>org.msgpack</groupId>
<artifactId>msgpack-core</artifactId>
<version>${msgpack.version}</version>
</dependency>
这里我用的0.9.1
版本。
编写MessagePackEncoder#
继承自MessageToByteEncoder
,是ChannelOutBoundHandler
的子类,主要用于对发送的数据进行处理
public class MessagePackEncoder extends MessageToByteEncoder<Object> {
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, Object o, ByteBuf byteBuf) throws Exception {
MessageBufferPacker packer = MessagePack.newDefaultBufferPacker();
packer.packString((String) o);
byteBuf.writeBytes(packer.toByteArray());
}
}
编写MessagePackDecoder#
继承自ByteToMessageDecoder
,是ChannelInboundHandler
的子类
public class MessagePackDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
int bufLen = byteBuf.readableBytes();
byte[] bytes = new byte[bufLen];
byteBuf.readBytes(bytes);
MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(bytes);
list.add(unpacker.unpackString());
}
}
原书中使用
getBytes
来读取,但是getBytes
是绝对读取,并不会移动ByteBuf的位置,所以netty会抛出一个异常,说我们的Decoder什么都没读,但是它返回了一个结果。可能是老版本的netty允许这个行为,但是新版本不允许了,这里改用readBytes
添加Encoder和Decoder到bootstrap#
服务器端
ServerBootstrap bootstrap = new ServerBootstrap()
.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast("LengthFieldBasedFrameDecoder", new LengthFieldBasedFrameDecoder(65535, 0, 2, 0, 2));
socketChannel.pipeline().addLast("MessagePackDecoder", new MessagePackDecoder());
socketChannel.pipeline().addLast("LengthFieldPrepender", new LengthFieldPrepender(2));
socketChannel.pipeline().addLast("MessagePackEncoder", new MessagePackEncoder());
socketChannel.pipeline().addLast("TimeServerHandler", new TimeServerHandler());
}
});
客户端
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group);
bootstrap.channel(NioSocketChannel.class);
bootstrap.option(ChannelOption.TCP_NODELAY, true);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast("LengthFieldBasedFrameDecoder", new LengthFieldBasedFrameDecoder(65535, 0, 2, 0, 2));
socketChannel.pipeline().addLast("MessagePackDecoder", new MessagePackDecoder());
socketChannel.pipeline().addLast("LengthFieldPrepender", new LengthFieldPrepender(2));
socketChannel.pipeline().addLast("MessagePackEncoder", new MessagePackEncoder());
socketChannel.pipeline().addLast("TimeClientHandler", new TimeClientHandler());
}
});
其中LengthFieldBasedFrameDecoder
和LengthFieldPrepender
的作用是在要发送的东西前面添加一个固定字节长度的整数,这里是2个字节,这个整数用来描述后面的内容的长度,这样就能解决粘包和拆包问题。
这里Handler有一个执行顺序的问题,我调了半天,可以去看下这个文章,说的很详细:一文搞懂Netty中Handler的执行顺序
我这篇文章里也简单总结一下。
首先按照我们上面的添加方式,服务器端和客户端的Handler链如下:
服务器:
LengthFieldBasedFrameDecoder -> MessagePackDecoder -> LengthFieldPrepender -> MessagePackEncoder -> TimeServerHandler
客户端:
LengthFieldBasedFrameDecoder -> MessagePackDecoder -> LengthFieldPrepender -> MessagePackEncoder -> TimeClientHandler
这里面,用来解码的Decoder
是InboundHandler
,用来编码的Encoder
(Prepender
也是)是OutboundHandler
。一个请求过来,先链的顺序查找并调用InboundHandler
,也就是:
服务器:
LengthFieldBasedFrameDecoder -> MessagePackDecoder -> TimeServerHandler
客户端:
LengthFieldBasedFrameDecoder -> MessagePackDecoder -> TimeClientHandler
也就是数据先经过固定长度头的哪个解码器解码,然后再用MessagePack解码器解码,然后再到我们的TimeClientHandler
。
关于如何调用OutboundHandler
,主要看TimeClientHandler
中如何向对方返回数据,如果对方使用ctx.writeAndFlush
,那么将从当前Handler
的位置反向向前查找并调用所有的OutboundHandler
,也就是:
TimeClientHandler -> MessagePackEncoder -> LengthFieldPrepender
TimeServerHandler -> MessagePackEncoder -> LengthFieldPrepender
如果对方使用ctx.channel().writeAndFlush
,那么将从当前调用链的尾部反向向前查找并调用所有的OutboundHandler
,在本例中是一样的。所以把我们用于处理业务的Handler放到最后能保证所有OutboundHandler
都会被调用。如果你修改bootstrap中Handler的顺序:
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group);
bootstrap.channel(NioSocketChannel.class);
bootstrap.option(ChannelOption.TCP_NODELAY, true);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast("LengthFieldBasedFrameDecoder", new LengthFieldBasedFrameDecoder(65535, 0, 2, 0, 2));
socketChannel.pipeline().addLast("MessagePackDecoder", new MessagePackDecoder());
// 业务Handler放到中间
socketChannel.pipeline().addLast("TimeClientHandler", new TimeClientHandler());
socketChannel.pipeline().addLast("LengthFieldPrepender", new LengthFieldPrepender(2));
socketChannel.pipeline().addLast("MessagePackEncoder", new MessagePackEncoder());
}
});
那么如果你使用ctx.writeAndFlush
,所有的OutboundHandler
都不会被调用,也就是所有的编码器都不会被调用。
作者:Yudoge
出处:https://www.cnblogs.com/lilpig/p/16054978.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
欢迎按协议规定转载,方便的话,发个站内信给我嗷~
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· winform 绘制太阳,地球,月球 运作规律
· 上周热点回顾(3.3-3.9)