Netty使用protobuf作为通信协议

Netty中使用protobuf

  1. 环境准备

下载protoc代码生成器和源码包:http://code.google.com/p/protobuf/downloads/list

  • protobuf-2.4.1.tar.bz2:主要是生成protobuf-2.4.1.jar,操作需要安装maven,可以到网上下载这个Jar包,所以这里略。
  • protoc-2.4.1-win32.zip:生成XXX.Proto执行文件。

     

    参考http://blog.csdn.net/dreamdoc/article/details/6577180相关代码

     

  1. 制作.Proto文件

格式参考如下:

Netty使用protobuf作为通信协议_第1张图片

注:可以按如上内容编写文本文件,然后修改后缀为.Proto。

  1. proto文件结构

从上面的代码可以看出,proto文件的结构非常简单。

  • package java_package 定义生成的java类的package名称。
  • message定义一种类型,类型以Java中的一个class,Book是类名,在生成Java代码时,就是使用这个类名。
  • option optimize_for = LITE_RUNTIME:表示生成的java代码继承GeneratedMessageLite ,继承GeneratedMessageLite的好处是可张念以用jboss提供的编解码器,可以认为是必写项。

 

  • 该类型中包含的字段格式: [限定符 类型 字段名称 = tag [default = 默认值]]

 

  1. 支持的默认类型

.proto Type

Notes

C++ Type

Java Type

double

 

double

double

float

 

float

float

int32

Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead.

int32

int

int64

Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead.

int64

long

uint32

Uses variable-length encoding.

uint32

int1

uint64

Uses variable-length encoding.

uint64

long1

sint32

Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s.

int32

int

sint64

Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s.

int64

long

fixed32

Always four bytes. More efficient than uint32 if values are often greater than 228.

uint32

int1

fixed64

Always eight bytes. More efficient than uint64 if values are often greater than 256.

uint64

long1

sfixed32

Always four bytes.

int32

int

sfixed64

Always eight bytes.

int64

long

bool

 

bool

boolean

string

A string must always contain UTF-8 encoded or 7-bit ASCII text.

string

String

bytes

May contain any arbitrary sequence of bytes.

string

ByteString

    除此之外,还支持枚举,自定义类型等。

  1. protobuf的限定符

  • required:一个格式良好的消息一定要含有1个这种字段。表示该值是必须要设置的;如果该项不设值,在序列化时会抛出RuntimeException,推荐不使用该字段,该字段一旦使用,无法更改。

     

    由于一些历史原因,基本数值类型的repeated的字段并没有被尽可能地高效编码。在新的代码中,用户应该使用特殊选项[packed=true]来保证更高效的编码。如:repeated int32 samples = 4 [packed=true];

     

    required是永久性的:在将一个字段标识为required的时候,应该特别小心。如果在某些情况下不想写入或者发送一个required的字段,将原始该字段修饰符更改为optional可能会遇到问题——旧版本的使用者会认为不含该字段的消息是不完整的,从而可能会无目的的拒绝解析。在这种情况下,你应该考虑编写特别针对于应用程序的、自定义的消息校验函数。Google的一些工程师得出了一个结论:使用required弊多于利;他们更愿意使用optional和repeated而不是required。当然,这个观点并不具有普遍性。

     

  • optional:消息格式中该字段可以有0个或1个值(不超过1个)。不存在时使用默认值,默认值可自定义如[default = 10]。系统默认值如下:int = 0,bool = false,string=""

     

  • repeated:在一个格式良好的消息中,这种字段可以重复任意多次(包括0次)。重复的值的顺序会被保留。表示该值可以重复,相当于java中的List。

     

  1. guide style

Protocol Buffers官方提供了一个Guide Style。其中介绍了一些定义proto的优秀实践。一句话总结就是名称时,message名称以驼峰方式定义,字段名则需要以如author_name方式定义。字段名在生成Java代码时,会自动转换为符合Java风格的命名方式。

  1. 生成. Java文件

将编写的.Proto文件与下载的protoc.exe文件放在同一指定的目录,进入dos并系统编译命令:protoc XXX.proto --java_out=C:/,这时会在你所指令的目录生成相应的java文件。

  1. 它的一些缺点

  • 不支持大数据集的处理,少于1M
  • 不支持Date,Map这些Java内建的对象

 

  1. 客户端示例

