網(wǎng)站建設(shè)中圖標網(wǎng)絡(luò)營銷方式方法
連接閑置
網(wǎng)絡(luò)連接的閑置指的是當前網(wǎng)絡(luò)連接處于空閑狀態(tài),即沒有正在進行的數(shù)據(jù)傳輸或通信活動。當我們的某個連接不再發(fā)送請求或者接收響應(yīng)的時候,這個連接就開始處于閑置狀態(tài)。
網(wǎng)絡(luò)連接的閑置時間越長,說明該連接越不活躍。此時,可以根據(jù)不同的場景,采取不同行為,比如:
關(guān)閉連接
發(fā)送心跳,長連接場景下要求定時發(fā)送心跳保持連接狀態(tài)或者是探活
Netty連接閑置handler
Netty默認提供了io.netty.handler.timeout.IdleStateHandler管理連接閑置事件,它可以檢測連接空閑時間,當連接在指定時間內(nèi)沒有讀或者寫操作時,就會觸發(fā)一個IdleStateEvent事件:
IdleStateHandler主要提供有三個參數(shù),設(shè)置為0表示禁用:
readerIdleTimeSeconds:讀空閑時間,即當服務(wù)器指定時間沒有數(shù)據(jù)讀取,會觸發(fā)一個讀閑置事件。
writerIdleTimeSeconds:寫空閑時間,即當服務(wù)器指定時間沒有數(shù)據(jù)發(fā)送(或?qū)懭雱幼?#xff0c;參數(shù)不同階段不一樣),會觸發(fā)一個寫閑置事件。
allIdleTimeSeconds:讀/寫空閑時間,客戶端連接在指定時間內(nèi)沒有讀/寫操作時,就會觸發(fā)一個IdleStateEvent的All事件。
public IdleStateHandler(int readerIdleTimeSeconds,int writerIdleTimeSeconds,int allIdleTimeSeconds) {this(readerIdleTimeSeconds, writerIdleTimeSeconds, allIdleTimeSeconds,TimeUnit.SECONDS);}
讀閑置
Netty的ReadTimeoutHandler是一個針對讀閑置處理的handler,用于處理讀取超時事件。當客戶端或服務(wù)器在指定時間內(nèi)沒有收到數(shù)據(jù)時,這個類會觸發(fā)一個讀取超時事件。
在Netty中,使用IdleStateHandler來管理讀取超時和寫入超時。ReadTimeoutHandler繼承了IdleStateHandler,并在其基礎(chǔ)上實現(xiàn)了定制化的讀取超時功能。當讀取超時發(fā)生時,ReadTimeoutHandler會調(diào)用其channelRead方法,其中實現(xiàn)了具體的讀取超時邏輯。
ReadTimeoutHandler在達到閑置時間的時候會拋出一個讀超時異常,并關(guān)閉連接:
protected void readTimedOut(ChannelHandlerContext ctx) throws Exception {if (!closed) {ctx.fireExceptionCaught(ReadTimeoutException.INSTANCE);ctx.close();closed = true;}}
我一般把ReadTimeoutHandler用在服務(wù)端,當某個客戶端的連接閑置太長時間就關(guān)閉這個客戶端的連接,釋放資源。
一般情況下IdleStateHandler都是作為ChannelPipeline中的第一個Handler,用于處理連接的心跳檢測。當連接超時時會觸發(fā)IdleStateEvent事件,然后交給下一個handler處理該事件,所以ReadTimeoutHandler也是這樣,示例如下:
@Overridepublic void initChannel(SocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();pipeline.addLast(new ReadTimeoutHandler(READ_TIMEOUT_SECONDS));pipeline.addLast(new ServerHandler());}
寫閑置
寫閑置我一般用在客戶端,比如達到閑置時間關(guān)閉與服務(wù)端的連接或者發(fā)送心跳給服務(wù)端以保持長連接。
寫閑置則關(guān)閉示例如下:
public class ClientIdleHandler extends IdleStateHandler {private static final long IDLE_TIMEOUT = 30000;public ClientIdleHandler() {super(true, 0, IDLE_TIMEOUT, 0, TimeUnit.MILLISECONDS);}@Overrideprotected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) throws Exception {if (evt == IdleStateEvent.FIRST_WRITER_IDLE_STATE_EVENT) {// 連接閑置,關(guān)閉連接ctx.close();}}
}
或者也可以不關(guān)閉,發(fā)送一個心跳包,但是要注意,這種情況,客戶端的閑置時間要少于服務(wù)端的。
我們也可以增加一個定時任務(wù)來發(fā)送心跳包:
// 重寫handlerAdded方法,添加一個定時任務(wù)@Overridepublic void handlerAdded(ChannelHandlerContext ctx) throws Exception {ctx.executor().scheduleAtFixedRate(() -> {// 判斷連接是否處于活動狀態(tài)if (ctx.channel().isActive()) { // 發(fā)送心跳包ctx.writeAndFlush(new Heartbeat()); }}, 0, idleTimeSeconds, TimeUnit.SECONDS);}
讀寫閑置
上面的讀閑置時間和寫閑置時間都達到了才觸發(fā),兩者取最大值:
long nextDelay = allIdleTimeNanos;if (!reading) {nextDelay -= ticksInNanos() - Math.max(lastReadTime, lastWriteTime);}
寫意圖
public IdleStateHandler(boolean observeOutput,long readerIdleTime, long writerIdleTime, long allIdleTime,TimeUnit unit)
在評估寫閑置的時候還有一個參數(shù):observeOutput。
非寫閑置判定的時候,是以數(shù)據(jù)發(fā)送出去為準,還是有寫的動作為準,默認該值是false,數(shù)據(jù)發(fā)送判定為寫閑置,而設(shè)置為true就是寫動作發(fā)生時,比如寫入緩存但是還沒發(fā)送就是非閑置。
依據(jù)觀測當前緩存的數(shù)據(jù)變化情況和進度來判斷。如下,和上次進行比對:
if (buf != null) {int messageHashCode = System.identityHashCode(buf.current());long pendingWriteBytes = buf.totalPendingWriteBytes();if (messageHashCode != lastMessageHashCode || pendingWriteBytes != lastPendingWriteBytes) {lastMessageHashCode = messageHashCode;lastPendingWriteBytes = pendingWriteBytes;if (!first) {return true;}}long flushProgress = buf.currentProgress();if (flushProgress != lastFlushProgress) {lastFlushProgress = flushProgress;if (!first) {return true;}}}}
誤區(qū)
Netty提供了讀閑置處理的ReadTimeoutHandler,是不是提供的也有寫閑置的WriteTimeoutHandler。
明確說明:沒有寫閑置的WriteTimeoutHandler,但確實存在WriteTimeoutHandler,該hander表示的是寫數(shù)據(jù)動作的超時handler,卻不是表示連接寫閑置的handler,當在指定時間內(nèi)數(shù)據(jù)沒寫完,會拋出一個寫超時異常,可以看下源碼:
public void run() {// Was not written yet so issue a write timeout// The promise itself will be failed with a ClosedChannelException once the close() was issued// See https://github.com/netty/netty/issues/2159if (!promise.isDone()) {try {writeTimedOut(ctx);} catch (Throwable t) {ctx.fireExceptionCaught(t);}}removeWriteTimeoutTask(this);}protected void writeTimedOut(ChannelHandlerContext ctx) throws Exception {if (!closed) {ctx.fireExceptionCaught(WriteTimeoutException.INSTANCE);ctx.close();closed = true;}}