Netty的心跳機(jī)制
一、引入
在 TCP 保持長(zhǎng)連接的過(guò)程中,可能會(huì)出現(xiàn)斷網(wǎng)等網(wǎng)絡(luò)異常出現(xiàn),異常發(fā)生的時(shí)候, client 與 server 之間如果沒(méi)有交互的話(huà),它們是無(wú)法發(fā)現(xiàn)對(duì)方已經(jīng)掉線(xiàn)。
二、工作原理
在 client 與 server 之間在一定時(shí)間內(nèi)沒(méi)有數(shù)據(jù)交互時(shí), 即處于 idle 狀態(tài)時(shí), 客戶(hù)端或服務(wù)器就會(huì)發(fā)送一個(gè)特殊的數(shù)據(jù)包給對(duì)方, 當(dāng)接收方收到這個(gè)數(shù)據(jù)報(bào)文后, 也立即發(fā)送一個(gè)特殊的數(shù)據(jù)報(bào)文, 回應(yīng)發(fā)送方, 此即一個(gè) PING-PONG 交互。所以, 當(dāng)某一端收到心跳消息后, 就知道了對(duì)方仍然在線(xiàn), 這就確保 TCP 連接的有效性。
TCP 實(shí)際上自帶的就有長(zhǎng)連接選項(xiàng),本身是也有心跳包機(jī)制,也就是 TCP 的選項(xiàng):SO_KEEPALIVE。但是,TCP 協(xié)議層面的長(zhǎng)連接靈活性不夠。所以,一般情況下我們都是在應(yīng)用層協(xié)議上實(shí)現(xiàn)自定義心跳機(jī)制的,也就是在 Netty 層面通過(guò)編碼實(shí)現(xiàn)。通過(guò) Netty 實(shí)現(xiàn)心跳機(jī)制的話(huà),核心類(lèi)是 IdleStateHandler 。
三、實(shí)現(xiàn)
在?Netty中, 實(shí)現(xiàn)心跳機(jī)制的關(guān)鍵是?IdleStateHandler
public IdleStateHandler(int readerIdleTimeSeconds, int writerIdleTimeSeconds, int allIdleTimeSeconds) {
this((long)readerIdleTimeSeconds, (long)writerIdleTimeSeconds, (long)allIdleTimeSeconds, TimeUnit.SECONDS);
}
參數(shù)的含義:
?readerIdleTimeSeconds: 讀超時(shí). 即當(dāng)在指定的時(shí)間間隔內(nèi)沒(méi)有從?Channel?讀取到數(shù)據(jù)時(shí), 會(huì)觸發(fā)一個(gè)?READER_IDLE?的?IdleStateEvent?事件.?writerIdleTimeSeconds: 寫(xiě)超時(shí). 即當(dāng)在指定的時(shí)間間隔內(nèi)沒(méi)有數(shù)據(jù)寫(xiě)入到?Channel?時(shí), 會(huì)觸發(fā)一個(gè)?WRITER_IDLE?的?IdleStateEvent?事件.?allIdleTimeSeconds: 讀/寫(xiě)超時(shí). 即當(dāng)在指定的時(shí)間間隔內(nèi)沒(méi)有讀或?qū)懖僮鲿r(shí), 會(huì)觸發(fā)一個(gè)?ALL_IDLE?的?IdleStateEvent?事件.
注意:這三個(gè)參數(shù)默認(rèn)的時(shí)間單位是秒。
心跳處理類(lèi):ClientIdleStateTrigger
/**端發(fā)送一個(gè)心跳包。
* <p>
* 用于捕獲{@link IdleState#WRITER_IDLE}事件(未在指定時(shí)間內(nèi)向服務(wù)器發(fā)送數(shù)據(jù)),然后向Server
* p>
*/
public class ClientIdleStateTrigger extends ChannelInboundHandlerAdapter {
public static final String HEART_BEAT = "heart beat!";
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleState state = ((IdleStateEvent) evt).state();
if (state == IdleState.WRITER_IDLE) {
// write heartbeat to server
ctx.writeAndFlush(HEART_BEAT);
}
} else {
super.userEventTriggered(ctx, evt);
}
}
}
四、源碼剖析

第254行代碼其實(shí)表示該方法只是進(jìn)行了透?jìng)鳎蛔鋈魏螛I(yè)務(wù)邏輯處理,讓channelPipe中的下一個(gè)handler處理channelRead方法,但是記錄了一下這里的調(diào)用時(shí)間
channelActive方法

initialize方法

這邊會(huì)觸發(fā)一個(gè)Task,ReaderIdleTimeoutTask,這個(gè)task是部分源碼

341行是這樣的,用當(dāng)前時(shí)間減去最后一次channelRead方法調(diào)用的時(shí)間,假如這個(gè)結(jié)果是6s,說(shuō)明最后一次調(diào)用channelRead已經(jīng)是6s之前的事情了,你設(shè)置的是5s,那么nextDelay則為-1,說(shuō)明超時(shí)了,那么354行則會(huì)觸發(fā)userEventTriggered方法,如果沒(méi)有超時(shí)則不觸發(fā)userEventTriggered方法。

五、總結(jié)
IdleStateHandler這個(gè)類(lèi)會(huì)根據(jù)你設(shè)置的超時(shí)參數(shù)的類(lèi)型和值,循環(huán)去檢測(cè)channelRead和write方法多久沒(méi)有被調(diào)用了,如果這個(gè)時(shí)間超過(guò)了你設(shè)置的值,那么就會(huì)觸發(fā)對(duì)應(yīng)的事件,read觸發(fā)read,write觸發(fā)write,all觸發(fā)all
?如果超時(shí)了,則會(huì)調(diào)用userEventTriggered方法,且會(huì)告訴你超時(shí)的類(lèi)型?如果沒(méi)有超時(shí),則會(huì)循環(huán)定時(shí)檢測(cè),除非你將IdleStateHandler移除Pipeline
