趁着今天有时间,多发一点。这一章主要介绍Protobuf编解码器,首先先说protobuf是个什么东西。它其实是Google开发的一个数据交换的格式,它独立于语言,独立于平台。用过它的都会觉得它太好用了,记得当初在iesLab开发电力系统高级应用软件的服务端用到的通信协议就是它,除了代码风格不是很好之外,其他的真是太方便了,膜拜Google的技术啊。
在详细介绍之前,我先来问大家一个问题,如果让你开发一款通信协议,你希望如何设计协议?我相信,大家既然都已经习惯了面向对象编程,都是选择将对象转换为协议发送,当然接受的时候是将协议转换为对象。先抛开半包问题不说,先谈数据帧的传输,我们是不是希望:1. 通信的时候最好能用最少的字节来传递更多的消息,这样就可以加快消息的传输,产生很小的延迟。2. 我们的消息可以跨平台传输,不管对方用的什么语言?C还是C++还是java,对同样的通信协议可以通用,不会因为不同的平台因为字节大小而痛苦。那么java给我带来的序列化可以用么?当然可以但是有个问题,首先java的序列化的对象生成的字节数目太过庞大,几乎是正常二进制编码的5倍左右,当然是protobuf的更度倍。第二,你用java平台序列化的对象,在C++的平台是没法解析的。所以,基于这个protobuf给我们解决了,他的编码字节足够小,不同平台直接可以通用。
在Netty中天生给我们集成了protobuf的编解码器,不需要我们自己去实现,主要有解码器:ProtobufVarint32FrameDecoder(负责半包解码),ProtobufDecoder(负责protobuf的解码)
编码器:ProtobufVarint32LengthFieldPrepender(负责半包编码),ProtobufEncoder(负责协议编码)
这个其实很好理解,一个是为了解决半包问题,另一个解决协议解析问题。
我们看一下服务端的代码:
SubscribeProto.Subscribe.getDefaultInstance()这个是protobuf协议工具给我们生成的,具体的百度一下就知道了,它是什么意思呢?你想啊,Netty的解码器又不是万能的,他怎么知道你的帧格式是什么样子的?所以这个就是告诉他你给我照着这个样式解码,是不是很方便啊!
package com.dlb.note.server; import com.dlb.note.doj.SubscribeProto; import com.google.protobuf.ProtocolStringList; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.protobuf.ProtobufDecoder; import io.netty.handler.codec.protobuf.ProtobufEncoder; import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder; import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender; /** * 功能:protobuf时间服务器 * 版本:1.0 * 日期:2016/12/9 18:33 * 作者:馟苏 */ public class ProtobufTimeServer { /** * main函数 * @param args */ public static void main(String []args) { // 构造nio线程组 EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workGroup = new NioEventLoopGroup(); try { ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(bossGroup, workGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 1024) .childHandler(new ChannelInitializer() { protected void initChannel(Channel channel) throws Exception { /** * 以下是支持protobuf的解码器 */ channel.pipeline() .addLast(new ProtobufVarint32FrameDecoder()) // 半包解码 .addLast(new ProtobufDecoder(SubscribeProto.Subscribe.getDefaultInstance())) // protobuf解码 .addLast(new ProtobufVarint32LengthFieldPrepender()) // 半包编码 .addLast(new ProtobufEncoder()) // protobuf编码 .addLast(new MyProtoBufHandler()); } }); // 绑定端口,同步等待成功 ChannelFuture future = bootstrap.bind(8888).sync(); System.out.println("----服务端在8888端口监听----"); // 等待服务端监听端口关闭 future.channel().closeFuture().sync(); } catch (Exception e) { e.printStackTrace(); } finally { // 优雅的退出,释放线程池资源 bossGroup.shutdownGracefully(); workGroup.shutdownGracefully(); } } } class MyProtoBufHandler extends ChannelHandlerAdapter { // 客户端链接异常 @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { System.out.println("client exception,ip=" + ctx.channel().remoteAddress()); ctx.close(); super.exceptionCaught(ctx, cause); } // 客户端链接到来 @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { System.out.println("client come,ip=" + ctx.channel().remoteAddress()); super.channelActive(ctx); } // 客户端链接关闭 @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { System.out.println("client close,ip=" + ctx.channel().remoteAddress()); ctx.close(); super.channelInactive(ctx); } // 可读 @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // 读数据 SubscribeProto.Subscribe subscribe = (SubscribeProto.Subscribe) msg; ProtocolStringList addressList = subscribe.getAddressList(); System.out.println(subscribe.getName()); for (String str : addressList) { System.out.println(str); } System.out.println("---------------------------------------------------"); super.channelRead(ctx, msg); } }