netty权威指南学习笔记六——编解码技术之MessagePack

  编解码技术主要应用在网络传输中,将对象比如BOJO进行编解码以利于网络中进行传输。平常我们也会将编解码说成是序列化/反序列化

  定义:当进行远程跨进程服务调用时,需要把被传输的java对象编码为字节数组或者ByteBuffer对象。而当远程服务读取到ByteBuffer对象或者字节数组时,需要将其解码为发送时的java对象。这被称为java对象编解码技术。比如java的序列化。

  但是,java的序列化有一定的弊端;

  •   java序列化是java私有的协议,其他语言不支持,故而不能实现跨语言;
  •   其次,序列化后的码流太大;
  •   再次,序列化性能太低,耗时长。

  因此,通常不会选择java序列化作为远程跨节点调用的编解码框架。

  当前业界主流的编解码框架有:1)MessagePack高效的二进制序列化框架;2)Google 的Protobuf;3)Facebook的Thrift;4)JBoss Marshalliing

下面运行MessagePack的编解码

  这个示例在权威指南上,作者并没有给出完整代码,本博主刚开始运行也没有运行出来,经过网络搜索,参考相关文章运行了出来,其中潜在存在着一些坑,运行中本博主也发现一些现象也总结出来。

  一、首先我们需要引入相关jar包

 1         <dependency>
 2             <groupId>org.msgpack</groupId>
 3             <artifactId>msgpack</artifactId>
 4             <version>0.6.11</version>
 5 
 6         </dependency>
 7         <!-- 创建项目时已经存在,这里贴出来,但本博主不重复放包
 8               <dependency>
 9                   <groupId>org.javassist</groupId>
10                   <artifactId>javassist</artifactId>
11                   <version>3.22.0-GA</version>
12               </dependency>
13       -->

二、放上编解码代码,

坑一、一定要在继承的类后面加上泛型,这样,方法中的参数才能跟着发生变化

如过没有添加<>,则解码实现接口方法时直接生成的方法的参数如下:

编码代码

 1 package com.messagePack;
 2 
 3 import io.netty.buffer.ByteBuf;
 4 import io.netty.channel.ChannelHandlerContext;
 5 import io.netty.handler.codec.MessageToByteEncoder;
 6 import org.msgpack.MessagePack;
 7 
 8 public class MsgPackEncoder extends MessageToByteEncoder<Object> {
 9     @Override
10     protected void encode(ChannelHandlerContext channelHandlerContext, Object o, ByteBuf byteBuf) throws Exception {
11         MessagePack msgPack = new MessagePack();
12 //      编码,然后转为ButyBuf传递
13         byte[] bytes = msgPack.write(o);
14         byteBuf.writeBytes(bytes);
15     }
16 }

解码代码

 1 package com.messagePack;
 2 
 3 import io.netty.buffer.ByteBuf;
 4 import io.netty.channel.ChannelHandlerContext;
 5 import io.netty.handler.codec.MessageToMessageDecoder;
 6 import org.msgpack.MessagePack;
 7 
 8 import java.util.List;
 9 
10 public class MsgPackDecoder extends MessageToMessageDecoder<ByteBuf> {
11     @Override
12     protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
13 //      获取要解码的byte数组
14         final byte[] bytes;
15         final int length = byteBuf.readableBytes();
16         bytes = new byte[length];
17         byteBuf.getBytes(byteBuf.readerIndex(),bytes,0,length);
18 //      调用MessagePack 的read方法将其反序列化为Object对象
19         MessagePack msgPack = new MessagePack();
20         list.add(msgPack.read(bytes));
21     }
22 }

如果实现的接口后面没有添加泛型<ByteBuf>,则解码实现接口方法时直接生成的方法的参数如下:

