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

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

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

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

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

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

下面运行MessagePack的编解码

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

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

         <dependency>
<groupId>org.msgpack</groupId>
<artifactId>msgpack</artifactId>
<version>0.6.11</version> </dependency>
<!-- 创建项目时已经存在,这里贴出来,但本博主不重复放包
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.22.0-GA</version>
</dependency>
-->

二、放上编解码代码,

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

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

编码代码

 package com.messagePack;

 import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import org.msgpack.MessagePack; public class MsgPackEncoder extends MessageToByteEncoder<Object> {
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, Object o, ByteBuf byteBuf) throws Exception {
MessagePack msgPack = new MessagePack();
// 编码,然后转为ButyBuf传递
byte[] bytes = msgPack.write(o);
byteBuf.writeBytes(bytes);
}
}

解码代码

 package com.messagePack;

 import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder;
import org.msgpack.MessagePack; import java.util.List; public class MsgPackDecoder extends MessageToMessageDecoder<ByteBuf> {
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
// 获取要解码的byte数组
final byte[] bytes;
final int length = byteBuf.readableBytes();
bytes = new byte[length];
byteBuf.getBytes(byteBuf.readerIndex(),bytes,0,length);
// 调用MessagePack 的read方法将其反序列化为Object对象
MessagePack msgPack = new MessagePack();
list.add(msgPack.read(bytes));
}
}

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

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

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

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

三、服务端代码

 package com.messagePack;

 import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler; public class EchoServer {
public void bind(int port) throws InterruptedException {
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup,workGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG,1024)
.childHandler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline()
.addLast("decoder",new MsgPackDecoder())
.addLast("encoder",new MsgPackEncoder())
.addLast(new EchoServerHandler());
}
});
// 绑定端口,同步等待成功
ChannelFuture f = b.bind(port).sync();
// 等待服务端监听端口关闭
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws InterruptedException {
int port = 8080;
if(args.length>0&&args!=null){
port = Integer.parseInt(args[0]);
}
new EchoServer().bind(port); }
}

IO处理

 package com.messagePack;

 import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter; import java.util.List; public class EchoServerHandler extends ChannelInboundHandlerAdapter {
int count;
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("server receive the msgpack message : "+msg+"");
// 原路返回给客户端
ctx.writeAndFlush(msg);
/* 在EchoClientHandler中向服务端发送一个pojo对象,经过MessagePack编解码后,
在EchoServerHandler中的channelRead方法中打印的msg为pojo对象的toString方法内容,
不可以直接将msg转换为User,如果采用如下代码运行不成功*/
/* List<User> users = (List<User>) msg;
System.out.println("到这里面来了,users是否为空:");
System.out.println(users!=null);
for(User u : users){
System.out.println("This is"+ ++count +" times server receive client request."+u);
ctx.write(u);
} ctx.flush();*/
} @Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}

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

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

四、客户端代码

 package com.messagePack;

 import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder; public class EchoClient {
public void connection(int port,String host) throws InterruptedException {
NioEventLoopGroup workGroup = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(workGroup)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY,true)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS,3000)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast("msgpack decoder",new MsgPackDecoder())
.addLast("msgpack encoder",new MsgPackEncoder())
.addLast(new EchoClientHandler());
//
}
});
// 发起异步连接操作
ChannelFuture f = b.connect(host,port).sync();
// 等待客户端链路关闭
f.channel().closeFuture().sync();
} finally {
workGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws InterruptedException {
int port = 8080;
if(args.length>0&&args!=null){
System.out.println(args[0]);
port = Integer.parseInt(args[0]);
}
new EchoClient().connection(port,"127.0.0.1");
}
}

