Netty中使用protobuf
-
环境准备
下载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相关代码
-
制作.Proto文件
格式参考如下:
注:可以按如上内容编写文本文件,然后修改后缀为.Proto。
-
proto文件结构
从上面的代码可以看出,proto文件的结构非常简单。
- package java_package 定义生成的java类的package名称。
- message定义一种类型,类型以Java中的一个class,Book是类名,在生成Java代码时,就是使用这个类名。
-
option optimize_for = LITE_RUNTIME:表示生成的java代码继承GeneratedMessageLite ,继承GeneratedMessageLite的好处是可张念以用jboss提供的编解码器,可以认为是必写项。
- 该类型中包含的字段格式: [限定符 类型 字段名称 = tag [default = 默认值]]
-
支持的默认类型
.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 |
除此之外,还支持枚举,自定义类型等。
-
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。
-
guide style
Protocol Buffers官方提供了一个Guide Style。其中介绍了一些定义proto的优秀实践。一句话总结就是名称时,message名称以驼峰方式定义,字段名则需要以如author_name方式定义。字段名在生成Java代码时,会自动转换为符合Java风格的命名方式。
-
生成. Java文件
将编写的.Proto文件与下载的protoc.exe文件放在同一指定的目录,进入dos并系统编译命令:protoc XXX.proto --java_out=C:/,这时会在你所指令的目录生成相应的java文件。
-
它的一些缺点
-
不支持大数据集的处理,少于1M
-
不支持Date,Map这些Java内建的对象
-
客户端示例
-
如果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);
-
服务器端示例
-
ServerHandler示例
-
如果使用默认的Protobuf解码方式传输二进制,则接收时按以下代码接收:
if((e.getMessage() instanceof ChannelBuffer)){
ChannelBuffer channelBuffer =(ChannelBuffer)e.getMessage();
byte[] bytes = channelBuffer.array();
}
-
PipelineFactory示例
-
其中,以下两行代码是默认的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));
|