8.1.2 Protobuf编解码开发
Protobuf的类库使用比较简单,下面我们就通过对SubscrjbeReqProto进行编解码来介绍Protobuf的使用。
8-1 Protobuf入门TestsubscrjbeReqProto
1 package lqy7_protobuf_140; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 import com.google.protobuf.InvalidProtocolBufferException; 7 8 /** 9 * @author Administrator 10 * @date 2014年2月23日 11 * @version 1.0 12 */ 13 public class TestSubscribeReqProto { 14 15 private static byte[] encode(SubscribeReqProto.SubscribeReq req) { 16 return req.toByteArray(); 17 } 18 private static SubscribeReqProto.SubscribeReq decode(byte[] body) 19 throws InvalidProtocolBufferException { 20 return SubscribeReqProto.SubscribeReq.parseFrom(body); 21 } 22 23 private static SubscribeReqProto.SubscribeReq createSubscribeReq() { 24 SubscribeReqProto.SubscribeReq.Builder builder = SubscribeReqProto.SubscribeReq 25 .newBuilder(); 26 builder.setSubReqID(1); 27 builder.setUserName("Lilinfeng"); 28 builder.setProductName("Netty Book"); 29 List<String> address = new ArrayList<>(); 30 address.add("NanJing YuHuaTai"); 31 address.add("BeiJing LiuLiChang"); 32 address.add("ShenZhen HongShuLin"); 33 builder.addAllAddress(address); 34 return builder.build(); 35 } 36 37 /** 38 * @param args 39 * @throws InvalidProtocolBufferException 40 */ 41 public static void main(String[] args) 42 throws InvalidProtocolBufferException { 43 SubscribeReqProto.SubscribeReq req = createSubscribeReq(); 44 System.out.println("Before encode : " + req.toString()); 45 System.out.println("encode : " + encode(req)); 46 SubscribeReqProto.SubscribeReq req2 = decode(encode(req)); 47 System.out.println("After decode : " + req.toString()); 48 System.out.println("Assert equal : --> " + req2.equals(req)); 49 50 } 51 52 }
首先我们看如何创建SubscribeReqProto.SubscribeReq实例,
第24行通过SubscribeReqProto.SubscribeReq的静态方法newBuilder创建SubscribeReqProto.SubscribeReq的Builder实例,通过Builder构建器对SubscribeReq的属性进行设置,对于集合类型,通过addAllXXX()方法可以将集合对象设置到对应的属性中。
编码时通过调用 SubscribeReqProto.SubscribeReq 实例的 toByteArray 即可将
SubscribeReq编码为byte数组,使用才非常方便。
解码时通过SubscribeReqProto.SubscribeReq的静态方法parseFrom将二进制byte数组解码为原始的对象。
由于Protobuf支持复杂POJO对象编解码,所以代码都是通过工具自动生成,相比于传统的POJO对象的赋值操作,其使用略微复杂一些,但是习惯之后也不会带来额外的工作量,主要差异还是编程习惯的不同。
Protobuf的编解码解口非常简单和实用,但是功能和性能却非常强大,这也是它流行的一个重要原因。
下个小节我们将执行TestSubscribeReqProto,看它的功能是否正常
8.1.3 运行Protobuf例程
我们运行上一小节编写的TestsubscribeReqProto程序,看经过编解码后的对象是否和编码之前的初始对象等价,代码执行结果如图
1 Before encode : subReqID: 1 2 userName: "Lilinfeng" 3 productName: "Netty Book" 4 address: "NanJing YuHuaTai" 5 address: "BeiJing LiuLiChang" 6 address: "ShenZhen HongShuLin" 7 8 encode : [B@3bc8d400 9 After decode : subReqID: 1 10 userName: "Lilinfeng" 11 productName: "Netty Book" 12 address: "NanJing YuHuaTai" 13 address: "BeiJing LiuLiChang" 14 address: "ShenZhen HongShuLin" 15 16 Assert equal : --> true
运行结果表明,经过Protobuf编解码后,生成的SubscribeReqProto.Subscrib巳Req与编码前原始的SubscribeReqProto.SubscribeReq等价。
至此,我们己经学会了如何搭建Protobuf的开发和运行环境,:Ff二初步掌握了Protobut..的编解码接口的使用方法,而且通过实际demo的开发和运行巩固了所学的知识。从下个小节开始,我们将学习使用Netty的Protobuf编解码框架。
8.2 Netty的Protobuf服务端开发
我们仍旧以第7章的例程作为demo进行学习,看看如何开发出一个Protobuf版本的图书订购程序。
8.2.1 Protobuf版本的图书订购服务端开发
对SubReqServer进行升级,代码如下。Protobuf版本图书订购代码SubReqServer
1 package lqy7_protobuf_140; 2 import io.netty.bootstrap.ServerBootstrap; 3 import io.netty.channel.ChannelFuture; 4 import io.netty.channel.ChannelInitializer; 5 import io.netty.channel.ChannelOption; 6 import io.netty.channel.EventLoopGroup; 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.codec.protobuf.ProtobufDecoder; 11 import io.netty.handler.codec.protobuf.ProtobufEncoder; 12 import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder; 13 import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender; 14 import io.netty.handler.logging.LogLevel; 15 import io.netty.handler.logging.LoggingHandler; 16 /** 17 * @author lilinfeng 18 * @date 2014年2月14日 19 * @version 1.0 20 */ 21 public class SubReqServer { 22 public void bind(int port) throws Exception { 23 // 配置服务端的NIO线程组 24 EventLoopGroup bossGroup = new NioEventLoopGroup(); 25 EventLoopGroup workerGroup = new NioEventLoopGroup(); 26 try { 27 ServerBootstrap b = new ServerBootstrap(); 28 b.group(bossGroup, workerGroup) 29 .channel(NioServerSocketChannel.class) 30 .option(ChannelOption.SO_BACKLOG, 100) 31 .handler(new LoggingHandler(LogLevel.INFO)) 32 .childHandler(new ChannelInitializer<SocketChannel>() { 33 @Override 34 public void initChannel(SocketChannel ch) { 35 //下面该行注释则报错,因为忽略对半包的处理 ProtobufVarint32FrameDecoder 36 ch.pipeline().addLast(new ProtobufVarint32FrameDecoder()); 37 ch.pipeline().addLast( 38 new ProtobufDecoder( 39 SubscribeReqProto.SubscribeReq 40 .getDefaultInstance())); 41 ch.pipeline().addLast( 42 new ProtobufVarint32LengthFieldPrepender()); 43 ch.pipeline().addLast(new ProtobufEncoder()); 44 ch.pipeline().addLast(new SubReqServerHandler()); 45 } 46 }); 47 48 // 绑定端口,同步等待成功 49 ChannelFuture f = b.bind(port).sync(); 50 51 // 等待服务端监听端口关闭 52 f.channel().closeFuture().sync(); 53 } finally { 54 // 优雅退出,释放线程池资源 55 bossGroup.shutdownGracefully(); 56 workerGroup.shutdownGracefully(); 57 } 58 } 59 60 public static void main(String[] args) throws Exception { 61 int port = 8080; 62 if (args != null && args.length > 0) { 63 try { 64 port = Integer.valueOf(args[0]); 65 } catch (NumberFormatException e) { 66 // 采用默认值 67 } 68 } 69 new SubReqServer().bind(port); 70 } 71 }
第34行首先向ChannelPipeline添加ProtobufVarint32FrameDecoder,它主要用于半包处理,随后继续添加ProtobufDecoder解码器,它的参数是com.google.protobuf.MessageLite,实际上就是要告诉ProtobufDecoder需要解码的目标类是什么,否则仅仅从字节数组中是无法判断出要解码的目标类型信息的。
下面我们继续看SubReqServerHandler的实现。
8-3 Protobuf版本图书订购代码SubReqServerHandler
1 package lqy7_protobuf_140; 2 3 import io.netty.channel.ChannelHandler.Sharable; 4 import io.netty.channel.ChannelHandlerAdapter; 5 import io.netty.channel.ChannelHandlerContext; 6 7 /** 8 * @author lilinfeng 9 * @date 2014年2月14日 10 * @version 1.0 11 */ 12 @Sharable 13 public class SubReqServerHandler extends ChannelHandlerAdapter { 14 15 @Override 16 public void channelRead(ChannelHandlerContext ctx, Object msg) 17 throws Exception { 18 SubscribeReqProto.SubscribeReq req = (SubscribeReqProto.SubscribeReq) msg; 19 if ("Lilinfeng".equalsIgnoreCase(req.getUserName())) { 20 System.out.println("Service accept client subscribe req : [" 21 + req.toString() + "]"); 22 ctx.writeAndFlush(resp(req.getSubReqID())); 23 } 24 } 25 26 private SubscribeRespProto.SubscribeResp resp(int subReqID) { 27 SubscribeRespProto.SubscribeResp.Builder builder = SubscribeRespProto.SubscribeResp 28 .newBuilder(); 29 builder.setSubReqID(subReqID); 30 builder.setRespCode(0); 31 builder.setDesc("Netty book order succeed, 3 days later, sent to the designated address"); 32 return builder.build(); 33 } 34 35 @Override 36 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 37 cause.printStackTrace(); 38 ctx.close();// 发生异常,关闭链路 39 } 40 }
由于ProtobufDecoder已经对消息进行了自动解码,因此接收到的订购请求消息可以直接使用。对用户名进行校验,校验通过后构造应答消息返回给客户端,由于使用了ProtobufEncoder,所以不需要对SubscribeRespProto.SubscribeResp进行手工编码。
下个小节我们继续看客户端的代码实现。
Protobuf版本的图书订购客户端开发与第7章的demo类似,唯一不同的就是订购请求消息使用Protobuf进行消息编解码。
Protobuf版本图书订购代码SubReqClient
1 package lqy7_protobuf_140; 2 3 import io.netty.bootstrap.Bootstrap; 4 import io.netty.channel.ChannelFuture; 5 import io.netty.channel.ChannelInitializer; 6 import io.netty.channel.ChannelOption; 7 import io.netty.channel.EventLoopGroup; 8 import io.netty.channel.nio.NioEventLoopGroup; 9 import io.netty.channel.socket.SocketChannel; 10 import io.netty.channel.socket.nio.NioSocketChannel; 11 import io.netty.handler.codec.protobuf.ProtobufDecoder; 12 import io.netty.handler.codec.protobuf.ProtobufEncoder; 13 import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder; 14 import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender; 15 16 /** 17 * @author lilinfeng 18 * @date 2014年2月14日 19 * @version 1.0 20 */ 21 public class SubReqClient { 22 23 public void connect(int port, String host) throws Exception { 24 // 配置客户端NIO线程组 25 EventLoopGroup group = new NioEventLoopGroup(); 26 try { 27 Bootstrap b = new Bootstrap(); 28 b.group(group).channel(NioSocketChannel.class) 29 .option(ChannelOption.TCP_NODELAY, true) 30 .handler(new ChannelInitializer<SocketChannel>() { 31 @Override 32 public void initChannel(SocketChannel ch) 33 throws Exception { 34 ch.pipeline().addLast( 35 new ProtobufVarint32FrameDecoder()); 36 ch.pipeline().addLast( 37 new ProtobufDecoder( 38 SubscribeRespProto.SubscribeResp 39 .getDefaultInstance())); 40 ch.pipeline().addLast( 41 new ProtobufVarint32LengthFieldPrepender()); 42 ch.pipeline().addLast(new ProtobufEncoder()); 43 ch.pipeline().addLast(new SubReqClientHandler()); 44 } 45 }); 46 47 // 发起异步连接操作 48 ChannelFuture f = b.connect(host, port).sync(); 49 50 // 当代客户端链路关闭 51 f.channel().closeFuture().sync(); 52 } finally { 53 // 优雅退出,释放NIO线程组 54 group.shutdownGracefully(); 55 } 56 } 57 58 /** 59 * @param args 60 * @throws Exception 61 */ 62 public static void main(String[] args) throws Exception { 63 int port = 8080; 64 if (args != null && args.length > 0) { 65 try { 66 port = Integer.valueOf(args[0]); 67 } catch (NumberFormatException e) { 68 // 采用默认值 69 } 70 } 71 new SubReqClient().connect(port, "127.0.0.1"); 72 } 73 }
需要指出的是客户端需要解码的对象是订购响应,所以第37~38行使用SubscribeRespProto.SubscribeResp的实例做入参。
protobuf版本图书订购代码 SubReqClientHandler
1 package lqy7_protobuf_140; 2 3 import io.netty.channel.ChannelHandlerAdapter; 4 import io.netty.channel.ChannelHandlerContext; 5 6 import java.util.ArrayList; 7 import java.util.List; 8 9 /** 10 * @author lilinfeng 11 * @date 2014年2月14日 12 * @version 1.0 13 */ 14 public class SubReqClientHandler extends ChannelHandlerAdapter { 15 16 /** 17 * Creates a client-side handler. 18 */ 19 public SubReqClientHandler() { 20 } 21 22 @Override 23 public void channelActive(ChannelHandlerContext ctx) { 24 for (int i = 0; i < 10; i++) { 25 ctx.write(subReq(i)); 26 } 27 ctx.flush(); 28 } 29 30 private SubscribeReqProto.SubscribeReq subReq(int i) { 31 SubscribeReqProto.SubscribeReq.Builder builder = SubscribeReqProto.SubscribeReq 32 .newBuilder(); 33 builder.setSubReqID(i); 34 builder.setUserName("Lilinfeng"); 35 builder.setProductName("Netty Book For Protobuf"); 36 List<String> address = new ArrayList<>(); 37 address.add("NanJing YuHuaTai"); 38 address.add("BeiJing LiuLiChang"); 39 address.add("ShenZhen HongShuLin"); 40 builder.addAllAddress(address); 41 return builder.build(); 42 } 43 44 @Override 45 public void channelRead(ChannelHandlerContext ctx, Object msg) 46 throws Exception { 47 System.out.println("Receive server response : [" + msg + "]"); 48 } 49 50 @Override 51 public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 52 ctx.flush(); 53 } 54 55 @Override 56 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 57 cause.printStackTrace(); 58 ctx.close(); 59 } 60 }
客户端接收到服务端的应答消息之后会直接打印,按照设计,应该打印10次。下面我们就测试下Protobuf的服务端和客户端,看它是否能正常运行。
8.2.3 Protobuf版本的图书订购程序功能测试分别运行服务端和客户端,运行结果如下。
服务端
1 Service accept client subscribe req : [subReqID: 0 2 userName: "Lilinfeng" 3 productName: "Netty Book For Protobuf" 4 address: "NanJing YuHuaTai" 5 address: "BeiJing LiuLiChang" 6 address: "ShenZhen HongShuLin" 7 ] 8 Service accept client subscribe req : [subReqID: 1 9 userName: "Lilinfeng" 10 productName: "Netty Book For Protobuf" 11 address: "NanJing YuHuaTai" 12 address: "BeiJing LiuLiChang" 13 address: "ShenZhen HongShuLin" 14 ] 15 Service accept client subscribe req : [subReqID: 2 16 userName: "Lilinfeng" 17 productName: "Netty Book For Protobuf" 18 address: "NanJing YuHuaTai" 19 address: "BeiJing LiuLiChang" 20 address: "ShenZhen HongShuLin" 21 ] 22 Service accept client subscribe req : [subReqID: 3 23 userName: "Lilinfeng" 24 productName: "Netty Book For Protobuf" 25 address: "NanJing YuHuaTai" 26 address: "BeiJing LiuLiChang" 27 address: "ShenZhen HongShuLin" 28 ] 29 Service accept client subscribe req : [subReqID: 4 30 userName: "Lilinfeng" 31 productName: "Netty Book For Protobuf" 32 address: "NanJing YuHuaTai" 33 address: "BeiJing LiuLiChang" 34 address: "ShenZhen HongShuLin" 35 ] 36 Service accept client subscribe req : [subReqID: 5 37 userName: "Lilinfeng" 38 productName: "Netty Book For Protobuf" 39 address: "NanJing YuHuaTai" 40 address: "BeiJing LiuLiChang" 41 address: "ShenZhen HongShuLin" 42 ] 43 Service accept client subscribe req : [subReqID: 6 44 userName: "Lilinfeng" 45 productName: "Netty Book For Protobuf" 46 address: "NanJing YuHuaTai" 47 address: "BeiJing LiuLiChang" 48 address: "ShenZhen HongShuLin" 49 ] 50 Service accept client subscribe req : [subReqID: 7 51 userName: "Lilinfeng" 52 productName: "Netty Book For Protobuf" 53 address: "NanJing YuHuaTai" 54 address: "BeiJing LiuLiChang" 55 address: "ShenZhen HongShuLin" 56 ] 57 Service accept client subscribe req : [subReqID: 8 58 userName: "Lilinfeng" 59 productName: "Netty Book For Protobuf" 60 address: "NanJing YuHuaTai" 61 address: "BeiJing LiuLiChang" 62 address: "ShenZhen HongShuLin" 63 ] 64 Service accept client subscribe req : [subReqID: 9 65 userName: "Lilinfeng" 66 productName: "Netty Book For Protobuf" 67 address: "NanJing YuHuaTai" 68 address: "BeiJing LiuLiChang" 69 address: "ShenZhen HongShuLin" 70 ]
客户端
1 Receive server response : [subReqID: 0 2 respCode: 0 3 desc: "Netty book order succeed, 3 days later, sent to the designated address" 4 ] 5 Receive server response : [subReqID: 1 6 respCode: 0 7 desc: "Netty book order succeed, 3 days later, sent to the designated address" 8 ] 9 Receive server response : [subReqID: 2 10 respCode: 0 11 desc: "Netty book order succeed, 3 days later, sent to the designated address" 12 ] 13 Receive server response : [subReqID: 3 14 respCode: 0 15 desc: "Netty book order succeed, 3 days later, sent to the designated address" 16 ] 17 Receive server response : [subReqID: 4 18 respCode: 0 19 desc: "Netty book order succeed, 3 days later, sent to the designated address" 20 ] 21 Receive server response : [subReqID: 5 22 respCode: 0 23 desc: "Netty book order succeed, 3 days later, sent to the designated address" 24 ] 25 Receive server response : [subReqID: 6 26 respCode: 0 27 desc: "Netty book order succeed, 3 days later, sent to the designated address" 28 ] 29 Receive server response : [subReqID: 7 30 respCode: 0 31 desc: "Netty book order succeed, 3 days later, sent to the designated address" 32 ] 33 Receive server response : [subReqID: 8 34 respCode: 0 35 desc: "Netty book order succeed, 3 days later, sent to the designated address" 36 ] 37 Receive server response : [subReqID: 9 38 respCode: 0 39 desc: "Netty book order succeed, 3 days later, sent to the designated address" 40 ]
运行结果表明,我们基于Netty Protobuf编解码框架开发的图书订购程序可以正常工作。利用Netty提供的Protobuf编解码能力,我们在不需要了解Protobuf实现和使用细节的情况下就能轻松支持Protobuf编解码,可以方便地实现跨语言的远程服务调用和与周边的异构系统进行通信对接
8.3 Protobuf的使用注意事项
ProtobufDecoder仅仅负责解码,它不支持读半包。因此,在ProtobufDecoder前面,一定要有能够处理读半包的解码器F有三种方式可以选择。
1.使用Netty提供的ProtobufVarint32FrameDecoder,它可以处理半包消息:
2继承Netty提供的通用半包解码器LengthFieldBasedFrameDecoder;
3.继承ByteToMessageDecoder类,自己处理半包消息。
如果你共使用ProtobufDecoder解码器而忽略对半包消息的处理,程序是不能正常工作的。以前面的图书订购为例对服务端代码进行修改,注释ProtobufVarint32FrameDecoder
总结
本章首先介绍了Protobuf的入门知识,通过一个简单的样例代码开发让读者熟悉了如何使用Protobuf对POJO对象进行编解码:在掌握了Protobuf的基础知识之后,讲解如何使用Netty的Protobuf编解码框架进行客户端和服务端的开发;最后,对Protobuf解码器的使用陷阱进行了说明,并给出了正确的使用建议。
在下一章中,我们继续学习另一种序列化技术JBoss的Marshalling序列化框架,它是JBoss内部使用的序列化框架,Netty提供了Marshalling编码和解码器,方便用户在Netty中使用Marshalling。第9章适用于对Marshalling框架感兴趣的读者,如果你想直接学习后面的知识,也可以跳过第9章。
(中级篇 NettyNIO编解码开发)第八章-Google Protobuf 编解码-2
原文:http://www.cnblogs.com/crazylqy/p/4901369.html