架構師入門筆記十一 Netty5編解碼

架構師入門筆記十一 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對象圖形序列化框架(已經非常成熟了,很多大公司都在用)。主要特點是性能高效和易用。可以用來序列化對象到文件、數據庫或者網絡中。

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的通信連接和心跳檢測實戰應用。如果覺得不錯,可以點個贊哦 大笑大笑大笑




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章