netty整合protocol buffer

netty整合protocol buffer

例子

其实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 0001000 0001  ++  001 0110 (drop the msb and reverse the groups of 7 bits)10010110128 + 16 + 4 + 2 = 150

首先写出9601的二进制数,然后舍弃最高位,然后两个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中包含了PersonAddressBook,到时候传输,我们就传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的传递。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章