Protocol Buffers介紹
Protocol Buffers(Protobuf)是Google推出的語言無關,平臺無關的可擴展機制沦泌,用于序列化結(jié)構(gòu)數(shù)據(jù)。由于是一種二進制的序列化協(xié)議餐曹,并且數(shù)據(jù)結(jié)構(gòu)做了相應優(yōu)化摆屯,因此比傳統(tǒng)的XML、JSON數(shù)據(jù)格式序列化后體積更小诗良,傳輸更快汹桦。Protobuf有proto2和proto3兩個版本,有著不同的數(shù)據(jù)格式定義鉴裹,在這里我們使用proto3版本舞骆。Protobuf使用了一個編譯器,可根據(jù).proto
文件中定義的數(shù)據(jù)結(jié)構(gòu)径荔,自動生成各種語言的模板代碼督禽,方便使用。
一個hello world示例程序
我們以一個hello world
的例子來嘗試在Netty中使用Protobuf:編寫一個GreetClient
总处,向服務端發(fā)送用戶姓名name(如world)狈惫;服務端接收到請求,在姓名name前面添加前綴hello(如name為hello鹦马,添加前綴變?yōu)?code>hello word)胧谈,并將該消息message返回給客戶端。這個例子參考了grpc官方的hello world例子荸频。
編寫helloworld.proto文件
首先我們編寫一個helloworld.proto
文件菱肖,內(nèi)容如下:
syntax = "proto3";
option java_multiple_files = true;
option java_package = "com.alphabc.netty.protobuf.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";
package helloworld;
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
編譯生成Java代碼
從Protobuf的官方github release頁面下載對應操作系統(tǒng)上需要用到的編譯器文件,并解壓得到protoc试溯。
為了使用protoc將helloworld.proto
文件編譯成Java代碼蔑滓,進入項目目錄執(zhí)行以下命令:
protoc --java_out=src/main/java src/main/java/com/alphabc/netty/protobuf/helloworld/helloworld.proto
最終生成的類目錄結(jié)構(gòu)大致如下:
src/main/java/com/alphabc/netty/protobuf/helloworld
├── HelloReply.java
├── HelloReplyOrBuilder.java
├── HelloRequest.java
├── HelloRequestOrBuilder.java
├── HelloWorldProto.java
└── helloworld.proto
由于生成的模板代碼較多,此處就不貼出來了。
引入Maven依賴
引入Netty和Protobuf的Maven依賴如下:
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.32.Final</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.11.0</version>
</dependency>
測試Protobuf的序列化和反序列化
編寫Protobuf的序列化和反序列化測試類如下:
package com.alphabc.netty.protobuf;
import com.alphabc.netty.protobuf.helloworld.HelloRequest;
import com.google.protobuf.InvalidProtocolBufferException;
public class ProtobufTest {
public static void main(String[] args) throws InvalidProtocolBufferException {
HelloRequest helloRequest = HelloRequest.newBuilder().setName("Jack").build();
byte[] bytes = helloRequest.toByteArray();
HelloRequest parse = HelloRequest.parseFrom(bytes);
System.out.println(parse.toString());
System.out.println(parse.getName());
}
}
執(zhí)行后控制臺輸出:
name: "Jack"
Jack
編寫服務端和客戶端代碼
編寫Netty服務端GreetServer
代碼:
package com.alphabc.netty.protobuf;
import com.alphabc.netty.protobuf.helloworld.HelloRequest;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
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 java.net.InetSocketAddress;
public class GreetServer {
private final int port;
public GreetServer(int port) {
this.port = port;
}
public static void main(String[] args) throws Exception {
if (args.length != 1) {
System.err.println("Usage: " + GreetServer.class.getSimpleName() + " <port>");
return;
}
int port = Integer.parseInt(args[0]);
new GreetServer(port).start();
}
public void start() throws Exception {
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.localAddress(new InetSocketAddress(port))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new ProtobufVarint32FrameDecoder())
.addLast(new ProtobufDecoder(HelloRequest.getDefaultInstance()))
.addLast(new ProtobufVarint32LengthFieldPrepender())
.addLast(new ProtobufEncoder())
.addLast(new GreetServerHandler());
}
});
ChannelFuture f = b.bind().sync();
System.out.println(GreetServer.class.getName() + " started and listen on " + f.channel().localAddress());
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully().sync();
workerGroup.shutdownGracefully().sync();
}
}
}
服務端處理器GreetServerHandler
代碼如下:
package com.alphabc.netty.protobuf;
import com.alphabc.netty.protobuf.helloworld.HelloReply;
import com.alphabc.netty.protobuf.helloworld.HelloRequest;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
public class GreetServerHandler extends SimpleChannelInboundHandler<HelloRequest> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, HelloRequest msg) throws Exception {
System.out.println("request: " + msg.getName());
HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + msg.getName()).build();
ChannelFuture future = ctx.writeAndFlush(reply);
future.addListener(ChannelFutureListener.CLOSE);
}
}
Netty客戶端代碼:
package com.alphabc.netty.protobuf;
import com.alphabc.netty.protobuf.helloworld.HelloReply;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
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;
import java.net.InetSocketAddress;
public class GreetClient {
private final String host;
private final int port;
public GreetClient(String host, int port) {
this.host = host;
this.port = port;
}
public static void main(String[] args) throws Exception {
if (args.length != 2) {
System.err.println("Usage: " + GreetClient.class.getSimpleName() + " <host> <port>");
return;
}
final String host = args[0];
final int port = Integer.parseInt(args[1]);
new GreetClient(host, port).start();
}
private void start() throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.remoteAddress(new InetSocketAddress(host, port))
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new ProtobufVarint32FrameDecoder())
.addLast(new ProtobufDecoder(HelloReply.getDefaultInstance()))
.addLast(new ProtobufVarint32LengthFieldPrepender())
.addLast(new ProtobufEncoder())
.addLast(new GreetClientHandler());
}
});
ChannelFuture f = b.connect().sync();
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully().sync();
}
}
}
客戶端處理器GreetClientHandler
:
package com.alphabc.netty.protobuf;
import com.alphabc.netty.protobuf.helloworld.HelloReply;
import com.alphabc.netty.protobuf.helloworld.HelloRequest;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
public class GreetClientHandler extends SimpleChannelInboundHandler<HelloReply> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, HelloReply msg) throws Exception {
System.out.println("Greeting: " + msg.getMessage());
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
HelloRequest request = HelloRequest.newBuilder().setName("world").build();
ctx.writeAndFlush(request);
}
}
代碼測試
首先啟動服務端键袱,監(jiān)聽于5000端口燎窘,再啟動客戶端進行測試。
服務端控制臺輸出如下:
com.alphabc.netty.protobuf.GreetServer started and listen on /0:0:0:0:0:0:0:0:5000
request: world
客戶端控制臺輸出如下:
Greeting: Hello world
總結(jié)
從hello world
示例程序可以看出蹄咖,Netty已經(jīng)為我們內(nèi)置相應的Protobuf編解碼器褐健,編寫一個使用Protobuf協(xié)議的服務端和客戶端還是相對簡單的,但是要寫好還是有很多事情要處理澜汤,如異常處理和支持多個Protobuf消息等等問題蚜迅。
參考資料
- GitHub - protocolbuffers/protobuf: Protocol Buffers - Google's data interchange format
- Protocol Buffers | Google Developers
- Language Guide (proto3) | Protocol Buffers | Google Developers
- Protocol Buffer Basics: Java | Protocol Buffers | Google Developers
- grpc-java/examples/src/main/java/io/grpc/examples/helloworld at master · grpc/grpc-java