例子
其实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("linlin@163.com")
.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("linlin@163.com")
.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("Gin@qq.com").
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的传递。