【Netty4 简单项目实践】三、压缩消息体:使用google的protocol buff
2016-04-25 22:25
597 查看
这里能看到
怎么
配置处理器
怎么 生成protocol buff的消息类
怎么 接收多个类型的protocol buff
怎么 发送 protocol buff
【前言】老古董的C++程序员最讨厌的地方就是爱扣什么鸟传输带宽。讲道理,带宽上的那点开销和编解码的开销+代码维护(Json)成本比起来根本不值一提。
比如之前看到的:
public class ByteStreamHandler extends SimpleChannelInboundHandler<String>{
}
如果写成
public class ByteStreamHandler extends SimpleChannelInboundHandler<byte[]>{
}
那么StringDecoder的输出根本不会传递到 ByteStreamHandler,奇妙的地方就在SimpleChannelInboundHandler指定的类型。
所以想用Protocol Buff的话,最终的处理器要定义成Protocol Buff类型的处理器
打开之后到google/protobuf目录下随便拷贝个.proto文件出来,咱们改一改
比如把 any.proto 拷贝出来做修改
这里有个变通的方法---封装类。
举个例子,假如有两个消息体(按protocol buff 3协议)
定义一个包装类
这样就好了!用上面的./protoc --java_out=./ any.proto 命令生成对应的.java文件。(在protocol buff3里面,所有的域默认都是optional的)
那么如何使用呢?现在,处理器可以实现为
这样就实现了对象分类。当然一口气写俩处理类也不是不行。
|-0x00 0x10(两字节0x0010,表示消息体长度为16)-||-0x00 0x01(两字节命令字0x0001)-||- 16个字节 (16字节的消息体)-|
所以不得不增加解码这种消息体的解码器,Netty4有这种解码器 LengthFieldBasedFrameDecoder。解码器参数采用
lengthFieldOffset 0 表示长度域开始的位置,这里从0开始
lengthFieldLength 2 表示长度域的字节数,这里是2字节
lengthAdjustment 2 表示长度域长度需要加2。解码器从长度域下一个字节开始,捕获“长度+2”个字节作为消息的剩余部分字节
initialBytesToStrip 4 表示消息体的起始位置,从第四个字节开始
类似的还有一个编码器LengthFieldPrepender
lengthFieldLength 2 表示消息体长度2字节
lengthAdjustment -4表示消息体的真实长度为 编码后数据长度-4
然后还需要一对儿 Protocol编解码器
new ProtobufDecoder(Protocol.getDefaultInstance())
new
MyProtobufEncoder()
原有的ProtobufEncoder()处理不了中间多出来的两个字节的命令字,所以得自己写一个来剥离命令字
MyProtobufEncoder.java
消息封装类
现在来改造适合protocol buff的bootstrap。直接拿String服务的例子改造,TCP连接参数不变。只需要修改挂接的编解码器和处理器。
pipeline.addLast("frameEncoder",new LengthFieldPrepender(2,
-4, true));
pipeline.addLast("protoBufEncoder",new MyProtobufEncoder());
//包头有2字节包长,2字节类型,类型在本地用反射取出来,不依靠传递的类型
pipeline.addLast("frameDecoder",new
LengthFieldBasedFrameDecoder(65535, 0, 2, 2, 4));
pipeline.addLast("protoBufDecoder",new
ProtobufDecoder(Protocol.getDefaultInstance()));
pipeline.addLast(new MyHandler());
至此一个protocol buff的服务完成了。。。。么?
这里从内到外构造。
先构造一个Message对象: Message msg =Message.newBuilder().setId(1).setMsg("OK").build();
再构造Protocol对象:Protocol p = Protocol.newBuilder().setMessage(msg).build();
再封装成编码器可以理解的,上文中的MyMessageLite对象。
最后向ctx写入:ctx.writeAndFlush(MyMessageLite)
整个流程到此结束
怎么
配置处理器
怎么 生成protocol buff的消息类
怎么 接收多个类型的protocol buff
怎么 发送 protocol buff
【前言】老古董的C++程序员最讨厌的地方就是爱扣什么鸟传输带宽。讲道理,带宽上的那点开销和编解码的开销+代码维护(Json)成本比起来根本不值一提。
ChannelHandlerContext原理
本来是不想讲原理的,但是有几个认知需要明确一下。在每个处理类(比如编解码)的输出,如果不满足下一个类的输入类型要求,下一个类是根本不会响应。比如之前看到的:
public class ByteStreamHandler extends SimpleChannelInboundHandler<String>{
}
如果写成
public class ByteStreamHandler extends SimpleChannelInboundHandler<byte[]>{
}
那么StringDecoder的输出根本不会传递到 ByteStreamHandler,奇妙的地方就在SimpleChannelInboundHandler指定的类型。
所以想用Protocol Buff的话,最终的处理器要定义成Protocol Buff类型的处理器
开始Protocol Buff
https://github.com/google/protobuf/releases到这来下载,不要自己去编译,因为自己编译要先编译C++版本,而编译C++版本要从google下代码。。。打开之后到google/protobuf目录下随便拷贝个.proto文件出来,咱们改一改
比如把 any.proto 拷贝出来做修改
syntax = "proto3"; //第一行一定要写成这样;第一行一定要写成这样;第一行一定要写成这样 package seeplant.protobuf; //注意包名格式,这里的包名是本文件的包名 option java_package = "com.seeplant.protobuf"; //这里的包名是生成的java文件所包含的包名 option java_outer_classname = "ProtoMsg"; option java_multiple_files = true; // 会生成多个对象文件,一个Message一个 option java_generate_equals_and_hash = true; //然后定义一个Lgin消息,1234567是占位符,按顺序写就好了,注意第一个一定是1 message Login { uint32 msgType = 1; string userType = 2; int64 uid = 3; string userName = 4; string password = 5; int64 clsId = 6; int64 roomId = 7; }; //到此为止
生成java对象
在下载的protocol程序包的目录底下(有readme的地方),运行下面两行命令,之后你要做的就是把本地的com文件夹复制到eclipse里面!rm -rf ./com/ ./protoc --java_out=./ any.proto
多个消息体怎么办
第二讲说了,在我们的Netty里面的处理器只能接收一个对象,所以呢,假如你有8个类型的消息,你是要写8个处理器挂接到pipline上么?这里有个变通的方法---封装类。
举个例子,假如有两个消息体(按protocol buff 3协议)
message Login{ int32 id=1; }; message Message{ int64 id=1; string msg=2; };</span>
定义一个包装类
message Protocol{ Login login=1; Message msg=2; };
这样就好了!用上面的./protoc --java_out=./ any.proto 命令生成对应的.java文件。(在protocol buff3里面,所有的域默认都是optional的)
那么如何使用呢?现在,处理器可以实现为
public class MyHandler extends SimpleChannelInboundHandler<Protocol>{ @Override protected void channelRead0(ChannelHandlerContextctx, Protocolmsg)throws Exception { if(msg.hasLogin()) { //处理login Login login = msg.getLogin(); } else if (msg.hasMessage()) {} } }
这样就实现了对象分类。当然一口气写俩处理类也不是不行。
事情远没有结束--之前的连包问题解决不了
之前string服务里面靠终止字符来切割消息的办法现在不好用了,因为传的都是二进制码流。按照C++程序员的习惯,不得不加上两字节表示消息体长度的消息头,甚至要加两字节的“命令字”表示是哪种消息(看到这种C++程序员直接打死好吗,完全没有面向对象的能力)。现在发送的消息变成这样了|-0x00 0x10(两字节0x0010,表示消息体长度为16)-||-0x00 0x01(两字节命令字0x0001)-||- 16个字节 (16字节的消息体)-|
所以不得不增加解码这种消息体的解码器,Netty4有这种解码器 LengthFieldBasedFrameDecoder。解码器参数采用
lengthFieldOffset 0 表示长度域开始的位置,这里从0开始
lengthFieldLength 2 表示长度域的字节数,这里是2字节
lengthAdjustment 2 表示长度域长度需要加2。解码器从长度域下一个字节开始,捕获“长度+2”个字节作为消息的剩余部分字节
initialBytesToStrip 4 表示消息体的起始位置,从第四个字节开始
类似的还有一个编码器LengthFieldPrepender
lengthFieldLength 2 表示消息体长度2字节
lengthAdjustment -4表示消息体的真实长度为 编码后数据长度-4
然后还需要一对儿 Protocol编解码器
new ProtobufDecoder(Protocol.getDefaultInstance())
new
MyProtobufEncoder()
原有的ProtobufEncoder()处理不了中间多出来的两个字节的命令字,所以得自己写一个来剥离命令字
MyProtobufEncoder.java
@Sharable public class MyProtobufEncoderextends MessageToMessageEncoder<MyMessageLite> { @Override protected void encode( ChannelHandlerContext ctx, MyMessageLitemsg, List<Object>out)throws Exception { MessageLiteOrBuilder frame = msg.getMessageLit(); if (frameinstanceof MessageLite) { byte[] command = msg.getCommand(); byte[] load = ((MessageLite) frame).toByteArray(); ByteBufAllocator allocor = ctx.alloc(); ByteBuf buff = allocor.buffer(command.length +load.length); buff.writeBytes(command); buff.writeBytes(load); byte[] dst = new byte[load.length + command.length]; buff.getBytes(0, dst); out.add(buff); return; } if (frameinstanceof MessageLite.Builder) { out.add(wrappedBuffer(((MessageLite.Builder)frame).build().toByteArray())); } } }
消息封装类
public class MyMessageLite { private MessageLiteOrBuilder messageLit = null; byte[] command =new byte[2]; public MyMessageLite(byte[]command, MessageLiteOrBuildermessageLit){ this.command =command; this.messageLit =messageLit; } // setter getter略 }
改造bootstrap
终于万事俱备了。现在来改造适合protocol buff的bootstrap。直接拿String服务的例子改造,TCP连接参数不变。只需要修改挂接的编解码器和处理器。
pipeline.addLast("frameEncoder",new LengthFieldPrepender(2,
-4, true));
pipeline.addLast("protoBufEncoder",new MyProtobufEncoder());
//包头有2字节包长,2字节类型,类型在本地用反射取出来,不依靠传递的类型
pipeline.addLast("frameDecoder",new
LengthFieldBasedFrameDecoder(65535, 0, 2, 2, 4));
pipeline.addLast("protoBufDecoder",new
ProtobufDecoder(Protocol.getDefaultInstance()));
pipeline.addLast(new MyHandler());
至此一个protocol buff的服务完成了。。。。么?
如何发消息
事情并没有结束,我们发消息也是消息头+命令字+protocol buff形式。在向ChannelHandlerContex写数据的时候也必须封装两次才行。这里从内到外构造。
先构造一个Message对象: Message msg =Message.newBuilder().setId(1).setMsg("OK").build();
再构造Protocol对象:Protocol p = Protocol.newBuilder().setMessage(msg).build();
再封装成编码器可以理解的,上文中的MyMessageLite对象。
最后向ctx写入:ctx.writeAndFlush(MyMessageLite)
整个流程到此结束
相关文章推荐
- 异步FIFO
- django中models阅读笔记
- [POJ3107]Godfather(树形dp)
- 为什么catagory可以增加成员方法,不可以增加成员变量
- Golang tls 链接通信
- libgdx 裁剪多边形(clip polygon、masking polygon)
- golang的的模板引擎之pongo2
- 【GOF】单例模式singleTon的再认识
- Windows+GO+beego
- django fields lookup methods
- django站点管理1
- django 模板if判断的时候==两边需要有空格
- ContentNegotiatingViewResolver解析器
- URAL 1851|GOV-internship|最小割
- django migrate error: table 'xxx' already exists
- mongoose使用简记
- Django ValidationError中的单下划线
- category的介绍与简单实用
- Git Step By Step - Step 2: Go Back in Time
- golang中的类和接口的使用