Polymophic messages with Jboss’s Netty and Google’s Protocol Buffers.

First I want to give credit where due to: http://www.indelible.org/ink/protobuf-polymorphism/

I recently restarted working on my client/server architecture for Dunya. I decided to use protobuf to handle message generation since ideally my clients will be open and written in either C++, Java, or [your language here...].

I wanted an Abstract message type that could be extended by more specific types. For more detailed information on how to get this working in protobuf see the link above, or just look at my .proto file below. Now I will show you how to properly setup your pipeline to decode protobuf messages, how to construct and send the polymorphic message, and how to go about handling it in your Channel Handler. This is a great improvement over my previous implementation, because it is now easier to use cross language.
Protobuf definition

 
package request;

option java_package = "com.dunyaonline.communication.messages.requests";
option java_outer_classname = "AbstractRequestProtos";

message AbstractRequest {
	extensions 100 to 115;
	enum Type {
		NewCharRequest = 0;
		CharListRequest = 1;
	}
	required Type type = 1;
}

message CharListRequest {
	extend AbstractRequest {
		required CharListRequest request = 100;
	}
	required int32 userid = 1;
	required int32 world = 2;
}

message NewCharRequest {
        extend AbstractRequest{
		required NewCharRequest request = 101;
	}
	required string name = 1;
	required int32 userid = 2;
}

Client Message Initialization and Send

public void getCharacterList() {
	AbstractRequestProtos.CharListRequest clr = AbstractRequestProtos.CharListRequest
			.newBuilder().setUserid(12345).setWorld(6789).build();
	AbstractRequest ar = AbstractRequest
			.newBuilder()
			.setType(AbstractRequest.Type.CharListRequest)
			.setExtension(AbstractRequestProtos.CharListRequest.request,
					clr).build();
	cmService.sendMessage(ar);
}

public ChannelFuture sendMessage(Object obj) {
		return channel.write(obj);
}

Server Network Initialization

	private void initNetwork() {
		ChannelFactory factory = new NioServerSocketChannelFactory(
				Executors.newCachedThreadPool(),
				Executors.newCachedThreadPool());

		ServerBootstrap bootstrap = new ServerBootstrap(factory);
		bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
			public ChannelPipeline getPipeline() throws Exception {
				ChannelPipeline pipeline = Channels.pipeline();
				
				// Decoder
				pipeline.addLast("frameDecoder",
						new ProtobufVarint32FrameDecoder());

				pipeline.addLast(
						"ProtoCharacterListRequestDecoder",
						new ProtobufDecoder(
								AbstractRequestProtos.AbstractRequest
										.getDefaultInstance()));
				// Encoder
				pipeline.addLast("frameEncoder",
						new ProtobufVarint32LengthFieldPrepender());
				pipeline.addLast("protobufEncoder", new ProtobufEncoder());
				pipeline.addLast("handler", new ServerMessageHandler());
				return pipeline;
			}
		});

		bootstrap.setOption("child.tcpNoDelay", true);
		bootstrap.setOption("child.keepAlive", true);
		bootstrap.bind(new InetSocketAddress(PORT));
	}
}

Handler

public class ServerMessageHandler extends SimpleChannelHandler {

	private Logger LOGGER = Logger.getLogger(ServerMessageHandler.class);
	
	@Override
	public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
			throws Exception {
		super.messageReceived(ctx, e);
		AbstractRequest r = (AbstractRequest) e.getMessage();
		if(r.getType().getNumber() == AbstractRequest.Type.NewCharRequest_VALUE)
		{
			LOGGER.info(NewCharacterRequest.class.getName() + ": " + e.getMessage());
			
		}else if(r.getType().getNumber() == AbstractRequest.Type.CharListRequest_VALUE)
		{
			LOGGER.info(CharacterListRequest.class.getName() + ": " + e.getMessage());
                        ByteString str = r.getUnknownFields()
					.getField(CharListRequest.REQUEST_FIELD_NUMBER)
					.getLengthDelimitedList().get(0);
			CharListRequest clr = CharListRequest.parseFrom(str);
			
                        // Now you have an instance of CharListRequest and can invoke its getters:
                        getPlayerCharacters(clr.getUserid(), clr.getWorld());
		}else{
			LOGGER.info(ctx.getChannel().getRemoteAddress() + ": " + e.getMessage());
		}
	}
}

Leave a Comment

You must be logged in to post a comment.

14 Trackbacks \ Pings »