编码与解码
下面的文字都来自于极客时间
为什么要编解码呢?因为计算机数据传输的是二进制的字节数据
解码:字节数据 --> 字符串(字符数据)
编码:字符串(字符数据)–> 字节数据
我们在编写网络应用程序的时候需要注意 codec (编解码器),因为数据在网络中传输的都是二进制字节
码数据,而我们拿到的目标数据往往不是字节码数据。因此在发送数据时就 需要编码,接收数据时就需
要解码。
codec 的组成部分有两个:decoder(解码器)和 encoder(编码器)。
- encoder 负责把业务数据转换成字节码数据
- decoder 负责把字节码数据转换成业务数据
其实 Java 的序列化技术就可以作为 codec 去使用,但是它的硬伤太多:
- 无法跨语言,这应该是 Java 序列化最致命的问题了
- 序列化后的体积太大,是二进制编码的 5 倍多
- 序列化性能太低
Netty 自身提供了一些 编解码器,如下:
- StringEncoder对字符串数据进行编码
- ObjectEncoder对 Java 对象进行编码
Netty 本身自带的 ObjectDecoder 和 ObjectEncoder 可以用来实现 POJO 对象或各种业务对象的编码和解码,但其内部使用的仍是 Java 序列化技术,所以在某些场景下不适用。对于 POJO 对象或各种业务对象要实现编码和解码,我们需要更高效更强的技术。由此引出:Google 的 Protobuf。
Google 的 Protobuf
Protobuf 是 Google 发布的开源项目,全称 Google Protocol Buffers,特点如下:支持跨平台、多语言(支持目前大多数语言,例如 C++、C#、Java、python 等)高性能,高可靠性。
使用 protobuf 编译器能自动生成代码,Protobuf 是将类的定义使用.proto 文件进行描述,然后通过 protoc.exe 编译器根据.proto 自动成.java 文件在使用 Netty 开发时,经常会结合 Protobuf 作为 codec (编解码器)去使用,具体用法如下所示。
使用步骤:
- 第一步:将传递数据的实体类生成【基于构建者模式设计】
- 第二步:配置编解码器
- 第三步:传递数据使用生成后的实体类
使用案例
1 引入依赖
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.21.11</version>
</dependency>
定义protoc文件,是生成具体类的描述:
语法规则:https://www.topgoer.com/%E5%BE%AE%E6%9C%8D%E5%8A%A1/Protobuf%E8%AF%AD%E6%B3%95.html
syntax = "proto3";
option java_outer_classname = "BookMessageDB";
message Book{
int32 id = 1;
string name = 2;
}
2 安装插件
GenProtobuf是生产插件。Protobuf是一定要安装的。
配置GenProtobuf:
做如下配置:
特别注意:这个版本要和你引入的依赖版本一致,否则会出先有些方法或类找不到报红的情况
public class AIOServer {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// 解码
ch.pipeline().addLast("decoder", new ProtobufDecoder(BookMessage.Book.getDefaultInstance()));
ch.pipeline().addLast(new NettyServerHandler());
}
});
System.out.println("============服务器启动");
b.bind(9999).sync();
// bossGroup.shutdownGracefully();
// workGroup.shutdownGracefully();
}
}
public class AIOClient {
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// 设置编码器
ch.pipeline().addLast("encoder", new ProtobufEncoder());
ch.pipeline().addLast(new NettyClientHandler());
}
});
System.out.println();
ChannelFuture connect = bootstrap.connect(new InetSocketAddress(9999));
connect.channel().closeFuture().sync();
}
}
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf=(ByteBuf) msg;
System.out.println("client msg:"+buf.toString(CharsetUtil.UTF_8));
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
BookMessage.Book book =
BookMessage.Book.newBuilder().setId(1).setName("beyound").build();
ctx.writeAndFlush(book);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
BookMessage.Book book=(BookMessage.Book)msg;
System.out.println("receive book msg:"+ book.getName());
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(Unpooled.copiedBuffer("response", Charset.defaultCharset()));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}