1  @Override
2     protected void decode(ChannelHandlerContext channelHandlerContext, Object o, List list) throws Exception {
3 
4     }

 不过经过实验,将接收到的Object进行转换后仍然可以,要记好netty接收和传递信息都是经过ByteBuf进行的

 1     @Override
 2     protected void decode(ChannelHandlerContext channelHandlerContext, Object o, List list) throws Exception {
 3         ByteBuf byteBuf = (ByteBuf) o;
 4 //      获取要解码的byte数组
 5         final byte[] bytes;
 6         final int length = byteBuf.readableBytes();
 7         bytes = new byte[length];
 8         byteBuf.getBytes(byteBuf.readerIndex(),bytes,0,length);
 9 //      调用MessagePack 的read方法将其反序列化为Object对象
10         MessagePack msgPack = new MessagePack();
11         list.add(msgPack.read(bytes));
12     }

三、服务端代码

 1 package com.messagePack;
 2 
 3 import io.netty.bootstrap.ServerBootstrap;
 4 import io.netty.channel.ChannelFuture;
 5 import io.netty.channel.ChannelInitializer;
 6 import io.netty.channel.ChannelOption;
 7 import io.netty.channel.nio.NioEventLoopGroup;
 8 import io.netty.channel.socket.SocketChannel;
 9 import io.netty.channel.socket.nio.NioServerSocketChannel;
10 import io.netty.handler.logging.LogLevel;
11 import io.netty.handler.logging.LoggingHandler;
12 
13 public class EchoServer {
14     public void bind(int port) throws InterruptedException {
15         NioEventLoopGroup bossGroup = new NioEventLoopGroup();
16         NioEventLoopGroup workGroup = new NioEventLoopGroup();
17         try {
18             ServerBootstrap b = new ServerBootstrap();
19             b.group(bossGroup,workGroup)
20                     .channel(NioServerSocketChannel.class)
21                     .option(ChannelOption.SO_BACKLOG,1024)
22                     .childHandler(new LoggingHandler(LogLevel.INFO))
23                     .childHandler(new ChannelInitializer<SocketChannel>() {
24                         @Override
25                         protected void initChannel(SocketChannel socketChannel) throws Exception {
26                             socketChannel.pipeline()
27                                     .addLast("decoder",new MsgPackDecoder())
28                                     .addLast("encoder",new MsgPackEncoder())
29                                     .addLast(new EchoServerHandler());
30                         }
31                     });
32 //          绑定端口,同步等待成功
33             ChannelFuture f = b.bind(port).sync();
34 //          等待服务端监听端口关闭
35             f.channel().closeFuture().sync();
36         } finally {
37             bossGroup.shutdownGracefully();
38             workGroup.shutdownGracefully();
39         }
40     }
41     public static void main(String[] args) throws InterruptedException {
42         int port = 8080;
43         if(args.length>0&&args!=null){
44             port = Integer.parseInt(args[0]);
45         }
46         new EchoServer().bind(port);
47 
48     }
49 }

IO处理

 1 package com.messagePack;
 2 
 3 import io.netty.buffer.ByteBuf;
 4 import io.netty.buffer.Unpooled;
 5 import io.netty.channel.ChannelHandlerContext;
 6 import io.netty.channel.ChannelInboundHandlerAdapter;
 7 
 8 import java.util.List;
 9 
10 public class EchoServerHandler extends ChannelInboundHandlerAdapter {
11     int count;
12     @Override
13     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
14         System.out.println("server receive the msgpack message : "+msg+"");
15         // 原路返回给客户端
16         ctx.writeAndFlush(msg);
17 /*        在EchoClientHandler中向服务端发送一个pojo对象,经过MessagePack编解码后,
18         在EchoServerHandler中的channelRead方法中打印的msg为pojo对象的toString方法内容,
19         不可以直接将msg转换为User,如果采用如下代码运行不成功*/
20 /*        List<User> users = (List<User>) msg;
21         System.out.println("到这里面来了,users是否为空:");
22         System.out.println(users!=null);
23         for(User u : users){
24             System.out.println("This is"+ ++count +" times server receive client request."+u);
25             ctx.write(u);
26         }
27 
28         ctx.flush();*/
29     }
30 
31     @Override
32     public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
33         ctx.flush();
34     }
35 
36     @Override
37     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
38         ctx.close();
39     }
40 }

