MessagePack编解码
MessagePack的特点:
- 编解码高校,性能高
- 序列化之后的码流小
- 支持跨语言
MessagePack解码器:继承MessageToMessageDecoder
public class MsgpackDecoder extends MessageToMessageDecoder<ByteBuf> {
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
final byte[] array;
final int length = byteBuf.readableBytes();
array = new byte[length];
byteBuf.getBytes(byteBuf.readerIndex(),array, 0, length);
MessagePack messagePack = new MessagePack();
list.add(messagePack.read(array));
}
}
MessagePack编码器:继承MessageToByteEncoder,负责将Object类型的Pojo编码成byte数组,写入ByteBuf中。
public class MsgpackEncoder extends MessageToByteEncoder<Object> {
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, Object o, ByteBuf byteBuf) throws Exception {
MessagePack msgPack = new MessagePack();
byte[] raw = msgPack.write(o);
byteBuf.writeBytes(raw);
}
}
Google Protobuf 编解码
Protobuf是一个灵活、高效、结构化的数据序列化框架,相比于传统序列化工具,更小,更快更简单。
下面通过一个简单的例子分析如何使用Protobuf对POJO对象进行编解码。
首先需要proto.exe工具根据.proto文件生成代码。
- SubscribeReq.proto
message SubscribeReq{
required int32 subReqId = 1;
required string userName = 2;
required string broductName = 3;
required string address = 4;
}
- SubscribeResp.proto
message SubscribeResp{
required int32 subReqID = 1;
required int32 respCode = 2;
required string desc = 3;
}
通过protoc.exe命令生成Java代码。
protoc.exe --java_out=.\src .<文件地址>
服务端开发
public class SubReqServer {
public void bind(int port) throws Exception {
EventLoopGroup bossGrop = new NioEventLoopGroup();
EventLoopGroup workerGrop = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGrop, workerGrop).channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new ProtobufVarint32FrameDecoder());
socketChannel.pipeline().addLast(new ProtobufDecoder(SubscribeReqProto.SubscribeReq.getDefaultInstance()));
socketChannel.pipeline().addLast(new ProtobufVarint32LengthFieldPrepender());
socketChannel.pipeline().addLast(new ProtobufEncoder());
socketChannel.pipeline().addLast(new SubReqServerHandler());
}
});
ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
channelFuture.channel().closeFuture().sync();
} finally {
bossGrop.shutdownGracefully();
workerGrop.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
new SubReqServer().bind(8080);
}
}
代码关注点在类ChannelInitializer中。添加ProtobufVarint32FrameDecoder主要用于半包的处理。ProtobufDecoder编码器继承自MessageToMessageDecoder,参数是com.google.protobuf.MessageLite,目的是告诉ProtobufDecoder需要解码的目标类。
SubReqServerHandler类:
public class SubReqServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
SubscribeReqProto.SubscribeReq req = (SubscribeReqProto.SubscribeReq) msg;
if (Objects.equals("Lilinfeng", req.getUserName())){
System.out.println("Service accept client subscribe req : [" + req.toString() + "]");
ctx.writeAndFlush(resp(req.getSubReqId()));
}
}
private SubscribeRespProto.SubscribeResp resp(int subReqId){
SubscribeRespProto.SubscribeResp.Builder builder = SubscribeRespProto.SubscribeResp.newBuilder();
builder.setSubReqID(subReqId);
builder.setRespCode(0);
builder.setDesc("Netty book order succeed , 3 days later , Sent to the");
return builder.build();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
cause.printStackTrace();
ctx.close();
}
}
由于ProtobufDecoder已经对消息进行了自动解码,因此受到的消息可以直接使用。由于使用了ProtobufEncoder,所以不需要对SubscribeRespProto.subscribeResp进行手工编码。
客户端的代码和服务器端的基本相同因此就不在添加注释了。
public class SubReqClient {
public void connent(int port, String host) throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new ProtobufVarint32FrameDecoder());
socketChannel.pipeline().addLast(new ProtobufDecoder(SubscribeRespProto.SubscribeResp.getDefaultInstance()));
socketChannel.pipeline().addLast(new ProtobufVarint32LengthFieldPrepender());
socketChannel.pipeline().addLast(new ProtobufEncoder());
socketChannel.pipeline().addLast(new SubReqClientHandler());
}
});
ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
channelFuture.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
new SubReqClient().connent(8080, "127.0.0.1");
}
}
SubReqClientHander类
public class SubReqClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
for (int i = 0; i < 10 ; i ++){
ctx.write(subReq(i));
}
ctx.flush();
}
private SubscribeReqProto.SubscribeReq subReq(int i) {
SubscribeReqProto.SubscribeReq.Builder builder = SubscribeReqProto.SubscribeReq.newBuilder();
builder.setSubReqId(i);
builder.setUserName("Lilinfeng");
builder.setBroductName("Netty Book For");
builder.setAddress("BeiJing Li");
return builder.build();
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("Receive server Response : [" + msg +"]");
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
ProtobufDecoder仅仅负责解码,不支持读半包,因此在ProtobufDecoder前面一定要添加处理半包的解码器,有三种可选方案:
- 使用ProtoBufVarint32FrameDecoder处理半包消息。
- 使用通用半包解码器LengthFieldBasedFrameDecoder。
- 继承ByteToMessageDecoder类,自己处理半包消息。