【Netty】netty的编解码技术
一、编解码技术
1、java序列化
1、java序列化的实现
java提供了对象输入(ObjectInputStream),输出流(ObjectOutputStream),可以将java对象作为可存储的字节数组写入文件,也可以传输到网络上。
2、java序列化的目的
- 网络传输
- 对象持久化
2、java序列化的缺点
1、java了序列化从JDK1.1版本就已经提供,它不需要提供额外的类库,只需要实现java.io.Serializable,并生成序列ID即可,因此,它在诞生之初就得到了广发的应用
2、远程服务调用RPC时,很少直接使用java序列化进行消息的编解码和传输。
- 无法跨语言(java序列化属于java语言内部的私有协议,其他语言不支持,对于java序列化后的字节数组,别的语言无法进行反序列化)
- 序列化后的码流太大
- 序列化的性能太低
3、如何评判一个编解码框架的优劣,需要考虑的因素
- 是否支持跨语言,支持的语言种类是否丰富
- 编码后的码流大小
- 编解码的性能
- 类库是否小巧,api使用是否方便
- 使用者需要手工开发的工作量和难度
二、业界主流的编解码框架
名称 | 特点 | 备注 |
Google的ProtoBuf |
将数据结构以.proto文件进行描述,通过代码生成工具可以生成对应数据结构的POJO对象和Protobuf相关的方法和属性 1、结构化数据存储格式(XML,JSON) 2、高效的编解码性能 3、语言无关,平台无关,扩展性好 4、官方支持java,c++,python三种语言 |
xml可读性和可扩展性好,但为了可读性而牺牲的空间开销非常大 protobuf 利用二进制编码,在空间额性能上具有更大优势 |
facebook的thrift |
1、通用的二进制编解码 2、压缩二进制编解码 3、优化的可选字段压缩编解码 |
|
JBoss Marshalling |
相比传统的java序列化机制,它的优点如下 1、可插拔的类解析器,提供更加便捷的类加载定制策略,通过一个接口即可实现定制 2、可插拔的对象替换技术,不需要通过集成方式 3、可插拔的预定义类缓存表,可以减小序列化的字节数组长度,提升常用类型的对象序列化性能 4、无需实现java.io.Serializable接口,即可实现java序列化 5、通过缓存技术提升对象的序列化性能 |
jboss内部使用,应用范围有限 |
MessagePack |
高效的二进制序列化框架 1、粘包/半包支持 2、编解码高效,性能高 3、序列化之后的码流小 4、支持跨语言(java,python,ruby,haskell,c#,OCaml,Lua,Go,C,C++) |
三、MessagePack实现编解码技术
1、引入MessagePack的jar
<!--messagePack--> <dependency> <groupId>org.msgpack</groupId> <artifactId>msgpack</artifactId> <version>0.6.12</version> </dependency> <dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.22.0-GA</version> </dependency>
2、声明消息体(消息体上要加注解@Message)
package com.spring.test.service.netty.codec.messagepack.niov1; import org.msgpack.annotation.Message; /** * */ @Message public class UserInfo { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "UserInfo{" + "name='" + name + '\'' + ", age=" + age + '}'; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
3、开始编写服务端客户端
MessagePack编码类
package com.spring.test.service.netty.codec.messagepack.niov1; import org.msgpack.MessagePack; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; /** * MessagePack的编码器 */ public class MsgPackEncoder extends MessageToByteEncoder<Object> { @Override protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception { MessagePack messagePack = new MessagePack(); byte[] raw = messagePack.write(msg); out.writeBytes(raw); } }
MessagePack解码类
package com.spring.test.service.netty.codec.messagepack.niov1; import org.msgpack.MessagePack; import java.util.List; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToMessageDecoder; /** * MessagePack的解码器 */ public class MsgPackDecoder extends MessageToMessageDecoder<ByteBuf> { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception { final byte[] array; final int length = msg.readableBytes(); array = new byte[length]; msg.getBytes(msg.readerIndex(), array, 0, length); MessagePack msgPack = new MessagePack(); out.add(msgPack.read(array)); } }
第一种:不考虑半包读写
1、服务端代码+handler
package com.spring.test.service.netty.codec.messagepack.niov1; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; /** * */ public class EchoServer { public void bind(int port) { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 1024) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { //编码器 ch.pipeline().addLast("msgpack decoder", new MsgPackDecoder()); //解码器 ch.pipeline().addLast("msgpack encoder", new MsgPackEncoder()); //用户自定义的消息处理器 ch.pipeline().addLast(new MySelfEchoServerHandler()); } }); //绑定服务端端口,同步等待成功 ChannelFuture f = serverBootstrap.bind(port).sync(); //等待服务端监听端口关闭 f.channel().closeFuture().sync(); } catch (Exception e) { e.printStackTrace(); } finally { //优雅退出,释放线程池资源 bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } public static void main(String[] args) { int port = 8080; new EchoServer().bind(port); } } package com.spring.test.service.netty.codec.messagepack.niov1; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; /** * @author <a href="mailto:shangxiaofei@meituan.com">尚晓飞</a> * @date 4:38 PM 2019/8/25 */ public class MySelfEchoServerHandler extends ChannelHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { System.out.println("this Server receive the msgpack message:"+msg); ctx.write(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(); } }
2、客户端+handler
package com.spring.test.service.netty.codec.messagepack.niov1; import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; /** */ public class EchoClient { private final String host; private final int port; private final int sendNumber; EchoClient(String host, int port, int sendNumber) { this.host = host; this.port = port; this.sendNumber = sendNumber; } public void connect() { //配置客户端NIO线程组 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 ch) throws Exception { //编码器 ch.pipeline().addLast("msgpack decoder", new MsgPackDecoder()); //解码器 ch.pipeline().addLast("msgpack encoder", new MsgPackEncoder()); ch.pipeline().addLast(new MySelfEchoClientHandler(sendNumber)); } }); //发起异步连接操作 ChannelFuture f = bootstrap.connect(host, port).sync(); //等待客户端链路关闭 f.channel().closeFuture().sync(); } catch (Exception e) { e.printStackTrace(); } finally { //优雅的退出,释放NIO线程组 group.shutdownGracefully(); } } public static void main(String[] args) { new EchoClient("127.0.0.1", 8080, 1000).connect(); } } package com.spring.test.service.netty.codec.messagepack.niov1; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; /** * */ public class MySelfEchoClientHandler extends ChannelHandlerAdapter { private final int sendNumber; MySelfEchoClientHandler(int sendNumber) { this.sendNumber = sendNumber; } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { UserInfo[] userInfos=userInfos(); for(UserInfo userInfo:userInfos){ ctx.write(userInfo); } ctx.flush(); System.out.println("=======client request finished========"); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { System.out.println("Client receive the msgpage message:"+msg); ctx.write(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(); } private UserInfo[] userInfos(){ UserInfo[] userInfos=new UserInfo[sendNumber]; UserInfo userInfo=null; for(int i=0;i<sendNumber;i++){ userInfo=new UserInfo(); userInfo.setAge(i); userInfo.setName("ABCDE==>"+i); userInfos[i]=userInfo; } return userInfos; } }
第二种:考虑半包读写
1、服务端+handler
package com.spring.test.service.netty.codec.messagepack.niov2; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import io.netty.handler.codec.LengthFieldPrepender; /** * */ public class EchoServer { public void bind(int port) { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 1024) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast("frameDecoder",new LengthFieldBasedFrameDecoder(65535,0,2,0,2)); //编码器 ch.pipeline().addLast("msgpack decoder", new MsgPackDecoder()); ch.pipeline().addLast("frameEncoder",new LengthFieldPrepender(2)); //解码器 ch.pipeline().addLast("msgpack encoder", new MsgPackEncoder()); //用户自定义的消息处理器 ch.pipeline().addLast(new MySelfEchoServerHandler()); } }); //绑定服务端端口,同步等待成功 ChannelFuture f = serverBootstrap.bind(port).sync(); //等待服务端监听端口关闭 f.channel().closeFuture().sync(); } catch (Exception e) { e.printStackTrace(); } finally { //优雅退出,释放线程池资源 bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } public static void main(String[] args) { int port = 8080; new EchoServer().bind(port); } } package com.spring.test.service.netty.codec.messagepack.niov2; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; /** * */ public class MySelfEchoServerHandler extends ChannelHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { System.out.println("this Server receive the msgpack message:"+msg); ctx.write(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(); } }
2、客户端+handler
package com.spring.test.service.netty.codec.messagepack.niov2; import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import io.netty.handler.codec.LengthFieldPrepender; /** * */ public class EchoClient { private final String host; private final int port; private final int sendNumber; EchoClient(String host, int port, int sendNumber) { this.host = host; this.port = port; this.sendNumber = sendNumber; } public void connect() { //配置客户端NIO线程组 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 ch) throws Exception { ch.pipeline().addLast("frameDecoder",new LengthFieldBasedFrameDecoder(65535,0,2,0,2)); //编码器 ch.pipeline().addLast("msgpack decoder", new MsgPackDecoder()); ch.pipeline().addLast("frameEncoder",new LengthFieldPrepender(2)); //解码器 ch.pipeline().addLast("msgpack encoder", new MsgPackEncoder()); ch.pipeline().addLast(new MySelfEchoClientHandler(sendNumber)); } }); //发起异步连接操作 ChannelFuture f = bootstrap.connect(host, port).sync(); //等待客户端链路关闭 f.channel().closeFuture().sync(); } catch (Exception e) { e.printStackTrace(); } finally { //优雅的退出,释放NIO线程组 group.shutdownGracefully(); } } public static void main(String[] args) { new EchoClient("127.0.0.1", 8080, 1000).connect(); } } package com.spring.test.service.netty.codec.messagepack.niov2; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; /** * */ public class MySelfEchoClientHandler extends ChannelHandlerAdapter { private final int sendNumber; MySelfEchoClientHandler(int sendNumber) { this.sendNumber = sendNumber; } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { UserInfo[] userInfos=userInfos(); for(UserInfo userInfo:userInfos){ ctx.write(userInfo); } ctx.flush(); System.out.println("=======client request finished========"); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { System.out.println("Client receive the msgpage message:"+msg); ctx.write(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(); } private UserInfo[] userInfos(){ UserInfo[] userInfos=new UserInfo[sendNumber]; UserInfo userInfo=null; for(int i=0;i<sendNumber;i++){ userInfo=new UserInfo(); userInfo.setAge(i); userInfo.setName("ABCDE==>"+i); userInfos[i]=userInfo; } return userInfos; } }