坑二、这里的坑比较大,在上述代码注释中已经说明了,下面运行结果是注释掉以上14~16行代码,放开20~28行代码时候运行结果,结果表明收到的消息转化为User数组时候,是空的,但是后台并没有报错,不知道为什么在我的IDEA上运行不下去但是不报错

  补充解释,这里是因为采用messagepack解码后,得到的是一个Object list列表,所以不能转化为pojo,应该用List<Object> 来接收解码后的msg,同时这个list中的object也不能转化为pojo,它或许是pojo中的具体属性

四、客户端代码

 1 package com.messagePack;
 2 
 3 import io.netty.bootstrap.Bootstrap;
 4 import io.netty.buffer.ByteBuf;
 5 import io.netty.buffer.Unpooled;
 6 import io.netty.channel.ChannelFuture;
 7 import io.netty.channel.ChannelInitializer;
 8 import io.netty.channel.ChannelOption;
 9 import io.netty.channel.nio.NioEventLoopGroup;
10 import io.netty.channel.socket.SocketChannel;
11 import io.netty.channel.socket.nio.NioSocketChannel;
12 import io.netty.handler.codec.DelimiterBasedFrameDecoder;
13 import io.netty.handler.codec.string.StringDecoder;
14 
15 public class EchoClient {
16     public void connection(int port,String host) throws InterruptedException {
17         NioEventLoopGroup workGroup = new NioEventLoopGroup();
18         try {
19             Bootstrap b = new Bootstrap();
20             b.group(workGroup)
21                     .channel(NioSocketChannel.class)
22                     .option(ChannelOption.TCP_NODELAY,true)
23                     .option(ChannelOption.CONNECT_TIMEOUT_MILLIS,3000)
24                     .handler(new ChannelInitializer<SocketChannel>() {
25                         @Override
26                         protected void initChannel(SocketChannel socketChannel) throws Exception {
27                             socketChannel.pipeline().addLast("msgpack decoder",new MsgPackDecoder())
28                                     .addLast("msgpack encoder",new MsgPackEncoder())
29                                     .addLast(new EchoClientHandler());
30 //
31                         }
32                     });
33 //            发起异步连接操作
34             ChannelFuture f = b.connect(host,port).sync();
35 //                          等待客户端链路关闭
36             f.channel().closeFuture().sync();
37         } finally {
38             workGroup.shutdownGracefully();
39         }
40     }
41     public static void main(String[] args) throws InterruptedException {
42         int port = 8080;
43         if(args.length>0&&args!=null){
44             System.out.println(args[0]);
45             port = Integer.parseInt(args[0]);
46         }
47         new EchoClient().connection(port,"127.0.0.1");
48     }
49 }

IO处理类

 1 package com.messagePack;
 2 
 3 import io.netty.channel.ChannelHandlerContext;
 4 import io.netty.channel.ChannelInboundHandlerAdapter;
 5 
 6 public class EchoClientHandler extends ChannelInboundHandlerAdapter {
 7     private int count;
 8 
 9     @Override
10     public void channelActive(ChannelHandlerContext ctx) throws Exception {
11        /* User user = getUser();
12         ctx.writeAndFlush(user);*/
13         User[] users = getUsers();
14         for(User u : users){
15             ctx.write(u);
16         }
17         ctx.flush();
18     }
19 
20     @Override
21     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
22         System.out.println("this is client receive msg【  "+ ++count +"  】times:【"+msg+"】");
23        if(count<5){ //控制运行次数,因为不加这个控制直接调用下面代码的话,客户端和服务端会形成闭环循环,一直运行
24         ctx.write(msg);
25         }
26     }
27 
28     @Override
29     public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
30         ctx.flush();
31     }
32 
33     @Override
34     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
35         ctx.close();
36     }
37     private User[] getUsers(){
38         User[] users = new User[5];
39         for(int i=0;i<5;i++){
40             User user = new User();
41             user.setId(String.valueOf(i));
42             user.setAge(18+i);
43             user.setName("张元"+i);
44             user.setSex("男"+String.valueOf(i*2));
45             users[i]=user;
46         }
47         return users;
48     }
49 
50     private User getUser(){
51             User user = new User();
52             user.setId("11");
53             user.setAge(18);
54             user.setName("张元");
55             user.setSex("男");
56         return user;
57     }
58 }

