Google的Protobuf在业界非常流行,很多商业项目都选择Protobuf作为编解码框架,以下为Protobuf的一些优点:
(1)在谷歌内长期使用,产品成熟度高。
(2)跨语言,支持包括C++、Java、Python在内的多重语言。
(3)编码后的码流小,便于存储和传输。
(4)编解码性能高。
(5)支持不同协议向前兼容。
(6)支持定义可选和必选字段。
一、Protobuf开发环境搭建
1、下载Protobuf的Windows版,网址如下:https://developers.google.com/protocol-buffers/docs/downloads?hl=zh-cn,本示例基于protoc-2.6.1-win32.zip
2、下载Protobuf Java语言所需的jar包,网址如下:http://repo2.maven.org/maven2/com/google/protobuf/protobuf-java/2.6.1/,本示例基于protobuf-java-2.6.1.jar。
3、新建请求响应所需的proto文件
SubscribeReq.proto
package netty; option java_package = "com.serial.java.protobuf"; option java_outer_classname = "SubscribeReqProto"; message SubscribeReq{ required int32 subReqID = 1; required string userName = 2; required string productName = 3; repeated string address = 4; }
SubscribeRespProto.proto
package netty; option java_package = "com.serial.java.protobuf"; option java_outer_classname = "SubscribeRespProto"; message SubscribeResp{ required int32 subReqID = 1; required string respCode = 2; required string desc = 3; }
4、通过Protoc.exe生成所需的Java编解码POJO文件,命令行如下。
C:\Users\Administrator>d: D:\>cd "Program Files\protoc-2.6.1-win32" D:\Program Files\protoc-2.6.1-win32>protoc.exe --java_out=.\src .\netty\Subscrib eReq.proto D:\Program Files\protoc-2.6.1-win32>protoc.exe --java_out=.\src .\netty\Subscrib eResp.proto D:\Program Files\protoc-2.6.1-win32>
5、将生成的Java POJO文件拷贝到项目中,注意Protobuf所需的jar包也需包含在项目中,不然会报错。
6、创建测试类,测试Protobuf的编解码功能。
TestSubscribeReq.java
package com.serial.java.test;
import java.util.ArrayList;
import java.util.List;
import com.google.protobuf.InvalidProtocolBufferException;
import com.serial.java.protobuf.SubscribeReqProto;
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("leeka");
builder.setProductName("Netty book");
List<String> address = new ArrayList<String>();
address.add("Nanjing");
address.add("Beijing");
address.add("Hangzhou");
builder.addAllAddress(address);
return builder.build();
}
public static void main(String[] args)throws Exception {
SubscribeReqProto.SubscribeReq req = createSubscribeReq();
System.out.println("before encode:"+ req.toString());
SubscribeReqProto.SubscribeReq req2 = decode(encode(req));
System.out.println("after encode:"+ req2.toString());
System.out.println("Assert equal: " + req2.equals(req));
}
}
7、运行测试类,查看测试结果,控制台输出如下信息:
before encode:subReqID: 1 userName: "leeka" productName: "Netty book" address: "Nanjing" address: "Beijing" address: "Hangzhou" after encode:subReqID: 1 userName: "leeka" productName: "Netty book" address: "Nanjing" address: "Beijing" address: "Hangzhou" Assert equal: true
二、Netty的Protobuf服务端和客户端开发
服务端入口
package com.serial.java;
import com.serial.java.protobuf.SubscribeReqProto;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
public class SubReqServer {
public void bind(int port)throws Exception{
//配置服务端NIO线程组
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try{
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline()
.addLast(new ProtobufVarint32FrameDecoder())
.addLast(new ProtobufDecoder(
SubscribeReqProto.SubscribeReq.getDefaultInstance()))
.addLast(new ProtobufVarint32LengthFieldPrepender())
.addLast(new ProtobufEncoder())
.addLast(new SubReqServerHandler());
}
});
//绑定端口,同步等待成功
ChannelFuture f = b.bind(port).sync();
//等待服务端监听端口关闭
f.channel().closeFuture().sync();
}finally{
//退出时释放资源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception{
int port = 8085;
if(args!=null && args.length > 0){
port = Integer.valueOf(args[0]);
}
new SubReqServer().bind(port);
}
}
服务端处理类
package com.serial.java;
import com.serial.java.protobuf.SubscribeReqProto;
import com.serial.java.protobuf.SubscribeRespProto;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
public class SubReqServerHandler extends ChannelHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
SubscribeReqProto.SubscribeReq req = (SubscribeReqProto.SubscribeReq)msg;
//System.out.println("SubReqServerHandler channelRead:"+ req.getUserName());
if("leeka".equalsIgnoreCase(req.getUserName())){
System.out.println("service accept client subscribe req:["+ req +"]");
ctx.writeAndFlush(resp(req.getSubReqID()));
}
}
private SubscribeRespProto.SubscribeResp resp(int subReqID){
SubscribeRespProto.SubscribeResp.Builder builder = SubscribeRespProto.SubscribeResp.newBuilder();
builder.setSubReqID(subReqID);
builder.setRespCode("0");
builder.setDesc("Netty book order succeed, 3 days later, sent to the designated address");
return builder.build();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
cause.printStackTrace();
ctx.close();
}
}
客户端入口
package com.serial.java;
import com.serial.java.protobuf.SubscribeRespProto;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;
public class SubReqClient {
public void connect(int port,String host)throws Exception{
//配置客户端NIO线程组
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())
.addLast(new ProtobufDecoder(
SubscribeRespProto.SubscribeResp.getDefaultInstance()))
.addLast(new ProtobufVarint32LengthFieldPrepender())
.addLast(new ProtobufEncoder())
.addLast(new SubReqClientHandler());
};
});
//发起异步连接操作
ChannelFuture f = b.connect(host,port).sync();
//等待客户端链路关闭
f.channel().closeFuture().sync();
}finally{
//退出,释放资源
group.shutdownGracefully();
}
}
public static void main(String[] args)throws Exception {
int port = 8085;
if(args!=null && args.length > 0){
port = Integer.valueOf(args[0]);
}
new SubReqClient().connect(port, "127.0.0.1");
}
}
客户端处理类
package com.serial.java;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import com.serial.java.protobuf.SubscribeReqProto;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
public class SubReqClientHandler extends ChannelHandlerAdapter {
private static final Logger logger = Logger.getLogger(SubReqClientHandler.class.getName());
public SubReqClientHandler() {
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
for (int i = 0; i < 10; i++) {
ctx.write(req(i));
}
ctx.flush();
}
private SubscribeReqProto.SubscribeReq req(int i){
SubscribeReqProto.SubscribeReq.Builder r = SubscribeReqProto.SubscribeReq.newBuilder();
r.setSubReqID(i);
r.setProductName("Netty Book"+i);
r.setUserName("leeka");
List<String> address = new ArrayList<String>();
address.add("Nanjing");
address.add("Beijing");
r.addAllAddress(address);
return r.build();
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//super.channelReadComplete(ctx);
ctx.flush();
}
@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 {
logger.warning("unexpected exception from downstream:"+ cause.getMessage());
ctx.close();
}
}
OVER