Netty(7)PB

一、PB介绍

1.1 为甚需要PB ,它解决了什么问题?

网络上传输的数据都是二进制字节数据,说白了,就是一串的0 和 1,往硬件层面上说,是 高低电压(OR 光)脉冲。

从Java 对象装换成 二进制字节数据,涉及到一个编码格式的问题。最熟悉的场景是我们将一个Java字符串 ,通过UTF-8 转换为一个二进制字节数组传输,但除了 使用UTF-8编码(解码亦然),还有没有其他方式呢?

Netty 自身也提供了一些codec,比如

  • StringEncoder (StringDecoder) :对字符串编解码
  • ObjectEncoder(ObjectDecoder):对Java对象编解码
  • 等等

Netty自带的 ObjectEncoder(ObjectDecoder) 可以用来实现POJO对象或各种业务对象的编解码,本质上还是Java序列化技术,java 序列化本身效率不高,而且还有一些硬伤:

  • 不能跨语言
  • 序列化后的体积大,是二进制编码的 5 倍+
  • 序列化性能低

PB的出现就是解决这些硬伤的:
PB 是一种结构化数据存储格式,适合结构化数据串行化,或者说 序列化,适合RPC和存储。现在很多公司,都从HTTP+JSON–> RPC+PB

1.2 如何使用PB

使用示意图:
在这里插入图片描述
使用前,最好阅读下 PB的文档:

1.3 demo

案例1:如何发一种数据类型

使用 PB编解码,客户端Student对象发个简单消息给服务端,服务端能接收,能正常通信即可;

// PB描述文件内容
syntax="proto3";
option java_outer_classname="StudentPOJO"; //生成外部类名、同时也是类文件名
//PB使用 message 管理数据
message Student{ //会在 StudentPOJO (外部类)生成一个内部类 Student --> 即真正的POJO对象
    int32 id=1; //Student中一个属性,名字就是 id ,类型是 Int32 对应到Java的 int
    // 这里的 1  注意不是值,而是序号!!!

    string name=2;
}


//客户端 核心代码也就一个handler
bootstrap.group(group)
		.channel(NioSocketChannel.class)
		.handler(new ChannelInitializer<SocketChannel>() {
	@Override
	protected void initChannel(SocketChannel ch) throws Exception {
		// 注意: 这个 编码也是在第一行;并不是在最后一行
		ch.pipeline().addLast("pbEncoder", new ProtobufEncoder());
		ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
			@Override
			public void channelActive(ChannelHandlerContext ctx) throws Exception {
				StudentPOJO.Student student = StudentPOJO.Student.newBuilder().setName("justinwins")
						.setId(1024).build();
				ctx.writeAndFlush(student);

			}
		});
	}
});
//服务端 核心代码也就一个handler
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap
.group(boss, worker)
.option(ChannelOption.SO_BACKLOG, 128)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
		protected void initChannel(SocketChannel ch) throws Exception {
			// 注意:解码器的添加需要指定是哪种类型
			ch.pipeline().addLast("pbDecoder",
					new ProtobufDecoder(StudentPOJO.Student.getDefaultInstance()));
			ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
				@Override
				public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
					StudentPOJO.Student student = (StudentPOJO.Student) msg;
					System.out.println("收到客户端的数据id " + student.getId() + ",name " + student.getName());
				}
			});
		}
	}).childOption(ChannelOption.SO_KEEPALIVE, true);

案例2:如何发很多种数据类型

上面的案例中我们只能收发一种数据类型,实际生产过程中,肯定不止使用一种,难道每个POJO都大张旗鼓地搞个proto文件?
这就要从 .proto的高级语法说起了。

举个需求:客户端随机发送Boss OR Worker 这两个实例中的一个;要求服务端能够接收到

// .proto文件如下
syntax="proto3";
option optimize_for=SPEED;//加快解析速度
option java_outer_classname="MyDataInfo";
message MyMessage{
// 定义一个枚举类型
    enum DataType{
        BossType=0; // 枚举类型是从 0 开始编号的
        WorkerType=1;
    }

// 用data_type来标识传的是哪一个枚举。注意,这里的 =1 并不是指定为了WorkerType,
// 它只是很纯粹的表示顺序而已
    DataType data_type=1;

// oneof 表示每次枚举类型只能出现其中一个。 dataBody 就是随意起一个名字而已。
    oneof dataBody{
     Boss boss=2;
     Worker worker=3;
    }
}
message Boss{
    int32 id=1;
    string name=2;
}
message Worker{
    int32 age=1;
    string name=2;
}
// 核心server 代码如下
bootstrap.group(boss, worker).option(ChannelOption.SO_BACKLOG, 128).channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
	protected void initChannel(SocketChannel ch) throws Exception {
		// 注意:解码器的添加需要指定是哪种类型
		ch.pipeline().addLast("pbDecoder",
				new ProtobufDecoder(MyDataInfo.MyMessage.getDefaultInstance()));

		// 使用 SimpleChannelInboundHandler ,省去了强转的麻烦
		ch.pipeline().addLast(new SimpleChannelInboundHandler<MyDataInfo.MyMessage>() {
			@Override
			protected void channelRead0(ChannelHandlerContext ctx, MyDataInfo.MyMessage msg)
					throws Exception {
				if (msg.getDataType().equals(MyDataInfo.MyMessage.DataType.BossType)) {
					MyDataInfo.Boss msgBoss = msg.getBoss();
					System.out.println("接收到Boss:" + msgBoss.getName());
				} else {
					MyDataInfo.Worker msgWorker = msg.getWorker();
					System.out.println("接收到Worker:" + msgWorker.getName());
				}
			}
		});
	}
}).childOption(ChannelOption.SO_KEEPALIVE, true);

// 核心client代码如下
bootstrap.group(group)
	.channel(NioSocketChannel.class)
		.handler(new ChannelInitializer<SocketChannel>() {
	@Override
	protected void initChannel(SocketChannel ch) throws Exception {
		// 注意: 这个 编码也是在第一行;并不是在最后一行
		ch.pipeline().addLast("pbEncoder", new ProtobufEncoder());
		ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
			@Override
			public void channelActive(ChannelHandlerContext ctx) throws Exception {
				//随机发送一个 Boss OR Worker 实例
				int index = (int)(Math.random() * 10) % 2;
				if (index == 0) {
					MyDataInfo.MyMessage bossB = MyDataInfo.MyMessage.newBuilder()
							.setDataType(MyDataInfo.MyMessage.DataType.BossType)
							.setBoss(MyDataInfo.Boss.newBuilder().setId(1024).setName("bossB").build())
							.build();
					ctx.writeAndFlush(bossB);
				} else {
					MyDataInfo.MyMessage.Builder workerW = MyDataInfo.MyMessage.newBuilder()
							.setDataType(MyDataInfo.MyMessage.DataType.WorkerType).setWorker(
									MyDataInfo.Worker.newBuilder().setAge(10).setName("workerW").build());
					ctx.writeAndFlush(workerW);
				}

			}
		});
	}
});

1.4 遗留问题

PB 使用 message 来管理数据,如何理解?

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值