五、User代码

坑三、要传输的javabean一定要加上注解@message

 1 package com.messagePack;
 2 
 3 import org.msgpack.annotation.Message;
 4 
 5 @Message
 6 public class User {
 7     private String name;
 8     private int age;
 9     private String id;
10     private String sex;
11 
12     public int getAge() {
13         return age;
14     }
15 
16     public void setAge(int age) {
17         this.age = age;
18     }
19 
20     public String getId() {
21         return id;
22     }
23 
24     public void setId(String id) {
25         this.id = id;
26     }
27 
28     public String getSex() {
29         return sex;
30     }
31 
32     public void setSex(String sex) {
33         this.sex = sex;
34     }
35 
36     public String getName() {
37         return name;
38     }
39 
40     public void setName(String name) {
41         this.name = name;
42     }
43 
44     @Override
45     public String toString() {
46         return "User{" +
47                 "name='" + name + '\'' +
48                 ", age=" + age +
49                 ", id='" + id + '\'' +
50                 ", sex='" + sex + '\'' +
51                 '}';
52     }
53 }

六、运行结果

客户端

 服务端

 七、上面运行的结果发现打印的数据都完全一样,这是因为没有考虑粘包/半包的处理,还不能正常工作,下面我们利用Netty的LengthFieldPrepender和LengthFieldBasedFrameDecoder,来解决上述问题,这里只需要对客户端和服务端添加相关的处理类就可以了,改动代码如下:

客户端

 1 .handler(new ChannelInitializer<SocketChannel>() {
 2                         @Override
 3                         protected void initChannel(SocketChannel socketChannel) throws Exception {
 4                             socketChannel.pipeline()
 5                                     .addLast("frameDecoder",new LengthFieldBasedFrameDecoder(65535,
 6                                             0,4,0,4))
 7                                     .addLast("msgpack decoder",new MsgPackDecoder())
 8                                     .addLast("frameEncoder",new LengthFieldPrepender(4))
 9                                     .addLast("msgpack encoder",new MsgPackEncoder())
10                                     .addLast(new EchoClientHandler());
11 //
12                         }
13                     });

服务端

 1  .childHandler(new ChannelInitializer<SocketChannel>() {
 2                         @Override
 3                         protected void initChannel(SocketChannel socketChannel) throws Exception {
 4                             socketChannel.pipeline()
 5                                     .addLast("framDecoder",new LengthFieldBasedFrameDecoder(65535,
 6                                             0,4,0,4))
 7                                     .addLast("decoder",new MsgPackDecoder())
 8                                     .addLast("frameEncoder",new LengthFieldPrepender(4))
 9                                     .addLast("encoder",new MsgPackEncoder())
10                                     .addLast(new EchoServerHandler());
11                         }
12                     });

其实两者改动的地方完全一样,下面看一下运行效果

客户端

服务端

 

这次运行结果显示的是0~5的数据,但是遗憾的是不知道为什么有重复了一下,而且之重复了部分的运行,暂时不理会。以后应用多了可能就明白了。或者有路过的朋友知道的留个言,谢谢!

  

posted @ 2018-07-22 11:53  醉逍遥_001  阅读(1058)  评论(0编辑  收藏  举报