Netty使用google protobuf进行编解码

1、编解码技术

   基于java提供的对象输入\输出流ObjectInputStream和ObjectOutputStream,可以直接把java对象作为存储的字节数组写入文件,也可以传输到网络上。基于Jdk的默认序列化机制可以避免操作底层数组,从而提升开发效率。
    java序列化的目的主要有两个:
    网络传输
    对象持久化。
  基于netty的nio网络开发,主要是java对象的网络传输。在远程调用的时候需要将java对象编码为字节数组或是ByteBuffer对象进行传输,当远程服务读取到ByteBuffer对象或者字节数组的时候,需要将其解码为发送时的java对象。
    虽然Java序列化提供了实现,但有以下缺点:
    1、无法跨语言
    2、序列化后码流太大
   目前业界主流的编解码框架如下:
   Google 的Protocol Buffers,它的数据结构以.proto文件进行描述,通过代码生成工具生成对应数据结构的POJO对象和Protobuf相关的方法和属性。
     它的特点如下:
      结构化数据存储格式(xml,json等)
      高效的编码解码性能
      语言无关、平台无关、扩展性好。
      protobuf使用二进制编码,在空间和性能上相对于jdk的序列化有很大的性能优势。
    另外还有Facebook的Thrift、JBoss Marshalling。

2、google Protocol Buffers使用

下载protobuf-2.5.0.tar.gz ,然后进行解压安装,依据 README.txt进行安装

配置好环境变量后,可以执行如下命令输入protoc - - version检查是否安装成功。
其具体的安装可以参考
msg.proto文件内容如下:
option java_package = "zou.domain";
option java_outer_classname = "PersonProbuf";
  
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 phone = 4;
  
  message CountryInfo {
          required string name = 1;
          required string code = 2;
          optional int32 number = 3;
  }
}
  
message AddressBook {
  repeated Person person = 1;
}
上面的描述文件指定了类的路径,依据类的名称PersonProbuf,这里定义了Person类和AddressBook。
然后执行如下命令:
protoc --java_out=./ ./msg.proto 前面是指定输出的路劲,后面是指定要生成代码的文件。此时会在本路劲下生成zou/domain下的类PersonProbuf。其测试如下:
public class PersonProbufTest {

	public static void main(String[] args) throws Exception {
		PersonProbuf.Person p = createPerson();
		System.out.println("before encode " + p.toString());
		PersonProbuf.Person p2 = decode(encode(p));
		System.out.println("after encode " + p2.toString());
		System.out.println("equal ==" + p2.toString().equals(p.toString()));
	}

	private static byte[] encode(PersonProbuf.Person p) {
		return p.toByteArray();
	}

	private static PersonProbuf.Person decode(byte[] body) throws InvalidProtocolBufferException {
		return PersonProbuf.Person.parseFrom(body);
	}

	private static PersonProbuf.Person createPerson() {
		PersonProbuf.Person.Builder builder = PersonProbuf.Person.newBuilder();
		builder.setName("zouxh").setEmail("zouxiaohu87@126.com").setId(10);
		builder.addPhone(PersonProbuf.Person.PhoneNumber.newBuilder().setNumber("2").setType(PersonProbuf.Person.PhoneType.HOME).build());
		return builder.build();
	}
}
里面已经包含了对象转换为字节数字,以及字节数组转换为对象,另外还有对应的构造方法去设置对象。
下面给出通过pb进行Nio的通信的例子:
option java_package = "zou.domain";
option java_outer_classname = "SubscribeReqProto";
  
message SubscribeReq {
  required string userName = 1;
  required int32 subReqID = 2;
  required string productName = 3;
  repeated string address = 4;
}
option java_package = "zou.domain";
option java_outer_classname = "SubscribeRespProto";
  
message SubscribeResp {
  required int32 subReqID= 1;
  required int32  respCode=3;
  required string desc=4;
}
通过pb生成对应的java代码。
Server端代码如下:
public class SubReqServer {
	public static void main(String[] args) throws Exception {
		int port = 8080;
		if (args != null && args.length > 0) {
			try {
				port = Integer.parseInt(args[0]);
			} catch (Exception e) {
			}
		}
		new SubReqServer().bind(port);
	}

