Netty使用Google的ProtoBuf

protobuf是由Google开发的一套对数据结构进行序列化的方法,可用做通信协议,数据存储格式,等等。其特点是不限语言、不限平台、扩展性强

Netty也提供了对Protobuf的天然支持,我们今天就写一个简单的示例,简单地了解一下Netty对Google的protoBuf的支持

我们的示例场景很简单的:客户端发送一个信息,这个信息用Protobuf来做序列化,然后服务器端接收这个信息,解码,读取信息

protobuf与xml,json这样的数据格式一样,都有自己的一套语法,且语法很简单,很容易掌握,xml文件的后缀名是xml,json的后缀名是json,以此类推,那么protobuf的后缀名就是proto

关于proto的基本语法与java的bean很像,详细可以参考官网,可以看下这篇博客:

http://blog.sina.com.cn/s/blog_9b0604b40101qm35.html

现在我们定义一个类似java bean的proto文件,我们定义一个“富人”类,他有多辆车,我们先按照语法,写一个RichMan.proto,如下面的代码清单所示:

[plain]  view plain  copy
 
  1. package netty;  
  2.   
  3. option java_package = "com.lyncc.netty.codec.protobuf.demo";  
  4. option java_outer_classname = "RichManProto";  
  5.   
  6. message RichMan {  
  7.   
  8.    required int32 id = 1;  
  9.    required string name = 2;  
  10.    optional string email = 3;  
  11.      
  12.    enum CarType {  
  13.      AUDI = 0;  
  14.      BENZ = 1;  
  15.      LAMBORGHINI = 2;  
  16.      DASAUTO = 3;  
  17.    }  
  18.      
  19.    message Car {  
  20.       required string name = 1;  
  21.       optional CarType type = 2 [default = BENZ];  
  22.    }  
  23.      
  24.    repeated Car cars = 4;  
  25.      
  26. }  

给出上面代码的一些基本解释:

1)java_package值得是该文件生成的java文件的包路径

2)java_outer_classname值的是生成的class的名称

3)message和enum是它的基本类型,很类似于java的class和枚举

4)required表名这个字段是必须的,option表明这个字段可选,default表明这个字段有默认值

5)repeat表明这个字段可以重复,类似于java中的List,该例子中Car的声明中,就相当于java中的List<Car>

6)每个声明的后面的数字,例如1,2,3, 4等等,同级的声明不能重复

总而言之,这个“类”定义了一个富人,该富人有id,名称,邮箱,而且该富人有多个名车,这些名车的类型有奥迪,奔驰,兰博基尼,大众

好了,到目前为止,proto我们已经定义好了,Google提供了一个类似脚本的工具,可以使我们将proto文件转化成java文件

该文件叫做protoc-2.6.1-win32.zip,可以在很多地方下载到,下载地址:

http://download.csdn.net/detail/linuu/9515171

下载好,新建文件夹,且将加载的exe复制到该文件夹,且将我们刚才写的RichMan.proto复制到该文件夹下:

进入命令行,键入:

没有报错的情况下,会在同样的文件夹下生成如下的文件:

进入com的文件夹,你会发现生成的目录是与你proto中定义的java_package一样:

好了,到目前为止,我们已经生成了RichManProto文件了,将其复制到eclipse对应的目录下,整个项目代码的缩略图如下图所示:

添加maven的依赖:

[html]  view plain  copy
 
  1. <dependency>  
  2.     <groupId>com.google.protobuf</groupId>  
  3.     <artifactId>protobuf-java</artifactId>  
  4.     <version>2.6.1</version>  
  5. </dependency>  

注意就是版本必须是2.6.1,因为我们用的是protoc-2.6.1的exe去编译的,所以版本必须保持一致,否则有可能会报错

接下来就是一些大家耳熟能详的server,handler,client,bootstrap,废话多不说,上代码:

ProtoBufServer.java

