編碼與解碼
下面的文字都來自于極客時間
為什么要編解碼呢?因為計算機數據傳輸的是二進制的字節數據
解碼:字節數據 --> 字符串(字符數據)
編碼:字符串(字符數據)–> 字節數據
我們在編寫網絡應用程序的時候需要注意 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>() {@Overrideprotected 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>() {@Overrideprotected 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 {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {ByteBuf buf=(ByteBuf) msg;System.out.println("client msg:"+buf.toString(CharsetUtil.UTF_8));}@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {BookMessage.Book book =BookMessage.Book.newBuilder().setId(1).setName("beyound").build();ctx.writeAndFlush(book);}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {ctx.close();}
}
public class NettyServerHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {BookMessage.Book book=(BookMessage.Book)msg;System.out.println("receive book msg:"+ book.getName());}@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) throws Exception {ctx.writeAndFlush(Unpooled.copiedBuffer("response", Charset.defaultCharset()));}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {ctx.close();}
}