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
都不会被调用,也就是所有的编码器都不会被调用。