IO处理类

 package com.messagePack;

 import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter; public class EchoClientHandler extends ChannelInboundHandlerAdapter {
private int count; @Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
/* User user = getUser();
ctx.writeAndFlush(user);*/
User[] users = getUsers();
for(User u : users){
ctx.write(u);
}
ctx.flush();
} @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("this is client receive msg【 "+ ++count +" 】times:【"+msg+"】");
if(count<5){ //控制运行次数,因为不加这个控制直接调用下面代码的话,客户端和服务端会形成闭环循环,一直运行
ctx.write(msg);
}
} @Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
private User[] getUsers(){
User[] users = new User[5];
for(int i=0;i<5;i++){
User user = new User();
user.setId(String.valueOf(i));
user.setAge(18+i);
user.setName("张元"+i);
user.setSex("男"+String.valueOf(i*2));
users[i]=user;
}
return users;
} private User getUser(){
User user = new User();
user.setId("11");
user.setAge(18);
user.setName("张元");
user.setSex("男");
return user;
}
}

五、User代码

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

 package com.messagePack;

 import org.msgpack.annotation.Message;

 @Message
public class User {
private String name;
private int age;
private String id;
private String sex; public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} public String getId() {
return id;
} public void setId(String id) {
this.id = id;
} public String getSex() {
return sex;
} public void setSex(String sex) {
this.sex = sex;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} @Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", id='" + id + '\'' +
", sex='" + sex + '\'' +
'}';
}
}

六、运行结果

客户端

服务端

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

客户端

 .handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline()
.addLast("frameDecoder",new LengthFieldBasedFrameDecoder(65535,
0,4,0,4))
.addLast("msgpack decoder",new MsgPackDecoder())
.addLast("frameEncoder",new LengthFieldPrepender(4))
.addLast("msgpack encoder",new MsgPackEncoder())
.addLast(new EchoClientHandler());
//
}
});

服务端

  .childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline()
.addLast("framDecoder",new LengthFieldBasedFrameDecoder(65535,
0,4,0,4))
.addLast("decoder",new MsgPackDecoder())
.addLast("frameEncoder",new LengthFieldPrepender(4))
.addLast("encoder",new MsgPackEncoder())
.addLast(new EchoServerHandler());
}
});

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

客户端

服务端

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

  

netty权威指南学习笔记六——编解码技术之MessagePack的更多相关文章

  1. netty权威指南学习笔记八——编解码技术之JBoss Marshalling

    JBoss Marshalling 是一个java序列化包,对JDK默认的序列化框架进行了优化,但又保持跟java.io.Serializable接口的兼容,同时增加了一些可调参数和附加特性,这些参数 ...

  2. netty权威指南学习笔记七——编解码技术之GoogleProtobuf

    首先我们来看一下protobuf的优点: 谷歌长期使用成熟度高: 跨语言支持多种语言如:C++,java,Python: 编码后消息更小,更利于存储传输: 编解码性能高: 支持不同协议版本的兼容性: ...

  3. netty权威指南学习笔记二——netty入门应用

    经过了前面的NIO基础知识准备,我们已经对NIO有了较大了解,现在就进入netty的实际应用中来看看吧.重点体会整个过程. 按照权威指南写程序的过程中,发现一些问题:当我们在定义handler继承Ch ...

  4. netty权威指南学习笔记五——分隔符和定长解码器的应用

    TCP以流的方式进行数据传输,上层应用协议为了对消息进行区分,通常采用以下4中方式: 消息长度固定,累计读取到长度综合为定长LEN的报文后,就认为读取到了一个完整的消息,将计数器置位,重新开始读取下一 ...

  5. netty权威指南学习笔记四——TCP粘包/拆包之粘包问题解决

    发生了粘包,我们需要将其清晰的进行拆包处理,这里采用LineBasedFrameDecoder来解决 LineBasedFrameDecoder的工作原理是它依次遍历ByteBuf中的可读字节,判断看 ...

  6. netty权威指南学习笔记三——TCP粘包/拆包之粘包现象

    TCP是个流协议,流没有一定界限.TCP底层不了解业务,他会根据TCP缓冲区的实际情况进行包划分,在业务上,一个业务完整的包,可能会被TCP底层拆分为多个包进行发送,也可能多个小包组合成一个大的数据包 ...

  7. netty权威指南学习笔记一——NIO入门(3)NIO

    经过前面的铺垫,在这一节我们进入NIO编程,NIO弥补了原来同步阻塞IO的不足,他提供了高速的.面向块的I/O,NIO中加入的Buffer缓冲区,体现了与原I/O的一个重要区别.在面向流的I/O中,可 ...

  8. netty权威指南学习笔记一——NIO入门(1)BIO

    公司的一些项目采用了netty框架,为了加速适应公司开发,本博主认真学习netty框架,前一段时间主要看了看书,发现编程这东西,不上手还是觉得差点什么,于是为了加深理解,深入学习,本博主还是决定多动手 ...

  9. IDA Pro 权威指南学习笔记(六) - 次要的 IDA 显示窗口

    十六进制窗口 IDA 十六进制窗口可以配置为显示各种格式,并可作为十六进制编辑器使用 默认情况下,十六进制窗口显示程序内容和列表的标准十六进制代码,每行显示 16 个字节,以及其对应的 ASCII 字 ...