Netty使用protobuf作为通信协议_第2张图片

  • 如果Message类中用到repeated 定义的类型,则在客户端中按如下代码操作: builder.addBody(Message.Body.newBuilder().setContent(bytes).build());
  • 也可用下面默认的方式传输二进制,这时在节点7中就要使用默认的protobuf解码方式,

    byte[] test = "asdfa".getBytes();        

    ChannelBuffer channelBuffer = ChannelBuffers.buffer(test.length);

    // 将 获得到的数组写入 channelBuffer中

    channelBuffer.writeBytes(test);

    // 发送到服务器端

    ChannelFuture lastWriteFuture = channel.write(channelBuffer);

  1. 服务器端示例

  1. ServerHandler示例

  • 如果使用默认的Protobuf解码方式传输二进制,则接收时按以下代码接收:

if((e.getMessage() instanceof ChannelBuffer)){

        ChannelBuffer channelBuffer =(ChannelBuffer)e.getMessage();

        byte[] bytes = channelBuffer.array();

}

  1. PipelineFactory示例

Netty使用protobuf作为通信协议_第3张图片

  • 其中,以下两行代码是默认的Protobuf解码类型,如果到自定义解码,这两行可以注释掉,反之把自定义的加码解码注释掉.

pipeline.addLast("frameDecoder", new ProtobufVarint32FrameDecoder());

pipeline.addLast("frameEncoder", new ProtobufVarint32LengthFieldPrepender());

  • org.jboss.netty.handler.codec.frame ,这个包里面有三种封包方法(主要用在传输字符串):

    1.DelimiterBasedFrameDecoder 是利用分隔符来进行包的界定;2.FixedLengthFrameDecoder 是利用固定的长度来进行包的界定;3.LengthFieldBasedFrameDecoder 和 LengthFieldPrepender 是利用在发送数据的时候在里面加上头字段,头字段里面包含了包的长度。

netty自带的例子io.netty.example.worldclock,就是使用protobuf作为网络协议.

本文实现客户端-服务端之间请求-响应的网络协议,会在worldclock例子上稍作扩展.

首先定义protobuf,对于多种请求的格式,使用Union Type,以下Login,Service分别扩展了Request.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
message Request {
   extensions  100  to max;
   enum  Type{
     LOGIN= 0 ;
     SERVICE= 1 ;
   }
   required Type type= 1 ;
}
 
extend Request {
   optional Login login =  100 ;
   optional Service service =  101 ;
}
 
message Login {
   required string user =  1 ;
   required string pswd =  2 ;
}
 
message Service {
   optional string content =  1 ;
}
 
message Response {
   optional string result =  1 ;
}

客户端的主要代码,其他参考worldclock例子.

?
1
2
3
4
5
6
7
Login login = Login.newBuilder().setUser( "it is user!" )
         .setPswd( "it is pswd!" ).build();
Request.Builder builder = Request.newBuilder();
builder.setType(Request.Type.LOGIN);
builder.setExtension(Protocol.login, login);
Request request = builder.build();
ch.writeAndFlush(request).sync();

服务端的主要代码.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
switch  (request.getType().getNumber()) {
case  (Request.Type.LOGIN_VALUE): 
     System.out.println(request.getExtension(Protocol.login).getUser() +  " "
             + request.getExtension(Protocol.login).getPswd());
     break ;
case  (Request.Type.SERVICE_VALUE): 
     break ;
default :
     System.out.println( "Don't know this type" );
}
Response.Builder builder = Response.newBuilder();
builder.setResult( "I am response result!" );
ctx.writeAndFlush(builder.build()).sync();

要注意的是在服务端,你还要告诉netty,protobuf的extension如何解码.

?
1
2
3
4
5
6
7
8
9
ExtensionRegistry registry = ExtensionRegistry.newInstance();
Protocol.registerAllExtensions(registry);
ChannelPipeline p = ch.pipeline();
p.addLast( "frameDecoder" new  ProtobufVarint32FrameDecoder());
p.addLast( "protobufDecoder" ,
         new  ProtobufDecoder(Protocol.Request.getDefaultInstance(),
                 registry));
p.addLast( "frameEncoder" new  ProtobufVarint32LengthFieldPrepender());
p.addLast( "protobufEncoder" new  ProtobufEncoder());

以下是和worldclock例子中不同的地方.

?
1
2
3
4
ExtensionRegistry registry = ExtensionRegistry.newInstance();
Protocol.registerAllExtensions(registry);
p.addLast( "protobufDecoder" ,
         new  ProtobufDecoder(Protocol.Request.getDefaultInstance(),registry));

你可能感兴趣的:(netty,nio,protobuf)