[java]  view plain  copy
 
  1. package com.lyncc.netty.codec.protobuf.demo;  
  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.EventLoopGroup;  
  8. import io.netty.channel.nio.NioEventLoopGroup;  
  9. import io.netty.channel.socket.SocketChannel;  
  10. import io.netty.channel.socket.nio.NioServerSocketChannel;  
  11. import io.netty.handler.codec.protobuf.ProtobufDecoder;  
  12. import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;  
  13. import io.netty.handler.logging.LogLevel;  
  14. import io.netty.handler.logging.LoggingHandler;  
  15.   
  16. public class ProtoBufServer {  
  17.       
  18.     public void bind(int port) throws Exception {  
  19.         // 配置服务端的NIO线程组  
  20.         EventLoopGroup bossGroup = new NioEventLoopGroup();  
  21.         EventLoopGroup workerGroup = new NioEventLoopGroup();  
  22.         try {  
  23.             ServerBootstrap b = new ServerBootstrap();  
  24.             b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 100)  
  25.                     .handler(new LoggingHandler(LogLevel.INFO)).childHandler(new ChannelInitializer<SocketChannel>() {  
  26.                         @Override  
  27.                         public void initChannel(SocketChannel ch) {  
  28.                             ch.pipeline().addLast(new ProtobufVarint32FrameDecoder());  
  29.                             ch.pipeline().addLast(new ProtobufDecoder(RichManProto.RichMan.getDefaultInstance()));  
  30.                             ch.pipeline().addLast(new ProtoBufServerHandler());  
  31.                         }  
  32.                     });  
  33.   
  34.             // 绑定端口,同步等待成功  
  35.             ChannelFuture f = b.bind(port).sync();  
  36.   
  37.             System.out.println("init start");  
  38.             // 等待服务端监听端口关闭  
  39.             f.channel().closeFuture().sync();  
  40.         } finally {  
  41.             // 优雅退出,释放线程池资源  
  42.             bossGroup.shutdownGracefully();  
  43.             workerGroup.shutdownGracefully();  
  44.         }  
  45.     }  
  46.   
  47.     public static void main(String[] args) throws Exception {  
  48.         int port = 8080;  
  49.         if (args != null && args.length > 0) {  
  50.             try {  
  51.                 port = Integer.valueOf(args[0]);  
  52.             } catch (NumberFormatException e) {  
  53.                 // 采用默认值  
  54.             }  
  55.         }  
  56.         new ProtoBufServer().bind(port);  
  57.     }  
  58.   
  59. }  

可以看见,这个sever的bootstrap与其他的sever很相似,只是channelPipeline中在自定义的handler之前添加了netty对protobuf支持的两个天然的decoder

我没有深究,看名字就知道第一个Decoder是将帧byte数据转化成message,第二步就是将message转化成我们自定义的Rimanproto

自定义的handler很简单,就是打印一下解析的内容:

ProtoBufServerHandler.java

[java]  view plain  copy
 
  1. package com.lyncc.netty.codec.protobuf.demo;  
  2.   
  3. import io.netty.channel.ChannelHandlerContext;  
  4. import io.netty.channel.ChannelInboundHandlerAdapter;  
  5.   
  6. import java.util.List;  
  7.   
  8. import com.lyncc.netty.codec.protobuf.demo.RichManProto.RichMan.Car;  
  9.   
  10. public class ProtoBufServerHandler extends ChannelInboundHandlerAdapter {  
  11.       
  12.     @Override  
  13.     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {  
  14.         RichManProto.RichMan req = (RichManProto.RichMan) msg;  
  15.         System.out.println(req.getName()+"他有"+req.getCarsCount()+"量车");  
  16.         List<Car> lists = req.getCarsList();  
  17.         if(null != lists) {  
  18.               
  19.             for(Car car : lists){  
  20.                 System.out.println(car.getName());  
  21.             }  
  22.         }  
  23.     }  
  24.   
  25.   
  26.     @Override  
  27.     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {  
  28.         cause.printStackTrace();  
  29.         ctx.close();   
  30.     }  
  31.   
  32. }  

客户端代码

ProtoBufClient.java

