Netty中使用protobuf
下载protoc代码生成器和源码包:http://code.google.com/p/protobuf/downloads/list,
参考http://blog.csdn.net/dreamdoc/article/details/6577180相关代码
格式参考如下:
注:可以按如上内容编写文本文件,然后修改后缀为.Proto。
从上面的代码可以看出,proto文件的结构非常简单。
.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 |
除此之外,还支持枚举,自定义类型等。
由于一些历史原因,基本数值类型的repeated的字段并没有被尽可能地高效编码。在新的代码中,用户应该使用特殊选项[packed=true]来保证更高效的编码。如:repeated int32 samples = 4 [packed=true];
required是永久性的:在将一个字段标识为required的时候,应该特别小心。如果在某些情况下不想写入或者发送一个required的字段,将原始该字段修饰符更改为optional可能会遇到问题——旧版本的使用者会认为不含该字段的消息是不完整的,从而可能会无目的的拒绝解析。在这种情况下,你应该考虑编写特别针对于应用程序的、自定义的消息校验函数。Google的一些工程师得出了一个结论:使用required弊多于利;他们更愿意使用optional和repeated而不是required。当然,这个观点并不具有普遍性。
Protocol Buffers官方提供了一个Guide Style。其中介绍了一些定义proto的优秀实践。一句话总结就是名称时,message名称以驼峰方式定义,字段名则需要以如author_name方式定义。字段名在生成Java代码时,会自动转换为符合Java风格的命名方式。
将编写的.Proto文件与下载的protoc.exe文件放在同一指定的目录,进入dos并系统编译命令:protoc XXX.proto --java_out=C:/,这时会在你所指令的目录生成相应的java文件。
byte[] test = "asdfa".getBytes();
ChannelBuffer channelBuffer = ChannelBuffers.buffer(test.length);
// 将 获得到的数组写入 channelBuffer中
channelBuffer.writeBytes(test);
// 发送到服务器端
ChannelFuture lastWriteFuture = channel.write(channelBuffer);
if((e.getMessage() instanceof ChannelBuffer)){
ChannelBuffer channelBuffer =(ChannelBuffer)e.getMessage();
byte[] bytes = channelBuffer.array();
}
pipeline.addLast("frameDecoder", new ProtobufVarint32FrameDecoder());
pipeline.addLast("frameEncoder", new ProtobufVarint32LengthFieldPrepender());
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));
|