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的傳遞。

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