[java]  view plain  copy
 
  1. package com.lyncc.netty.codec.protobuf.demo;  
  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.ProtobufEncoder;  
  12. import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;  
  13.   
  14. public class ProtoBufClient {  
  15.       
  16.     public void connect(int port, String host) throws Exception {  
  17.         // 配置客户端NIO线程组  
  18.         EventLoopGroup group = new NioEventLoopGroup();  
  19.         try {  
  20.             Bootstrap b = new Bootstrap();  
  21.             b.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true)  
  22.                     .handler(new ChannelInitializer<SocketChannel>() {  
  23.                         @Override  
  24.                         public void initChannel(SocketChannel ch) throws Exception {  
  25.                             ch.pipeline().addLast(new ProtobufVarint32LengthFieldPrepender());  
  26.                             ch.pipeline().addLast(new ProtobufEncoder());  
  27.                             ch.pipeline().addLast(new ProtoBufClientHandler());  
  28.                         }  
  29.                     });  
  30.   
  31.             // 发起异步连接操作  
  32.             ChannelFuture f = b.connect(host, port).sync();  
  33.   
  34.             // 当代客户端链路关闭  
  35.             f.channel().closeFuture().sync();  
  36.         } finally {  
  37.             // 优雅退出,释放NIO线程组  
  38.             group.shutdownGracefully();  
  39.         }  
  40.     }  
  41.   
  42.     /** 
  43.      * @param args 
  44.      * @throws Exception 
  45.      */  
  46.     public static void main(String[] args) throws Exception {  
  47.         int port = 8080;  
  48.         if (args != null && args.length > 0) {  
  49.             try {  
  50.                 port = Integer.valueOf(args[0]);  
  51.             } catch (NumberFormatException e) {  
  52.                 // 采用默认值  
  53.             }  
  54.         }  
  55.         new ProtoBufClient().connect(port, "127.0.0.1");  
  56.     }  
  57.   
  58. }  

需要注意的是:

在传输之前需要将你的类进行protobuf的序列化,这是两个序列化的编码器

接着看:

ProtoBufClientHandler.java

[java]  view plain  copy
 
  1. package com.lyncc.netty.codec.protobuf.demo;  
  2.   
  3. import io.netty.channel.ChannelHandlerContext;  
  4. import io.netty.channel.ChannelInboundHandlerAdapter;  
  5.   
  6. import java.util.ArrayList;  
  7. import java.util.List;  
  8.   
  9. import com.lyncc.netty.codec.protobuf.demo.RichManProto.RichMan.Car;  
  10. import com.lyncc.netty.codec.protobuf.demo.RichManProto.RichMan.CarType;  
  11.   
  12. public class ProtoBufClientHandler extends ChannelInboundHandlerAdapter {  
  13.       
  14.     @Override  
  15.     public void channelActive(ChannelHandlerContext ctx) {  
  16.         System.out.println("=======================================");  
  17.         RichManProto.RichMan.Builder builder = RichManProto.RichMan.newBuilder();  
  18.         builder.setName("王思聪");  
  19.         builder.setId(1);  
  20.         builder.setEmail("[email protected]");  
  21.           
  22.         List<RichManProto.RichMan.Car> cars = new ArrayList<RichManProto.RichMan.Car>();  
  23.         Car car1 = RichManProto.RichMan.Car.newBuilder().setName("上海大众超跑").setType(CarType.DASAUTO).build();  
  24.         Car car2 = RichManProto.RichMan.Car.newBuilder().setName("Aventador").setType(CarType.LAMBORGHINI).build();  
  25.         Car car3 = RichManProto.RichMan.Car.newBuilder().setName("奔驰SLS级AMG").setType(CarType.BENZ).build();  
  26.           
  27.         cars.add(car1);  
  28.         cars.add(car2);  
  29.         cars.add(car3);  
  30.           
  31.         builder.addAllCars(cars);  
  32.         ctx.writeAndFlush(builder.build());  
  33.     }  
  34.   
  35.   
  36.     @Override  
  37.     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {  
  38.         cause.printStackTrace();  
  39.         ctx.close();  
  40.     }  
  41.   
  42. }  

好了,到此为止,所有的代码已经写完毕了,我们运行测试一下:

服务器端启动:

启动客户端后,再看看服务器端的控制台:

好了,到此为止,最简单的demo已经搭建完毕了~

猜你喜欢

转载自www.cnblogs.com/duan2/p/8919351.html