例子
其實netty去整合protobuf很簡單,只是我們需要新的編解碼器。
server:
public class TestProtobufServer {
public static void main(String[] args) {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try{
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new ProtobufVarint32FrameDecoder());
pipeline.addLast(new ProtobufDecoder(AddressBookProtos.AddressBook.getDefaultInstance()));
pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());
pipeline.addLast(new ProtobufEncoder());
pipeline.addLast(new MyTestProtobufServerHandler());
}
});
ChannelFuture channelFuture = serverBootstrap.bind("localhost", 9999).sync();
channelFuture.channel().closeFuture().sync();
}
catch (InterruptedException e) {
e.printStackTrace();
}
finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
其中,
pipeline.addLast(new ProtobufDecoder(AddressBookProtos.AddressBook.getDefaultInstance()));
規定了傳輸的對象。
那與protobuf有關的encoder和decoder是如何工作的呢?
我可以舉個簡單的例子(因爲具體的情況多樣且複雜)。
再寫一個testencode.proto
:
syntax = "proto2";
package protobufencode;
option java_package = "com.ocean.protobufencode";
option java_outer_classname = "TestEncode";
message Test1{
optional int32 a = 1;
}
編譯好。
測試:
public class MyEncodeTest {
public static void main(String[] args) throws Exception{
FileOutputStream out = new FileOutputStream("/Users/xxx/xxx/testencode");
TestEncode.Test1.newBuilder().setA(150).build().writeTo(out);
}
}
我把值設置成150並且輸出到一個文件中,我用hex fiend打開該文件(你也可以用其他能查看二進制和十六進制的工具):
089601
爲什麼150變成了089601?
將這串數分成三段:
08 96 01
08表示key,後面的值表示value。
我們只需關注
96 01
96 01 = 1001 0110 0000 0001
→ 000 0001 ++ 001 0110 (drop the msb and reverse the groups of 7 bits)
→ 10010110
→ 128 + 16 + 4 + 2 = 150
首先寫出96
和01
的二進制數,然後捨棄最高位,然後兩個7位數交換位置,最後拼接,++
表示左右拼接。
繼續我們的server。
server端的handler,就是打印address book中的所有person:
public class MyTestProtobufServerHandler extends SimpleChannelInboundHandler<AddressBookProtos.AddressBook> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, AddressBookProtos.AddressBook msg) throws Exception {
List<AddressBookProtos.Person> peopleList = msg.getPeopleList();
peopleList.forEach(person -> {
System.out.println("person id: " + person.getId());
System.out.println("person name: " + person.getName());
System.out.println("person email: " + person.getEmail());
List<AddressBookProtos.Person.PhoneNumber> phonesList = person.getPhonesList();
phonesList.forEach(phoneNumber -> {
switch (phoneNumber.getType()) {
case HOME:
System.out.println("home phone : #" + phoneNumber.getNumber());
break;
case WORK:
System.out.println("work phone : #" + phoneNumber.getNumber());
break;
case MOBILE:
System.out.println("mobile phone : #" + phoneNumber.getNumber());
break;
}
});
});
}
}
client的主類也一樣,就是增加protobuf的編解碼器:
public class MyTestProtobufClient {
public static void main(String[] args) {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new ProtobufVarint32FrameDecoder());
pipeline.addLast(new ProtobufDecoder(AddressBookProtos.AddressBook.getDefaultInstance()));
pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());
pipeline.addLast(new ProtobufEncoder());
pipeline.addLast(new MyTestProtobufClientHandler());
}
});
ChannelFuture channelFuture = bootstrap.connect("localhost", 9999).sync();
channelFuture.channel().closeFuture().sync();
}
catch (InterruptedException e) {
e.printStackTrace();
}
finally {
eventLoopGroup.shutdownGracefully();
}
}
}
client的handler就是要在連接一建立時發送數據:
public class MyTestProtobufClientHandler extends SimpleChannelInboundHandler<AddressBookProtos.AddressBook> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, AddressBookProtos.AddressBook msg) throws Exception {
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
AddressBookProtos.Person linlin = AddressBookProtos.Person.newBuilder().setId(2)
.setName("Linlin")
.setEmail("[email protected]")
.addPhones(AddressBookProtos.Person.PhoneNumber.newBuilder()
.setNumber("1567838281").
setType(AddressBookProtos.Person.PhoneType.MOBILE))
.addPhones(AddressBookProtos.Person.PhoneNumber.newBuilder()
.setNumber("1892939493").
setType(AddressBookProtos.Person.PhoneType.HOME)).build();
AddressBookProtos.AddressBook addressBook = AddressBookProtos.AddressBook.newBuilder().addPeople(linlin).build();
ctx.writeAndFlush(addressBook);
}
}
問題及解決
現在的侷限在於,難道只能傳AddressBookProtos.AddressBook
嗎?我要是隻想傳Person
怎麼辦?
這時候,我們需要一個更外層的類來包裹addressbook和person:
syntax = "proto2";
package protobuftest;
option java_package = "com.ocean.protobuftest";
option java_outer_classname = "MyMessageInfo";
message MyMessage{
enum DataType{
Person = 1;
AddressBook = 2;
}
required DataType data_type = 1;
oneof DataBody{
Person person = 2;
AddressBook address_book = 3;
}
}
message Person {
required string name = 1;
required int32 id = 2;
optional string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
required string number = 1;
optional PhoneType type = 2 [default = HOME];
}
repeated PhoneNumber phones = 4;
}
message AddressBook {
repeated Person people = 1;
}
MyMessage
中包含了Person
和AddressBook
,到時候傳輸,我們就傳MyMessage
:
serverBootstrap.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new ProtobufVarint32FrameDecoder());
pipeline.addLast(new ProtobufDecoder(MyMessageInfo.MyMessage.getDefaultInstance()));
pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());
pipeline.addLast(new ProtobufEncoder());
pipeline.addLast(new MyTestProtobufServerHandler());
}
});
client端也一樣,將decoder的入參對象改了:
bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new ProtobufVarint32FrameDecoder());
pipeline.addLast(new ProtobufDecoder(MyMessageInfo.MyMessage.getDefaultInstance()));
pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());
pipeline.addLast(new ProtobufEncoder());
pipeline.addLast(new MyTestProtobufClientHandler());
}
});
在client handler裏,我們用隨機數演示既可以傳遞person,又可以傳遞address book:
public class MyTestProtobufClientHandler extends SimpleChannelInboundHandler<MyMessageInfo.MyMessage> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, MyMessageInfo.MyMessage msg) throws Exception {
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
MyMessageInfo.MyMessage myMessage = null;
int rand = new Random().nextInt(2);
//如果是0,就傳address book
if (rand == 0) {
myMessage = MyMessageInfo.MyMessage.newBuilder().
setDataType(MyMessageInfo.MyMessage.DataType.AddressBook)
.setAddressBook(MyMessageInfo.AddressBook.newBuilder().
addPeople(MyMessageInfo.Person.newBuilder()
.setId(2)
.setName("Linlin")
.setEmail("[email protected]")
.addPhones(MyMessageInfo.Person.PhoneNumber.newBuilder()
.setNumber("1567838281").
setType(MyMessageInfo.Person.PhoneType.MOBILE))
.addPhones(MyMessageInfo.Person.PhoneNumber.newBuilder()
.setNumber("1892939493").
setType(MyMessageInfo.Person.PhoneType.HOME)).build())).build();
//如果是1,就傳person
}else if(rand==1){
myMessage = MyMessageInfo.MyMessage.newBuilder().
setDataType(MyMessageInfo.MyMessage.DataType.Person).
setPerson(MyMessageInfo.Person.newBuilder().
setId(100).
setName("Gin").
setEmail("[email protected]").
addPhones(MyMessageInfo.Person.PhoneNumber.newBuilder().
setType(MyMessageInfo.Person.PhoneType.WORK).
setNumber("13467875412")).build()).build();
}
ctx.writeAndFlush(myMessage);
}
}
在server handler裏,我們需要依靠datatype判斷客戶端傳過來的到底是person還是address book:
public class MyTestProtobufServerHandler extends SimpleChannelInboundHandler<MyMessageInfo.MyMessage> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, MyMessageInfo.MyMessage msg) throws Exception {
MyMessageInfo.MyMessage.DataType dataType = msg.getDataType();
if (dataType == MyMessageInfo.MyMessage.DataType.AddressBook) {
MyMessageInfo.AddressBook addressBook = msg.getAddressBook();
List<MyMessageInfo.Person> peopleList = addressBook.getPeopleList();
peopleList.forEach(person -> {
System.out.println("person id: " + person.getId());
System.out.println("person name: " + person.getName());
System.out.println("person email: " + person.getEmail());
List<MyMessageInfo.Person.PhoneNumber> phonesList = person.getPhonesList();
phonesList.forEach(phoneNumber -> {
switch (phoneNumber.getType()) {
case HOME:
System.out.println("home phone : #" + phoneNumber.getNumber());
break;
case WORK:
System.out.println("work phone : #" + phoneNumber.getNumber());
break;
case MOBILE:
System.out.println("mobile phone : #" + phoneNumber.getNumber());
break;
}
});
});
}
else if (dataType == MyMessageInfo.MyMessage.DataType.Person) {
MyMessageInfo.Person person = msg.getPerson();
System.out.println("person id: " + person.getId());
System.out.println("person name: " + person.getName());
System.out.println("person email: " + person.getEmail());
List<MyMessageInfo.Person.PhoneNumber> phonesList = person.getPhonesList();
phonesList.forEach(phoneNumber -> {
switch (phoneNumber.getType()) {
case HOME:
System.out.println("home phone : #" + phoneNumber.getNumber());
break;
case WORK:
System.out.println("work phone : #" + phoneNumber.getNumber());
break;
case MOBILE:
System.out.println("mobile phone : #" + phoneNumber.getNumber());
break;
}
});
}
}
}
這就實現了不同message的傳遞。