	public void bind(int port) throws Exception {
		//配置服务端的nio线程组,
		//EventLoopGroup是一个线程组,它包含了一组nio线程,专门用于网络事件的处理,实际上他们就是Reactor线程组
		//这里创建2个的原因是一个用于服务端接受客户的连接,另一个用于SockentChannel的网络读写。
		EventLoopGroup bossGroup = new NioEventLoopGroup();
		EventLoopGroup workerGroup = new NioEventLoopGroup();
		try {
			ServerBootstrap b = new ServerBootstrap();
			b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 1024).childHandler(new ChildChannelHandler());
			//绑定端口,同步等待成功
			ChannelFuture f = b.bind(port).sync();
			//等待服务端监听端口关闭;
			f.channel().closeFuture().sync();

		} finally {
			bossGroup.shutdownGracefully();
			workerGroup.shutdownGracefully();
		}

	}

	private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
		@Override
		protected void initChannel(SocketChannel ch) throws Exception {
			//arg0.pipeline().addLast(new TimeServerHandler());
			ch.pipeline().addLast(new ProtobufVarint32FrameDecoder());
			ch.pipeline().addLast(new ProtobufDecoder(SubscribeReqProto.SubscribeReq.getDefaultInstance()));
			ch.pipeline().addLast(new ProtobufVarint32LengthFieldPrepender());
			ch.pipeline().addLast(new ProtobufEncoder());
			ch.pipeline().addLast(new SubReqServerHandle());
		}
	}
}

看到这里添加了对应的解码器,ProtobufVarint32FrameDecoder主要是进行读半包的问题,ProtobufDecoder是解码器,其参数指定了是对哪种类型进行解码。
ServerHandler如下:
public class SubReqServerHandle extends ChannelHandlerAdapter {

	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
		SubscribeReqProto.SubscribeReq req = (SubscribeReqProto.SubscribeReq) msg;
		if ("zouxiaohu".equalsIgnoreCase(req.getUserName())) {
			System.out.println("Service accept client subscribe req :  [ " + req.toString() + "]");
			//马上进行发送
			ctx.writeAndFlush(resp(req.getSubReqID()));
		}
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		cause.printStackTrace();
		ctx.close();
	}

	private SubscribeRespProto.SubscribeResp resp(int subReqId) {
		SubscribeRespProto.SubscribeResp.Builder builder = SubscribeRespProto.SubscribeResp.newBuilder();
		builder.setSubReqID(subReqId);
		builder.setRespCode(0);
		builder.setDesc("Netty book order succedd , 3 days later,sent to the designated address");
		return builder.build();

	}

}
Client代码如下:
public class SubReqClient {

	public static void main(String[] args) throws Exception {
		int port = 8080;
		if (args != null && args.length > 0) {
			try {
				port = Integer.parseInt(args[0]);
			} catch (Exception e) {

			}
		}
		new SubReqClient().connet(port, "127.0.0.1");
	}

	public void connet(int port, String host) throws Exception {
		EventLoopGroup group = new NioEventLoopGroup();
		try {
			Bootstrap b = new Bootstrap();
			//客户端禁用nangle算法
			b.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true).handler(new ChannelInitializer<SocketChannel>() {

				@Override
				protected void initChannel(SocketChannel ch) throws Exception {
					ch.pipeline().addLast(new ProtobufVarint32FrameDecoder());
					ch.pipeline().addLast(new ProtobufDecoder(SubscribeRespProto.SubscribeResp.getDefaultInstance()));
					ch.pipeline().addLast(new ProtobufVarint32LengthFieldPrepender());
					ch.pipeline().addLast(new ProtobufEncoder());
					ch.pipeline().addLast(new SubReqClientHandler());

				}
			});
			//发起异步连接操作
			ChannelFuture f = b.connect(host, port).sync();
			//等待客户端关闭
			f.channel().closeFuture().sync();

		} finally {
			group.shutdownGracefully();
		}
	}

}

ClientHandler代码如下:
public class SubReqClientHandler extends ChannelHandlerAdapter {

	public SubReqClientHandler() {

	}

	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		for (int i = 0; i < 10; i++) {
			ctx.write(subReq(i));
		}
		ctx.flush();

	}

	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
		System.out.println("Receive server response : [ " + msg + " ]");
	}

	@Override
	public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
		ctx.flush();
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		cause.printStackTrace();
		ctx.close();
	}

	private SubscribeReqProto.SubscribeReq subReq(int i) {
		SubscribeReqProto.SubscribeReq.Builder builder = SubscribeReqProto.SubscribeReq.newBuilder();
		builder.setSubReqID(i);
		builder.setUserName("zouxiaohu");
		builder.setProductName("Netty Book For protobuf");
		List<String> address = new ArrayList<String>();
		address.add("NanJing YuHuaTai");
		address.add("BeiJing LiuLiChang");
		builder.addAllAddress(address);
		return builder.build();
	}

}








  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值