随机推荐

  1. 树莓派4B踩坑指南 - (1)系统简介及特性

    系统简介及特性 19年双十一入坑树莓派4B,发现不是一般的坑,对于新出来的4B,从外包装壳,到接口,到内核,很多老的资料已经不再适用,又没有什么特别大的论坛可以讨论,只能自己一点点的摸索. 所以将遇到 ...

  2. 安装pymongo

    cd /usr/local/src wget --no-check-certificate https://pypi.python.org/packages/source/p/pymongo/pymo ...

  3. Node.js 阻塞 回调函数

    回调例程 N所有API都支持回调函数,可以处理大量并发请求.回调函数一般作为最后一个参数出现: function foo1(name, age, callback){ } function foo2( ...

  4. Scrapy 中的模拟登陆

    目前,大部分网站都具有用户登陆功能,其中某些网站只有在用户登陆后才能获得有价值的信息,在爬取这类网站时,Scrapy 爬虫程序先模拟登陆,再爬取内容 1.登陆实质 其核心是想服务器发送含有登陆表单数据 ...

  5. C# Show()与ShowDialog()的区别-----转载

    A.WinForm中窗体显示  显示窗体可以有以下2种方法:  Form.ShowDialog方法 (窗体显示为模式窗体)  Form.Show方法 (窗体显示为无模式窗体) 两者具体区别如下:  1 ...

  6. boost::property_tree 读取ini配置

    应用场景: 在后端服务器项目开发中,需要初始化一个Socket服务器,需要IP地址与对应端口号等参数:另外还可能因为对接数据库,就还需要数据库的相关配置参数,如我使用的是MySql数据库,就需要数据库 ...

  7. 如何借助 Python 俘获女孩子芳心?

    责编 | 刘静 天气降温,感情却升温了? 上午刚到公司,就收到小Q发来的灵魂拷问: ​ ​ “Q仔!要不然下午请个假!我带你去精神科看看!?”我实在忍不了,脱口而出. 话音未落,前排的运营小花回头看向 ...

  8. SSH框架系列:Spring读取配置文件以及获取Spring注入的Bean

    分类: [java]2013-12-09 16:29 1020人阅读 评论(0) 收藏 举报 1.简介 在SSH框架下,假设我们将配置文件放在项目的src/datasource.properties路 ...

  9. Dom4j 使用简介(全而好的文章)

    版权声明:本文由冰云完成,首发于CSDN,未经许可,不得使用于任何商业用途.文中代码部分引用自DOM4J文档.欢迎转载,但请保持文章及版权声明完整.如需联络请发邮件:icecloud(AT)sina. ...

  10. Springboot配置文件内容加密

      使用的是jasypt-spring-boot-starter,具体介绍可以参考 https://gitee.com/yangziyi2017/Jasypt-Spring-Boot   引入依赖 & ...