Google 的 Protobuf 在业界非常流行,是很多商业项目的首选,下面是它的主要优点:
- 在谷歌内部长期使用,产品成熟度高
- 支持多种语言
- 编码后的消息更小,更加有利于存储和传输
- 编解码的性能非常高
- 支持不同协议版本的向前兼容
- 支持定义可选和必选字段
Protobuf 入门
Protobuf 是一个灵活、高效、结构化的数据序列化框架,相比于 XML 等传统的序列化工具,它更小、更快、更简单。
目前已经正在使用的有超过 48,162 种报文格式定义和超过 12,183 个 .proto 文件。他们用于 RPC 系统和持续数据存储系统。
环境准备
1. 安装Protobuf
## windows 下
解压即可,发现有两文件:protoc.exe & readme.txt
## linux 下
tar -xzf protobuf-2.1.0.tar.gz
cd protobuf-2.1.0
./configure --prefix=$INSTALL_DIR
make
make check
make install
2. 生成代码
protoc.exe 工具根据.proto文件生成代码,例如我们这有两个文件:
## SubscribeReq.proto
package netty;
option java_package = "com.miao.server.protobuf";
option java_outer_classname = "SubscribeReqProto";
message SubscribeReq{
required int32 subReqID = 1;
required string userName = 2;
required string productName = 3;
repeated string address = 4;
}
## SubscribeResp.proto
package netty;
option java_package = "com.miao.server.protobuf";
option java_outer_classname = "SubscribeRespProto";
message SubscribeResp{
required int32 subReqID = 1;
required int32 respCode =2;
required string desc = 3;
}
然后通过命令行生成Java代码:
D:\**\** > protoc.exe --java_out=.\src SubscribeReq.ptoto
D:\**\** > protoc.exe --java_out=.\src SubscribeResp.proto
最后在当前目录的src目录下生成了 com\miao\server\protobuf\SubscribeReq.java & SubscribeResp.java
文件
入门小例子
将生成的SubscribeReq.java 拖到项目目录下:
新建主类 TestSubscribeReq.java:
public class TestSubscribeReq
{
private static byte[] encode(SubscribeReqProto.SubscribeReq req){
return req.toByteArray();
}
private static SubscribeReqProto.SubscribeReq decode(byte[] body) throws InvalidProtocolBufferException
{
return SubscribeReqProto.SubscribeReq.parseFrom(body);
}
private static SubscribeReqProto.SubscribeReq createSubscribeReq(){
SubscribeReqProto.SubscribeReq.Builder builder =SubscribeReqProto.SubscribeReq.newBuilder();
builder.setSubReqID(1);
builder.setUserName("miaoqiao");
builder.setProductName("Netty test");
List<String> address=new ArrayList<>();
address.add("Beijing");
address.add("Dalian");
address.add("Meiguo");
address.add("huizhou");
builder.addAllAddress(address);
return builder.build();
}
public static void main(String[] args) throws InvalidProtocolBufferException
{
SubscribeReqProto.SubscribeReq req=createSubscribeReq();
System.out.println("Before encode : "+req.toString());
SubscribeReqProto.SubscribeReq req2=decode(encode(req));
System.out.println("After decode : "+req.toString());
System.out.println("Assert equal : --> "+req2.equals(req));
}
}
运行主类,结果如下:
Before encode : subReqID: 1
userName: "miaoqiao"
productName: "Netty test"
address: "Beijing"
address: "Dalian"
address: "Meiguo"
address: "huizhou"
After decode : subReqID: 1
userName: "miaoqiao"
productName: "Netty test"
address: "Beijing"
address: "Dalian"
address: "Meiguo"
address: "huizhou"
Assert equal : --> true
netty 实例
服务端开发
// 服务端主程序
public class SubReqServer
{
public void bind(int port) throws InterruptedException
{
EventLoopGroup boss=new NioEventLoopGroup();
EventLoopGroup worker=new NioEventLoopGroup();
try
{
ServerBootstrap b=new ServerBootstrap();
b.group(boss, worker)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>()
{
@Override
protected void initChannel(SocketChannel ch) throws Exception
{
// 解码器,用于半包处理
ch.pipeline().addLast(
new ProtobufVarint32FrameDecoder()
);
// Protobuf 解码器,参数说明要解码的目标类
ch.pipeline().addLast(
new ProtobufDecoder(
SubscribeReqProto.SubscribeReq.getDefaultInstance()
)
);
ch.pipeline().addLast(
new ProtobufVarint32LengthFieldPrepender()
);
ch.pipeline().addLast(
new ProtobufEncoder()
);
ch.pipeline().addLast(
new SubReqServerHandler()
);
}
});
ChannelFuture f=b.bind(port).sync();
f.channel().closeFuture().sync();
} finally
{
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
public static void main(String[] args) throws InterruptedException
{
int port = 8005;
new SubReqServer().bind(port);
}
}
// 服务端Handler
@Sharable
public class SubReqServerHandler extends ChannelInboundHandlerAdapter
{
@Override
public void channelRead(ChannelHandlerContext ctx , Object msg) throws Exception
{
SubscribeReqProto.SubscribeReq req= (SubscribeReqProto.SubscribeReq) msg;
if("miaoqiao".equalsIgnoreCase(req.getUserName())){
System.out.println("Server accept client req : ["+req.toString()+"]");
ctx.writeAndFlush(reqp(req.getSubReqID()));
}
}
private SubscribeRespProto.SubscribeResp reqp(int subReqID){
SubscribeRespProto.SubscribeResp.Builder builder =
SubscribeRespProto.SubscribeResp.newBuilder();
builder.setSubReqID(subReqID);
builder.setRespCode(0);
builder.setDesc("Netty book order succeed, 3 days later send to the destination");
return builder.build();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx , Throwable cause) throws Exception
{
super.exceptionCaught(ctx , cause);
}
}
客户端开发
public class SubReqClient
{
public void connect(int port, String host) throws InterruptedException
{
EventLoopGroup group=new NioEventLoopGroup();
try
{
Bootstrap b=new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>()
{
@Override
protected void initChannel(SocketChannel ch) throws Exception
{
ch.pipeline().addLast(
new ProtobufVarint32FrameDecoder()
);
ch.pipeline().addLast(
new ProtobufDecoder(
SubscribeRespProto.SubscribeResp.getDefaultInstance()
)
);
ch.pipeline().addLast(
new ProtobufVarint32LengthFieldPrepender()
);
ch.pipeline().addLast(
new ProtobufEncoder()
);
ch.pipeline().addLast(new SubReqClientHandler());
}
});
ChannelFuture f=b.connect(host, port).sync();
f.channel().closeFuture().sync();
} finally
{
group.shutdownGracefully();
}
}
public static void main(String[] args) throws InterruptedException
{
int port=8005;
String host="127.0.0.1";
new SubReqClient().connect(port, host);
}
}
// 客户端Handler
public class SubReqClientHandler extends ChannelInboundHandlerAdapter
{
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception
{
for (int i = 0; i < 10; i++)
{
ctx.write(subReq(i));
}
ctx.flush();
}
private SubscribeReqProto.SubscribeReq subReq(int i){
SubscribeReqProto.SubscribeReq.Builder builder=
SubscribeReqProto.SubscribeReq.newBuilder();
builder.setSubReqID(i);
builder.setUserName("miaoqiao");
builder.setProductName("Netty Book for Protobuf");
List<String> address=new ArrayList<>();
address.add("beijing");
address.add("dalian");
address.add("shanghai");
builder.addAllAddress(address);
return builder.build();
}
@Override
public void channelRead(ChannelHandlerContext ctx , Object msg) throws Exception
{
System.out.println("Receive server response : {"+msg+"]");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx , Throwable cause) throws Exception
{
cause.printStackTrace();
ctx.flush();
}
}
结果测试
// 服务端 console
Server accept client req : [subReqID: 0
userName: "miaoqiao"
productName: "Netty Book for Protobuf"
address: "beijing"
address: "dalian"
address: "shanghai"
]
Server accept client req : [subReqID: 1
userName: "miaoqiao"
productName: "Netty Book for Protobuf"
address: "beijing"
address: "dalian"
address: "shanghai"
]
Server accept client req : [subReqID: 2
userName: "miaoqiao"
productName: "Netty Book for Protobuf"
address: "beijing"
address: "dalian"
address: "shanghai"
]
Server accept client req : [subReqID: 3
userName: "miaoqiao"
productName: "Netty Book for Protobuf"
address: "beijing"
address: "dalian"
address: "shanghai"
]
Server accept client req : [subReqID: 4
userName: "miaoqiao"
productName: "Netty Book for Protobuf"
address: "beijing"
address: "dalian"
address: "shanghai"
]
......
// 客户端 console
Receive server response : {subReqID: 0
respCode: 0
desc: "Netty book order succeed, 3 days later send to the destination"
]
Receive server response : {subReqID: 1
respCode: 0
desc: "Netty book order succeed, 3 days later send to the destination"
]
Receive server response : {subReqID: 2
respCode: 0
desc: "Netty book order succeed, 3 days later send to the destination"
]
Receive server response : {subReqID: 3
respCode: 0
desc: "Netty book order succeed, 3 days later send to the destination"
]
Receive server response : {subReqID: 4
respCode: 0
desc: "Netty book order succeed, 3 days later send to the destination"
]
......