架構師入門筆記十一 Netty5編解碼
1 基礎知識
1.1 什麼是編解碼技術
編碼(Encode)稱爲序列化(serialization),它將對象序列化爲字節數組,用於網絡傳輸、數據持久化或者其它用途。
解碼(Decode)稱爲反序列化(deserialization),把從網絡、磁盤等讀取的字節數組還原成原始對象(通常是原始對象的拷貝),以方便後續的業務邏輯操作。
進行遠程跨進程服務調用時,需要使用特定的編解碼技術,對需要進行網絡傳輸的對象做編碼或者解碼,以便完成遠程調用。這樣理解:我們通過api調用A平臺的接口,A平臺返回給我們一個Json格數的數據(或者是xml格式數據)。我們再解析Json數據轉換成需要的格式。
1.2 爲什麼要用編解碼技術
因爲java的序列化存在很多缺點,比如
1 無法跨語言(最爲致命的問題,因爲java的序列化是java語言內部的私有協議,其他語言並不支持),
2 序列化後碼流太大(採用二進制編解碼技術要比java原生的序列化技術強),
3 序列化性能太低等
1.3 有那些主流的編解碼框架
1.3.1 google 的 Protobuf
它由谷歌開源而來。它將數據結構以 .proto 文件進行描述,通過代碼生成工具可以生成對應數據結構的POJO對象和Protobuf相關的方法和屬性。
特點如下:
1) 結構化數據存儲格式(XML,JSON等);
2) 高效的編解碼性能;
3) 語言無關、平臺無關、擴展性好;
4) 官方支持Java、C++和Python三種語言。
1.3.2 JBoss Marshalling
JBoss Marshalling是一個Java對象的序列化API包,修正了JDK自帶的序列化包的很多問題,但又保持跟java.io.Serializable接口的兼容;同時增加了一些可調的參數和附加的特性,並且這些參數和特性可通過工廠類進行配置。
相比於傳統的Java序列化機制,它的優點如下:
1) 可插拔的類解析器,提供更加便捷的類加載定製策略,通過一個接口即可實現定製;
2) 可插拔的對象替換技術,不需要通過繼承的方式;
3) 可插拔的預定義類緩存表,可以減小序列化的字節數組長度,提升常用類型的對象序列化性能;
4) 無須實現java.io.Serializable接口,即可實現Java序列化;
5) 通過緩存技術提升對象的序列化性能。
1.3.3 MessagePack 框架
MessagePack是一個高效的二進制序列化格式。像JSON一樣可以在各種語言之間交換數據。但是它比JSON更快、更小(It's like JSON.but fast and small)。
1.3.4 Kyro
Kryo 是一個快速高效的Java對象圖形序列化框架(已經非常成熟了,很多大公司都在用)。主要特點是性能高效和易用。可以用來序列化對象到文件、數據庫或者網絡中。
2 JBoss Marshalling
2.1 Marshalling簡介
JBoss Marshalling 是一個Java 對象序列化包,對 JDK 默認的序列化框架進行了優化,但又保持跟 Java.io.Serializable 接口的兼容,同時增加了一些可調的參數和附件的特性, 這些參數和附加的特性, 這些參數和特性可通過工廠類進行配置。
2.2 代碼事例
本章節不再用byteBuf傳數據,改用實體類交互數據。ReqData是Client端請求實體類,RespData是服務端返回實體類。客戶端向服務端發送兩種數據請求,一種是普通的數據交互,另一種是傳附件。
import java.io.Serializable;
import java.util.Arrays;
/**
* 發起請求的實體類
* step1 序列化 Serializable
*/
public class ReqData implements Serializable {
private Long id;
private String name;
private String requestMsg;
private byte[] attachment; // 傳文件的時候會用到
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getRequestMsg() {
return requestMsg;
}
public void setRequestMsg(String requestMsg) {
this.requestMsg = requestMsg;
}
public byte[] getAttachment() {
return attachment;
}
public void setAttachment(byte[] attachment) {
this.attachment = attachment;
}
@Override
public String toString() {
return "ReqData [id=" + id + ", name=" + name + ", requestMsg="
+ requestMsg + ", attachment=" + Arrays.toString(attachment)
+ "]";
}
}
import java.io.Serializable;
/**
* 返回數據實體類 step1 序列化 Serializable
*/
public class RespData implements Serializable {
private Long id;
private String name;
private String responseMsg;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getResponseMsg() {
return responseMsg;
}
public void setResponseMsg(String responseMsg) {
this.responseMsg = responseMsg;
}
@Override
public String toString() {
return "RespData [id=" + id + ", name=" + name + ", responseMsg="
+ responseMsg + "]";
}
}
核心代碼Marshalling工廠類
import io.netty.handler.codec.marshalling.DefaultMarshallerProvider;
import io.netty.handler.codec.marshalling.DefaultUnmarshallerProvider;
import io.netty.handler.codec.marshalling.MarshallerProvider;
import io.netty.handler.codec.marshalling.MarshallingDecoder;
import io.netty.handler.codec.marshalling.MarshallingEncoder;
import io.netty.handler.codec.marshalling.UnmarshallerProvider;
import org.jboss.marshalling.MarshallerFactory;
import org.jboss.marshalling.Marshalling;
import org.jboss.marshalling.MarshallingConfiguration;
/**
* Marshalling工廠
*/
public final class MarshallingFactory {
private static final String NAME = "serial"; // 指定值,不可隨意修改
private static final int VERSION = 5;
private static final int MAX_OBJECT_SIZE = 1024 * 1024 * 1;
/**
* 創建Jboss Marshalling解碼器MarshallingDecoder
* @return MarshallingDecoder
*/
public static MarshallingDecoder buildMarshallingDecoder() {
// step1 通過工具類 Marshalling,獲取Marshalling實例對象,參數serial 標識創建的是java序列化工廠對象
final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory(NAME);
// step2 初始化Marshalling配置
final MarshallingConfiguration configuration = new MarshallingConfiguration();
// step3 設置Marshalling版本號
configuration.setVersion(VERSION);
// step4 初始化生產者
UnmarshallerProvider provider = new DefaultUnmarshallerProvider(marshallerFactory, configuration);
// step5 通過生產者和單個消息序列化後最大長度構建 Netty的MarshallingDecoder
MarshallingDecoder decoder = new MarshallingDecoder(provider, MAX_OBJECT_SIZE);
return decoder;
}
/**
* 創建Jboss Marshalling編碼器MarshallingEncoder
* @return MarshallingEncoder
*/
public static MarshallingEncoder builMarshallingEncoder() {
final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory(NAME);
final MarshallingConfiguration configuration = new MarshallingConfiguration();
configuration.setVersion(VERSION);
MarshallerProvider provider = new DefaultMarshallerProvider(marshallerFactory, configuration);
MarshallingEncoder encoder = new MarshallingEncoder(provider);
return encoder;
}
}
服務器處理類
import java.io.File;
import java.io.FileOutputStream;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.ReferenceCountUtil;
public class ServerHandler extends ChannelHandlerAdapter{
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("Server.......Server");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
try {
// step1 獲取客戶端傳來的數據
ReqData requestData = (ReqData) msg;
// step2 打印數據
System.out.println("Server : " + requestData.toString());
// step3 設置返回值
RespData responseData = new RespData();
responseData.setId(requestData.getId());
responseData.setName(requestData.getName() + " 不錯哦!");
responseData.setResponseMsg(requestData.getRequestMsg() + " 你很棒棒的!");
// 有附件的情況
if (null != requestData.getAttachment()) {
byte[] attachment = GzipUtils.ungzip(requestData.getAttachment());
String path = System.getProperty("user.dir") + File.separatorChar + "receive" + File.separatorChar + "001.jpg";
FileOutputStream outputStream = new FileOutputStream(path);
outputStream.write(attachment);
outputStream.close();
responseData.setResponseMsg("收到圖片了, 圖片路徑是 : " + path);
}
// step4 把數據返回給客戶端
ctx.writeAndFlush(responseData);
} catch (Exception e) {
e.printStackTrace();
} finally {
ReferenceCountUtil.release(msg);
}
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
服務端Netty啓動代碼
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
public class Server {
private static final int PORT = 8888; // 監聽的端口號
public static void main(String[] args) {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO)) // 設置打印日誌級別,其他知識點上一章節均有介紹,這裏不做重複說明
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(MarshallingFactory.buildMarshallingDecoder()); // 配置解碼器
socketChannel.pipeline().addLast(MarshallingFactory.builMarshallingEncoder()); // 配置編碼器
socketChannel.pipeline().addLast(new ServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture future = bootstrap.bind(PORT).sync();
future.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
客戶端處理類
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.ReferenceCountUtil;
public class ClientHandler extends ChannelHandlerAdapter{
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("Client.......Client");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
try {
// step1 獲取客戶端傳來的數據
RespData respData = (RespData) msg;
// step2 打印數據
System.out.println("Client : " + respData.toString());
} catch (Exception e) {
e.printStackTrace();
} finally {
ReferenceCountUtil.release(msg);
}
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
客戶端啓動服務類
import java.io.File;
import java.io.FileInputStream;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
public class Client {
private static final int PORT = 8888;
private static final String HOST = "127.0.0.1";
public static void main(String[] args) {
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(workerGroup)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(MarshallingFactory.buildMarshallingDecoder()); // 配置編碼器
socketChannel.pipeline().addLast(MarshallingFactory.builMarshallingEncoder()); // 配置解碼器
socketChannel.pipeline().addLast(new ClientHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128);
ChannelFuture future = bootstrap.connect(HOST, PORT).sync();
// 普通請求
ReqData reqData = new ReqData();
reqData.setId(1L);
reqData.setName("ITDragon博客");
reqData.setRequestMsg("這是一篇Netty的編解碼博客!");
future.channel().writeAndFlush(reqData);
// 傳附件
ReqData reqData2 = new ReqData();
reqData2.setId(2L);
reqData2.setName("發送附件案例");
reqData2.setRequestMsg("客戶端給服務端發送一張圖片");
// 指定附件的路徑 System.getProperty("user.dir") --》 當前程序所在目錄 File.separatorChar --》會根據操作系統選擇自動選擇 / 或者 \)
String path = System.getProperty("user.dir") + File.separatorChar + "sources" + File.separatorChar + "001.jpg";
File file = new File(path);
FileInputStream inputStream = new FileInputStream(file);
byte[] data = new byte[inputStream.available()];
inputStream.read(data);
inputStream.close();
reqData2.setAttachment(GzipUtils.gzip(data));
future.channel().writeAndFlush(reqData2);
future.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
workerGroup.shutdownGracefully();
}
}
}
文件壓縮解壓工具類
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
public class GzipUtils {
public static byte[] gzip(byte[] data) throws Exception{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
GZIPOutputStream gzip = new GZIPOutputStream(bos);
gzip.write(data);
gzip.finish();
gzip.close();
byte[] ret = bos.toByteArray();
bos.close();
return ret;
}
public static byte[] ungzip(byte[] data) throws Exception{
ByteArrayInputStream bis = new ByteArrayInputStream(data);
GZIPInputStream gzip = new GZIPInputStream(bis);
byte[] buf = new byte[1024];
int num = -1;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
while((num = gzip.read(buf, 0 , buf.length)) != -1 ){
bos.write(buf, 0, num);
}
gzip.close();
bis.close();
byte[] ret = bos.toByteArray();
bos.flush();
bos.close();
return ret;
}
}
代碼和前幾章節大同小異,這裏就不做過多的描述。執行結果(刪掉了一些太長的打印信息):
[14:16:14] nioEventLoopGroup-0-0 INFO [] [] [io.netty.handler.logging.LoggingHandler] - [id: 0x2a77484b] REGISTERED
[14:16:14] nioEventLoopGroup-0-0 INFO [] [] [io.netty.handler.logging.LoggingHandler] - [id: 0x2a77484b] BIND: 0.0.0.0/0.0.0.0:8888
[14:16:14] nioEventLoopGroup-0-0 INFO [] [] [io.netty.handler.logging.LoggingHandler] - [id: 0x2a77484b, /0:0:0:0:0:0:0:0:8888] ACTIVE
[14:16:20] nioEventLoopGroup-0-2 INFO [] [] [io.netty.handler.logging.LoggingHandler] - [id: 0x2a77484b, /0:0:0:0:0:0:0:0:8888] RECEIVED: [id: 0x5fda9fbc, /127.0.0.1:59193 => /127.0.0.1:8888]
Server.......Server
Server : ReqData [id=1, name=ITDragon博客, requestMsg=這是一篇Netty的編解碼博客!, attachment=null]
Server : ReqData [id=2, name=發送附件案例, requestMsg=客戶端給服務端發送一張圖片, attachment=[....]]
[14:16:20] main WARN [] [] [io.netty.bootstrap.Bootstrap] - Unknown channel option: SO_BACKLOG=128
Client.......Client
Client : RespData [id=1, name=ITDragon博客 不錯哦!, responseMsg=這是一篇Netty的編解碼博客! 你很棒棒的!]
Client : RespData [id=2, name=發送附件案例 不錯哦!, responseMsg=收到圖片了, 圖片路徑是 : F:\DayDayUp\...\receive\001.jpg]
3 優質博客鏈接
以上便是Netty的編解碼的入門筆記,下一章介紹Netty的通信連接和心跳檢測實戰應用。如果覺得不錯,可以點個贊哦