一、連接假死出現(xiàn)原因
在網(wǎng)絡(luò)編程的領(lǐng)域當(dāng)中妓布,很多問題都會莫名其妙的出現(xiàn)姻蚓,讓人措手不及。
其中一種匣沼,就是連接假死狰挡,那么連接假死是如何出現(xiàn)的呢?可能存在以下幾種情況:
1)網(wǎng)絡(luò)設(shè)備出現(xiàn)故障释涛。例如網(wǎng)卡加叁,機(jī)房等,底層的 TCP 連接已經(jīng)斷開了唇撬,但應(yīng)用程序沒有感知到它匕,仍然占用著資源。
2)公網(wǎng)網(wǎng)絡(luò)不穩(wěn)定窖认,出現(xiàn)丟包豫柬。如果連續(xù)出現(xiàn)丟包,這時(shí)現(xiàn)象就是客戶端數(shù)據(jù)發(fā)不出去扑浸,服務(wù)端也一直收不到數(shù)據(jù)烧给,進(jìn)程一直占據(jù)資源,耗在這兒首装。
3)應(yīng)用程序線程阻塞,無法進(jìn)行數(shù)據(jù)讀寫杭跪。
連接假死會產(chǎn)生以下問題:
1)假死的連接占用的資源不能自動釋放仙逻。
2)向假死的連接發(fā)送數(shù)據(jù),得到的反饋是發(fā)送超時(shí)涧尿。
二系奉、如何解決連接假死?
Netty提供了一個(gè)叫做IdleStateHandler的處理器,用來進(jìn)行空閑檢測姑廉。
其實(shí)現(xiàn)方式是:
當(dāng)Channel有一段時(shí)間沒有執(zhí)行讀缺亮、寫或兩者操作時(shí)觸發(fā)IdleStateEvent。
有三個(gè)主要參數(shù):
- readerIdleTime:一個(gè)IdleStateEvent桥言,其狀態(tài)是IdleState.READER_IDLE時(shí)的指定時(shí)間段內(nèi)萌踱,沒有執(zhí)行讀操作將被觸發(fā)葵礼。 指定0以禁用。
- writerIdleTime:一個(gè)IdleStateEvent并鸵,其狀態(tài)是IdleState.WRITER_IDLE時(shí)的指定時(shí)間段內(nèi)鸳粉,沒有執(zhí)行寫操作將被觸發(fā)。 指定0以禁用园担。
- allIdleTime:一個(gè)IdleStateEvent届谈,其狀態(tài)是IdleState.ALL_IDLE時(shí)的指定時(shí)間段內(nèi),沒有進(jìn)行讀取和寫入都將被觸發(fā)弯汰。 指定0以禁用
我們正常使用時(shí)艰山,也使用這個(gè)三個(gè)參數(shù)的構(gòu)造即可:
public IdleStateHandler(
int readerIdleTimeSeconds,
int writerIdleTimeSeconds,
int allIdleTimeSeconds) {
this(readerIdleTimeSeconds, writerIdleTimeSeconds, allIdleTimeSeconds,
TimeUnit.SECONDS);
}
如上所示,其默認(rèn)單位是秒咏闪,當(dāng)然也有可以指定單位的構(gòu)造曙搬,還能附帶考慮buf處理數(shù)據(jù)時(shí)間的構(gòu)造,這里就不多介紹了汤踏。
接下來我們看看如何使用织鲸,我們需要將這個(gè)處理器添加到pipeline當(dāng)中,需要注意的是溪胶,我們需要自己去處理事件搂擦,IdleStateEvent。
關(guān)于這個(gè)事件的處理哗脖,我們不能使用常規(guī)的入站或者出站處理器了瀑踢,此處需要學(xué)習(xí)一個(gè)新的處理器,專門用來處理特殊事件的才避,我通過下面的代碼進(jìn)行演示:
服務(wù)端接收數(shù)據(jù)橱夭,添加如下代碼:
//空閑檢測處理器,檢測5秒內(nèi)未讀取
ch.pipeline().addLast(new IdleStateHandler(5, 0, 0));
//可以同時(shí)作為入站和出站處理器桑逝,此處用來處理特殊事件
ch.pipeline().addLast(new ChannelDuplexHandler() {
//用戶事件觸發(fā)
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
IdleStateEvent stateEvent = (IdleStateEvent) evt;
//我們的服務(wù)器端只進(jìn)行數(shù)據(jù)接收
if (stateEvent.state() == IdleState.READER_IDLE) {
System.out.println("已經(jīng)5秒沒有接收到數(shù)據(jù)");
}
}
});
客戶端發(fā)送數(shù)據(jù)棘劣,添加如下代碼:
//空閑檢測處理器,檢測5秒未寫入
ch.pipeline().addLast(new IdleStateHandler(0, 5, 0));
//可以同時(shí)作為入站和出站處理器楞遏,此處用來處理特殊事件
ch.pipeline().addLast(new ChannelDuplexHandler() {
//用戶事件觸發(fā)
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
IdleStateEvent stateEvent = (IdleStateEvent) evt;
//我們的服務(wù)器端只進(jìn)行數(shù)據(jù)接收
if (stateEvent.state() == IdleState.WRITER_IDLE) {
System.out.println("已經(jīng)5秒沒有寫入數(shù)據(jù)");
}
}
});
當(dāng)客戶端和服務(wù)端都啟動并建立連接后茬暇,如果沒有消息就會打印如下內(nèi)容:
已經(jīng)5秒沒有寫入數(shù)據(jù)
已經(jīng)5秒沒有寫入數(shù)據(jù)
已經(jīng)5秒沒有寫入數(shù)據(jù)
已經(jīng)5秒沒有寫入數(shù)據(jù)
... ...
已經(jīng)5秒沒有讀取到數(shù)據(jù)
已經(jīng)5秒沒有讀取到數(shù)據(jù)
已經(jīng)5秒沒有讀取到數(shù)據(jù)
已經(jīng)5秒沒有讀取到數(shù)據(jù)
... ...
其實(shí)還有后續(xù)的操作,就是寡喝,當(dāng)我們檢測到在一定時(shí)間內(nèi)沒有發(fā)生數(shù)據(jù)寫入或讀取糙俗,應(yīng)該怎么做?
可以制定一些策略预鬓,如連續(xù)多少次檢測到空閑等巧骚。最終我們要做的其實(shí)就是釋放資源:
ctx.channel().close();
上述的代碼實(shí)際并不友好,其實(shí)比較好的處理方式,需要結(jié)合業(yè)務(wù)區(qū)考慮劈彪,沒有消息讀取并不代表連接存在問題竣蹦,可以使用下面的方式,心跳檢測:
客戶端:當(dāng)檢測到?jīng)]有數(shù)據(jù)寫入時(shí)粉臊,可以定時(shí)向服務(wù)端發(fā)送心跳草添,比如每5秒就向服務(wù)端寫入一次數(shù)據(jù)。這個(gè)時(shí)間要小于服務(wù)器設(shè)置的檢測時(shí)間扼仲。
服務(wù)端:設(shè)置一個(gè)定時(shí)讀取的檢測時(shí)間远寸,比如8秒,這樣在8秒內(nèi)屠凶,一定會受到客戶端的心跳驰后,如果超過8秒沒有心跳,則可以認(rèn)為連接出現(xiàn)問題矗愧,可以關(guān)閉channel灶芝。
關(guān)于空閑檢測的內(nèi)容,就介紹這么多唉韭,有用的話點(diǎn)個(gè)贊再走吧R固椤!