最近經常會再面試中碰到 Netty 相關的問題,
全文采用大家喜歡的與面試官對話的形式展開, 如果大家覺得總結的不錯的話,不妨點一個贊鼓勵一下!這是我繼續堅持很重要的動力來源,
概覽:
- Netty 是什么?
- 為什么要用 Netty?
- Netty 應用場景了解么?
- Netty 核心組件有哪些?分別有什么作用?
- EventloopGroup 了解么?和 EventLoop 啥關系?
- Bootstrap 和 ServerBootstrap 了解么?
- NioEventLoopGroup 默認的建構式會起多少執行緒?
- Netty 執行緒模型了解么?
- Netty 服務端和客戶端的啟動程序了解么?
- Netty 長連接、心跳機制了解么?
- Netty 的零拷貝了解么?
Netty 是什么?
面試官 :介紹一下自己對 Netty 的認識吧!小伙子,
我 :好的!那我就簡單用 3 點來概括一下 Netty 吧!
- Netty 是一個 基于 NIO 的 client-server(客戶端服務器)框架,使用它可以快速簡單地開發網路應用程式,
- 它極大地簡化并優化了 TCP 和 UDP 套接字服務器等網路編程,并且性能以及安全性等很多方面甚至都要更好,
- 支持多種協議 如 FTP,SMTP,HTTP 以及各種二進制和基于文本的傳統協議,
用官方的總結就是:Netty 成功地找到了一種在不妥協可維護性和性能的情況下實作易于開發,性能,穩定性和靈活性的方法,
除了上面介紹的之外,很多開源專案比如我們常用的 Dubbo、RocketMQ、Elasticsearch、gRPC 等等都用到了 Netty,
網路編程我愿意稱 Netty 為王 ,
為什么要用 Netty?
面試官 :為什么要用 Netty 呢?能不能說一下自己的看法,
我 :因為 Netty 具有下面這些優點,并且相比于直接使用 JDK 自帶的 NIO 相關的 API 來說更加易用,
- 統一的 API,支持多種傳輸型別,阻塞和非阻塞的,
- 簡單而強大的執行緒模型,
- 自帶編解碼器解決 TCP 粘包/拆包問題,
- 自帶各種協議堆疊,
- 真正的無連接資料包套接字支持,
- 比直接使用 Java 核心 API 有更高的吞吐量、更低的延遲、更低的資源消耗和更少的記憶體復制,
- 安全性不錯,有完整的 SSL/TLS 以及 StartTLS 支持,
- 社區活躍
- 成熟穩定,經歷了大型專案的使用和考驗,而且很多開源專案都使用到了 Netty, 比如我們經常接觸的 Dubbo、RocketMQ 等等,
- ......
Netty 應用場景了解么?
面試官 :能不能通俗地說一下使用 Netty 可以做什么事情?
我 :憑借自己的了解,簡單說一下吧!理論上來說,NIO 可以做的事情 ,使用 Netty 都可以做并且更好,Netty 主要用來做網路通信 :
- 作為 RPC 框架的網路通信工具 :我們在分布式系統中,不同服務節點之間經常需要相互呼叫,這個時候就需要 RPC 框架了,不同服務節點之間的通信是如何做的呢?可以使用 Netty 來做,比如我呼叫另外一個節點的方法的話,至少是要讓對方知道我呼叫的是哪個類中的哪個方法以及相關引數吧!
- 實作一個自己的 HTTP 服務器 :通過 Netty 我們可以自己實作一個簡單的 HTTP 服務器,這個大家應該不陌生,說到 HTTP 服務器的話,作為 Java 后端開發,我們一般使用 Tomcat 比較多,一個最基本的 HTTP 服務器可要以處理常見的 HTTP Method 的請求,比如 POST 請求、GET 請求等等,
- 實作一個即時通訊系統 :使用 Netty 我們可以實作一個可以聊天類似微信的即時通訊系統,這方面的開源專案還蠻多的,可以自行去 Github 找一找,
- 實作訊息推送系統 :市面上有很多訊息推送系統都是基于 Netty 來做的,
- ......
Netty 核心組件有哪些?分別有什么作用?
面試官 :Netty 核心組件有哪些?分別有什么作用?
我 :表面上,嘴上開始說起 Netty 的核心組件有哪些,實則,內心已經開始 mmp 了,深度懷疑這面試官是存心搞我啊!
1.Channel
Channel 介面是 Netty 對網路操作抽象類,它除了包括基本的 I/O 操作,如 bind()、connect()、read()、write() 等,
比較常用的Channel介面實作類是NioServerSocketChannel(服務端)和NioSocketChannel(客戶端),這兩個 Channel 可以和 BIO 編程模型中的ServerSocket以及Socket兩個概念對應上,Netty 的 Channel 介面所提供的 API,大大地降低了直接使用 Socket 類的復雜性,
2.EventLoop
這么說吧!EventLoop(事件回圈)介面可以說是 Netty 中最核心的概念了!
《Netty 實戰》這本書是這樣介紹它的:
“
EventLoop 定義了 Netty 的核心抽象,用于處理連接的生命周期中所發生的事件,
是不是很難理解?說實話,我學習 Netty 的時候看到這句話是沒太能理解的,
說白了,EventLoop 的主要作用實際就是負責監聽網路事件并呼叫事件處理器進行相關 I/O 操作的處理,
那 Channel 和 EventLoop 直接有啥聯系呢?
Channel 為 Netty 網路操作(讀寫等操作)抽象類,EventLoop 負責處理注冊到其上的Channel 處理 I/O 操作,兩者配合參與 I/O 操作,
3.ChannelFuture
Netty 是異步非阻塞的,所有的 I/O 操作都為異步的,
因此,我們不能立刻得到操作是否執行成功,但是,你可以通過 ChannelFuture 介面的 addListener() 方法注冊一個 ChannelFutureListener,當操作執行成功或者失敗時,監聽就會自動觸發回傳結果,
并且,你還可以通過ChannelFuture 的 channel() 方法獲取關聯的Channel
public interface ChannelFuture extends Future<Void> {
Channel channel();
ChannelFuture addListener(GenericFutureListener<? extends Future<? super Void>> var1);
......
ChannelFuture sync() throws InterruptedException;
}
另外,我們還可以通過 ChannelFuture 介面的 sync()方法讓異步的操作變成同步的,
4.ChannelHandler 和 ChannelPipeline
下面這段代碼使用過 Netty 的小伙伴應該不會陌生,我們指定了序列化編解碼器以及自定義的 ChannelHandler 處理訊息,
b.group(eventLoopGroup)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new NettyKryoDecoder(kryoSerializer, RpcResponse.class));
ch.pipeline().addLast(new NettyKryoEncoder(kryoSerializer, RpcRequest.class));
ch.pipeline().addLast(new KryoClientHandler());
}
});
ChannelHandler 是訊息的具體處理器,他負責處理讀寫操作、客戶端連接等事情,
ChannelPipeline 為 ChannelHandler 的鏈,提供了一個容器并定義了用于沿著鏈傳播入站和出站事件流的 API ,當 Channel 被創建時,它會被自動地分配到它專屬的 ChannelPipeline,
我們可以在 ChannelPipeline 上通過 addLast() 方法添加一個或者多個ChannelHandler ,因為一個資料或者事件可能會被多個 Handler 處理,當一個 ChannelHandler 處理完之后就將資料交給下一個 ChannelHandler ,
EventloopGroup 了解么?和 EventLoop 啥關系?
面試官 :剛剛你也介紹了 EventLoop,那你再說說 EventloopGroup 吧!和 EventLoop 啥關系?
我 :
EventLoopGroup 包含多個 EventLoop(每一個 EventLoop 通常內部包含一個執行緒),上面我們已經說了 EventLoop 的主要作用實際就是負責監聽網路事件并呼叫事件處理器進行相關 I/O 操作的處理,
并且 EventLoop 處理的 I/O 事件都將在它專有的 Thread 上被處理,即 Thread 和 EventLoop 屬于 1 : 1 的關系,從而保證執行緒安全,
上圖是一個服務端對 EventLoopGroup 使用的大致模塊圖,其中 Boss EventloopGroup 用于接收連接,Worker EventloopGroup 用于具體的處理(訊息的讀寫以及其他邏輯處理),
從上圖可以看出:當客戶端通過 connect 方法連接服務端時,bossGroup 處理客戶端連接請求,當客戶端處理完成后,會將這個連接提交給 workerGroup 來處理,然后 workerGroup 負責處理其 IO 相關操作,
Bootstrap 和 ServerBootstrap 了解么?
面試官 :你再說說自己對 Bootstrap 和 ServerBootstrap 的了解吧!
我 :
Bootstrap 是客戶端的啟動引導類/輔助類,具體使用方法如下:
EventLoopGroup group = new NioEventLoopGroup();
try {
//創建客戶端啟動引導/輔助類:Bootstrap
Bootstrap b = new Bootstrap();
//指定執行緒模型
b.group(group).
......
// 嘗試建立連接
ChannelFuture f = b.connect(host, port).sync();
f.channel().closeFuture().sync();
} finally {
// 優雅關閉相關執行緒組資源
group.shutdownGracefully();
}
ServerBootstrap 客戶端的啟動引導類/輔助類,具體使用方法如下:
// 1.bossGroup 用于接收連接,workerGroup 用于具體的處理
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//2.創建服務端啟動引導/輔助類:ServerBootstrap
ServerBootstrap b = new ServerBootstrap();
//3.給引導類配置兩大執行緒組,確定了執行緒模型
b.group(bossGroup, workerGroup).
......
// 6.系結埠
ChannelFuture f = b.bind(port).sync();
// 等待連接關閉
f.channel().closeFuture().sync();
} finally {
//7.優雅關閉相關執行緒組資源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
從上面的示例中,我們可以看出:
- Bootstrap 通常使用 connet() 方法連接到遠程的主機和埠,作為一個 Netty TCP 協議通信中的客戶端,另外,Bootstrap 也可以通過 bind() 方法系結本地的一個埠,作為 UDP 協議通信中的一端,
- ServerBootstrap通常使用 bind() 方法系結本地的埠上,然后等待客戶端的連接,
- Bootstrap 只需要配置一個執行緒組— EventLoopGroup ,而 ServerBootstrap需要配置兩個執行緒組— EventLoopGroup ,一個用于接收連接,一個用于具體的處理,
NioEventLoopGroup 默認的建構式會起多少執行緒?
面試官 :看過 Netty 的原始碼了么?NioEventLoopGroup 默認的建構式會起多少執行緒呢?
我 :嗯嗯!看過部分,
回顧我們在上面寫的服務器端的代碼:
// 1.bossGroup 用于接收連接,workerGroup 用于具體的處理 EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup();
為了搞清楚NioEventLoopGroup 默認的建構式 到底創建了多少個執行緒,我們來看一下它的原始碼,
/**
* 無參建構式,
* nThreads:0
*/
public NioEventLoopGroup() {
//呼叫下一個構造方法
this(0);
}
/**
* Executor:null
*/
public NioEventLoopGroup(int nThreads) {
//繼續呼叫下一個構造方法
this(nThreads, (Executor) null);
}
//中間省略部分建構式
/**
* RejectedExecutionHandler():RejectedExecutionHandlers.reject()
*/
public NioEventLoopGroup(int nThreads, Executor executor, final SelectorProvider selectorProvider,final SelectStrategyFactory selectStrategyFactory) {
//開始呼叫父類的建構式
super(nThreads, executor, selectorProvider, selectStrategyFactory, RejectedExecutionHandlers.reject());
}
一直向下走下去的話,你會發現在 MultithreadEventLoopGroup 類中有相關的指定執行緒數的代碼,如下:
// 從1,系統屬性,CPU核心數*2 這三個值中取出一個最大的
//可以得出 DEFAULT_EVENT_LOOP_THREADS 的值為CPU核心數*2
private static final int DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
// 被呼叫的父類建構式,NioEventLoopGroup 默認的建構式會起多少執行緒的秘密所在
// 當指定的執行緒數nThreads為0時,使用默認的執行緒數DEFAULT_EVENT_LOOP_THREADS
protected MultithreadEventLoopGroup(int nThreads, ThreadFactory threadFactory, Object... args) {
super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, threadFactory, args);
}
綜上,我們發現 NioEventLoopGroup 默認的建構式實際會起的執行緒數為 CPU核心數*2,
另外,如果你繼續深入下去看建構式的話,你會發現每個NioEventLoopGroup物件內部都會分配一組NioEventLoop,其大小是 nThreads, 這樣就構成了一個執行緒池, 一個NIOEventLoop 和一個執行緒相對應,這和我們上面說的 EventloopGroup 和 EventLoop關系這部分內容相對應,
Netty 執行緒模型了解么?
面試官 :說一下 Netty 執行緒模型吧!
我 :大部分網路框架都是基于 Reactor 模式設計開發的,
“
Reactor 模式基于事件驅動,采用多路復用將事件分發給相應的 Handler 處理,非常適合處理海量 IO 的場景,
在 Netty 主要靠 NioEventLoopGroup 執行緒池來實作具體的執行緒模型的 ,
我們實作服務端的時候,一般會初始化兩個執行緒組:
- bossGroup :接收連接,
- workerGroup :負責具體的處理,交由對應的 Handler 處理,
下面我們來詳細看一下 Netty 中的執行緒模型吧!
1.單執行緒模型 :
一個執行緒需要執行處理所有的 accept、read、decode、process、encode、send 事件,對于高負載、高并發,并且對性能要求比較高的場景不適用,
對應到 Netty 代碼是下面這樣的
“
使用 NioEventLoopGroup 類的無參建構式設定執行緒數量的默認值就是 CPU 核心數 *2 ,
//1.eventGroup既用于處理客戶端連接,又負責具體的處理, EventLoopGroup eventGroup = new NioEventLoopGroup(1); //2.創建服務端啟動引導/輔助類:ServerBootstrap ServerBootstrap b = new ServerBootstrap(); boobtstrap.group(eventGroup, eventGroup) //......
2.多執行緒模型
一個 Acceptor 執行緒只負責監聽客戶端的連接,一個 NIO 執行緒池負責具體處理:accept、read、decode、process、encode、send 事件,滿足絕大部分應用場景,并發連接量不大的時候沒啥問題,但是遇到并發連接大的時候就可能會出現問題,成為性能瓶頸,
對應到 Netty 代碼是下面這樣的:
// 1.bossGroup 用于接收連接,workerGroup 用于具體的處理
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//2.創建服務端啟動引導/輔助類:ServerBootstrap
ServerBootstrap b = new ServerBootstrap();
//3.給引導類配置兩大執行緒組,確定了執行緒模型
b.group(bossGroup, workerGroup)
//......
3.主從多執行緒模型
從一個 主執行緒 NIO 執行緒池中選擇一個執行緒作為 Acceptor 執行緒,系結監聽埠,接收客戶端連接的連接,其他執行緒負責后續的接入認證等作業,連接建立完成后,Sub NIO 執行緒池負責具體處理 I/O 讀寫,如果多執行緒模型無法滿足你的需求的時候,可以考慮使用主從多執行緒模型 ,
// 1.bossGroup 用于接收連接,workerGroup 用于具體的處理
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//2.創建服務端啟動引導/輔助類:ServerBootstrap
ServerBootstrap b = new ServerBootstrap();
//3.給引導類配置兩大執行緒組,確定了執行緒模型
b.group(bossGroup, workerGroup)
//......
Netty 服務端和客戶端的啟動程序了解么?
服務端
// 1.bossGroup 用于接收連接,workerGroup 用于具體的處理
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//2.創建服務端啟動引導/輔助類:ServerBootstrap
ServerBootstrap b = new ServerBootstrap();
//3.給引導類配置兩大執行緒組,確定了執行緒模型
b.group(bossGroup, workerGroup)
// (非必備)列印日志
.handler(new LoggingHandler(LogLevel.INFO))
// 4.指定 IO 模型
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) {
ChannelPipeline p = ch.pipeline();
//5.可以自定義客戶端訊息的業務處理邏輯
p.addLast(new HelloServerHandler());
}
});
// 6.系結埠,呼叫 sync 方法阻塞知道系結完成
ChannelFuture f = b.bind(port).sync();
// 7.阻塞等待直到服務器Channel關閉(closeFuture()方法獲取Channel 的CloseFuture物件,然后呼叫sync()方法)
f.channel().closeFuture().sync();
} finally {
//8.優雅關閉相關執行緒組資源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
簡單決議一下服務端的創建程序具體是怎樣的:
1.首先你創建了兩個 NioEventLoopGroup 物件實體:bossGroup 和 workerGroup,
- bossGroup : 用于處理客戶端的 TCP 連接請求,
- workerGroup :負責每一條連接的具體讀寫資料的處理邏輯,真正負責 I/O 讀寫操作,交由對應的 Handler 處理,
舉個例子:我們把公司的老板當做 bossGroup,員工當做 workerGroup,bossGroup 在外面接完活之后,扔給 workerGroup 去處理,一般情況下我們會指定 bossGroup 的 執行緒數為 1(并發連接量不大的時候) ,workGroup 的執行緒數量為 CPU 核心數 *2 ,另外,根據原始碼來看,使用 NioEventLoopGroup 類的無參建構式設定執行緒數量的默認值就是 CPU 核心數 *2 ,
2.接下來 我們創建了一個服務端啟動引導/輔助類:ServerBootstrap,這個類將引導我們進行服務端的啟動作業,
3.通過 .group() 方法給引導類 ServerBootstrap 配置兩大執行緒組,確定了執行緒模型,
通過下面的代碼,我們實際配置的是多執行緒模型,這個在上面提到過,
EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup();
4.通過channel()方法給引導類 ServerBootstrap指定了 IO 模型為NIO
- NioServerSocketChannel :指定服務端的 IO 模型為 NIO,與 BIO 編程模型中的ServerSocket對應
- NioSocketChannel : 指定客戶端的 IO 模型為 NIO, 與 BIO 編程模型中的Socket對應5.通過 .childHandler()給引導類創建一個ChannelInitializer ,然后制定了服務端訊息的業務處理邏輯 HelloServerHandler 物件6.呼叫 ServerBootstrap 類的 bind()方法系結埠
客戶端
//1.創建一個 NioEventLoopGroup 物件實體
EventLoopGroup group = new NioEventLoopGroup();
try {
//2.創建客戶端啟動引導/輔助類:Bootstrap
Bootstrap b = new Bootstrap();
//3.指定執行緒組
b.group(group)
//4.指定 IO 模型
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
// 5.這里可以自定義訊息的業務處理邏輯
p.addLast(new HelloClientHandler(message));
}
});
// 6.嘗試建立連接
ChannelFuture f = b.connect(host, port).sync();
// 7.等待連接關閉(阻塞,直到Channel關閉)
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
繼續分析一下客戶端的創建流程:
1.創建一個 NioEventLoopGroup 物件實體
2.創建客戶端啟動的引導類是 Bootstrap
3.通過 .group() 方法給引導類 Bootstrap 配置一個執行緒組
4.通過channel()方法給引導類 Bootstrap指定了 IO 模型為NIO
5.通過 .childHandler()給引導類創建一個ChannelInitializer ,然后制定了客戶端訊息的業務處理邏輯 HelloClientHandler 物件
6.呼叫 Bootstrap 類的 connect()方法進行連接,這個方法需要指定兩個引數:
- inetHost : ip 地址
- inetPort : 埠號
public ChannelFuture connect(String inetHost, int inetPort) {
return this.connect(InetSocketAddress.createUnresolved(inetHost, inetPort));
}
public ChannelFuture connect(SocketAddress remoteAddress) {
ObjectUtil.checkNotNull(remoteAddress, "remoteAddress");
this.validate();
return this.doResolveAndConnect(remoteAddress, this.config.localAddress());
}
connect 方法回傳的是一個 Future 型別的物件
public interface ChannelFuture extends Future<Void> {
......
}
也就是說這個方是異步的,我們通過 addListener 方法可以監聽到連接是否成功,進而列印出連接資訊,具體做法很簡單,只需要對代碼進行以下改動:
ChannelFuture f = b.connect(host, port).addListener(future -> {
if (future.isSuccess()) {
System.out.println("連接成功!");
} else {
System.err.println("連接失敗!");
}
}).sync();
什么是 TCP 粘包/拆包?有什么解決辦法呢?
面試官 :什么是 TCP 粘包/拆包?
我 :TCP 粘包/拆包 就是你基于 TCP 發送資料的時候,出現了多個字串“粘”在了一起或者一個字串被“拆”開的問題,比如你多次發送:“你好,你真帥啊!哥哥!”,但是客戶端接收到的可能是下面這樣的:
面試官 :那有什么解決辦法呢?
我 :
1.使用 Netty 自帶的解碼器
- LineBasedFrameDecoder : 發送端發送資料包的時候,每個資料包之間以換行符作為分隔,LineBasedFrameDecoder 的作業原理是它依次遍歷 ByteBuf 中的可讀位元組,判斷是否有換行符,然后進行相應的截取,
- DelimiterBasedFrameDecoder : 可以自定義分隔符解碼器,LineBasedFrameDecoder 實際上是一種特殊的 DelimiterBasedFrameDecoder 解碼器,
- FixedLengthFrameDecoder: 固定長度解碼器,它能夠按照指定的長度對訊息進行相應的拆包,
- LengthFieldBasedFrameDecoder:
2.自定義序列化編解碼器
在 Java 中自帶的有實作 Serializable 介面來實作序列化,但由于它性能、安全性等原因一般情況下是不會被使用到的,
通常情況下,我們使用 Protostuff、Hessian2、json 序列方式比較多,另外還有一些序列化性能非常好的序列化方式也是很好的選擇:
- 專門針對 Java 語言的:Kryo,FST 等等
- 跨語言的:Protostuff(基于 protobuf 發展而來),ProtoBuf,Thrift,Avro,MsgPack 等等
“
由于篇幅問題,這部分內容會在后續的文章中詳細分析介紹~~~
Netty 長連接、心跳機制了解么?
面試官 :TCP 長連接和短連接了解么?
我 :我們知道 TCP 在進行讀寫之前,server 與 client 之間必須提前建立一個連接,建立連接的程序,需要我們常說的三次握手,釋放/關閉連接的話需要四次揮手,這個程序是比較消耗網路資源并且有時間延遲的,
所謂,短連接說的就是 server 端 與 client 端建立連接之后,讀寫完成之后就關閉掉連接,如果下一次再要互相發送訊息,就要重新連接,短連接的有點很明顯,就是管理和實作都比較簡單,缺點也很明顯,每一次的讀寫都要建立連接必然會帶來大量網路資源的消耗,并且連接的建立也需要耗費時間,
長連接說的就是 client 向 server 雙方建立連接之后,即使 client 與 server 完成一次讀寫,它們之間的連接并不會主動關閉,后續的讀寫操作會繼續使用這個連接,長連接的可以省去較多的 TCP 建立和關閉的操作,降低對網路資源的依賴,節約時間,對于頻繁請求資源的客戶來說,非常適用長連接,
面試官 :為什么需要心跳機制?Netty 中心跳機制了解么?
我 :
在 TCP 保持長連接的程序中,可能會出現斷網等網路例外出現,例外發生的時候, client 與 server 之間如果沒有互動的話,它們是無法發現對方已經掉線的,為了解決這個問題, 我們就需要引入 心跳機制 ,
心跳機制的作業原理是: 在 client 與 server 之間在一定時間內沒有資料互動時, 即處于 idle 狀態時, 客戶端或服務器就會發送一個特殊的資料包給對方, 當接收方收到這個資料報文后, 也立即發送一個特殊的資料報文, 回應發送方, 此即一個 PING-PONG 互動,所以, 當某一端收到心跳訊息后, 就知道了對方仍然在線, 這就確保 TCP 連接的有效性.
TCP 實際上自帶的就有長連接選項,本身是也有心跳包機制,也就是 TCP 的選項:SO_KEEPALIVE,但是,TCP 協議層面的長連接靈活性不夠,所以,一般情況下我們都是在應用層協議上實作自定義心跳機制的,也就是在 Netty 層面通過編碼實作,通過 Netty 實作心跳機制的話,核心類是 IdleStateHandler ,
Netty 的零拷貝了解么?
面試官 :講講 Netty 的零拷貝?
我 :
維基百科是這樣介紹零拷貝的:
“
零復制(英語:Zero-copy;也譯零拷貝)技術是指計算機執行操作時,CPU 不需要先將資料從某處記憶體復制到另一個特定區域,這種技術通常用于通過網路傳輸檔案時節省 CPU 周期和記憶體帶寬,
在 OS 層面上的 Zero-copy 通常指避免在 用戶態(User-space) 與 內核態(Kernel-space) 之間來回拷貝資料,而在 Netty 層面 ,零拷貝主要體現在對于資料操作的優化,
Netty 中的零拷貝體現在以下幾個方面:
- 使用 Netty 提供的 CompositeByteBuf 類, 可以將多個ByteBuf 合并為一個邏輯上的 ByteBuf, 避免了各個 ByteBuf 之間的拷貝,
- ByteBuf 支持 slice 操作, 因此可以將 ByteBuf 分解為多個共享同一個存盤區域的 ByteBuf, 避免了記憶體的拷貝,
- 通過 FileRegion 包裝的FileChannel.tranferTo 實作檔案傳輸, 可以直接將檔案緩沖區的資料發送到目標 Channel, 避免了傳統通過回圈 write 方式導致的記憶體拷貝問題.
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/162015.html
標籤:Java
