什么是心跳包

心跳包就是在客戶端和服務(wù)器間定時(shí)通知對(duì)方自己狀態(tài)的一個(gè)自己定義的命令字,按照一定的時(shí)間間隔發(fā)送伍宦,類似于心跳,所以叫做心跳包次洼。
  
用來(lái)判斷對(duì)方(設(shè)備,進(jìn)程或其它網(wǎng)元)是否正常運(yùn)行卖毁,采用定時(shí)發(fā)送簡(jiǎn)單的通訊包揖曾,如果在指定時(shí)間段內(nèi)未收到對(duì)方響應(yīng),則判斷對(duì)方已經(jīng)離線亥啦。用于檢測(cè)TCP的異常斷開炭剪。基本原因是服務(wù)器端不能有效的判斷客戶端是否在線翔脱,也就是說(shuō)奴拦,服務(wù)器無(wú)法區(qū)分客戶端是長(zhǎng)時(shí)間在空閑,還是已經(jīng)掉線的情況届吁。所謂的心跳包就是客戶端定時(shí)發(fā)送簡(jiǎn)單的信息給服務(wù)器端告訴它我還在而已错妖。代碼就是每隔幾分鐘發(fā)送一個(gè)固定信息給服務(wù)端,服務(wù)端收到后回復(fù)一個(gè)固定信息如果服務(wù)端幾分鐘內(nèi)沒(méi)有收到客戶端信息則視客戶端斷開疚沐。
比如有些通信軟件長(zhǎng)時(shí)間不使用站玄,要想知道它的狀態(tài)是在線還是離線就需要心跳包株旷,定時(shí)發(fā)包收包尔邓。發(fā)包方:可以是客戶也可以是服務(wù)端,看哪邊實(shí)現(xiàn)方便合理梯嗽,一般是客戶端。服務(wù)器也可以定時(shí)發(fā)心跳下去灯节。一般來(lái)說(shuō)绵估,出于效率的考慮国裳,是由客戶端主動(dòng)向服務(wù)器端發(fā)包全跨,而不是服務(wù)器向客戶端發(fā)∨ㄈ簦客戶端每隔一段時(shí)間發(fā)一個(gè)包,使用TCP的挪钓,用send發(fā),使用UDP的英妓,用sendto發(fā)绍赛,服務(wù)器收到后辑畦,就知道當(dāng)前客戶端還處于“活著”的狀態(tài),否則蚯妇,如果隔一定時(shí)間未收到這樣的包,則服務(wù)器認(rèn)為客戶端已經(jīng)斷開箩言,進(jìn)行相應(yīng)的客戶端斷開邏輯處理焕襟。

服務(wù)器實(shí)現(xiàn)心跳機(jī)制的兩種策略

大部分CS的應(yīng)用需要心跳機(jī)制。心跳機(jī)制一般在Server和Client都要實(shí)現(xiàn)鸵赖,兩者實(shí)現(xiàn)原理基本一樣。Client不關(guān)心性能饵骨,怎么做都行茫打。

如果應(yīng)用是基于TCP的妖混,可以簡(jiǎn)單地通過(guò)SO_KEEPALIVE實(shí)現(xiàn)心跳轮洋。TCP在設(shè)置的KeepAlive定時(shí)器到達(dá)時(shí)向?qū)Χ税l(fā)一個(gè)檢測(cè)TCP segment,如果沒(méi)收到ACK或RST息堂,嘗試幾次后,就認(rèn)為對(duì)端已經(jīng)不存在荣堰,最后通知應(yīng)用程序竭翠。這里有個(gè)缺點(diǎn)是,Server主動(dòng)發(fā)出檢測(cè)包渡八,對(duì)性能有點(diǎn)影響。

應(yīng)用自己實(shí)現(xiàn)

Client啟動(dòng)一個(gè)定時(shí)器屎鳍,不斷發(fā)心跳问裕;

Server收到心跳后,給個(gè)回應(yīng)粮宛;

Server啟動(dòng)一個(gè)定時(shí)器,判斷Client是否存在忧饭,判斷方法這里列兩種:時(shí)間差和簡(jiǎn)單標(biāo)志筷畦。

1. 時(shí)間差策略

收到一個(gè)心跳后,記錄當(dāng)前時(shí)間(記為recvedTime)鳖宾。

判斷定時(shí)器時(shí)間到達(dá),計(jì)算多久沒(méi)收到心跳的時(shí)間(T)=當(dāng)前時(shí)間 - recvedTime(上面記錄的時(shí)間)帅刊。如果T大于某個(gè)設(shè)定值漂问,就可以認(rèn)為Client超時(shí)了女揭。

2. 簡(jiǎn)單標(biāo)志

收到一個(gè)心跳后栏饮,設(shè)置連接標(biāo)志為true;

判斷定時(shí)器時(shí)間到達(dá)袍嬉,查看所有的標(biāo)志,false的箍土,認(rèn)為對(duì)端超時(shí)了;true的將其設(shè)成false吴藻。

上面這種方法比上面簡(jiǎn)單一些弓柱,但檢測(cè)某個(gè)Client是否離線的誤差有點(diǎn)大。

您還有心跳嗎矢空?超時(shí)機(jī)制分析

問(wèn)題描述

在C/S模式中,有時(shí)我們會(huì)長(zhǎng)時(shí)間保持一個(gè)連接粥血,以避免頻繁地建立連接者祖,但同時(shí)绢彤,一般會(huì)有一個(gè)超時(shí)時(shí)間,在這個(gè)時(shí)間內(nèi)沒(méi)發(fā)起任何請(qǐng)求的連接會(huì)被斷開械巡,以減少負(fù)載,節(jié)約資源讥耗。并且該機(jī)制一般都是在服務(wù)端實(shí)現(xiàn)疹启,因?yàn)閏lient強(qiáng)制關(guān)閉或意外斷開連接,server端在此刻是感知不到的挣磨,如果放到client端實(shí)現(xiàn),在上述情況下茁裙,該超時(shí)機(jī)制就失效了。本來(lái)這問(wèn)題很普通晤锥,不太值得一提,但最近在項(xiàng)目中看到了該機(jī)制的一種糟糕的實(shí)現(xiàn)矾瘾,故在此深入分析一下。

問(wèn)題分析及解決方案

服務(wù)端一般會(huì)保持很多個(gè)連接谈喳,所以戈泼,一般是創(chuàng)建一個(gè)定時(shí)器,定時(shí)檢查所有連接中哪些連接超時(shí)了大猛。此外我們要做的是,當(dāng)收到客戶端發(fā)來(lái)的數(shù)據(jù)時(shí)膛壹,怎么去刷新該連接的超時(shí)信息唉堪?

最近看到一種實(shí)現(xiàn)方式是這樣做的:

public class Connection {
    private long lastTime;
    public void refresh() {
        lastTime = System.currentTimeMillis();
    }

    public long getLastTime() {
        return lastTime;
    }
    //......
}

在每次收到客戶端發(fā)來(lái)的數(shù)據(jù)時(shí),調(diào)用refresh方法链方。

然后在定時(shí)器里,用當(dāng)前時(shí)間跟每個(gè)連接的getLastTime()作比較祟蚀,來(lái)判定超時(shí):

public class TimeoutTask  extends TimerTask{
    public void run() {
        long now = System.currentTimeMillis();
        for(Connection c: connections){
            if(now - c.getLastTime()> TIMEOUT_THRESHOLD)
                ;//timeout, do something
        }
    }
}

看到這割卖,可能不少讀者已經(jīng)看出問(wèn)題來(lái)了,那就是內(nèi)存可見(jiàn)性問(wèn)題罢维,調(diào)用refresh方法的線程跟執(zhí)行定時(shí)器的線程肯定不是一個(gè)線程,那run方法中讀到的lastTime就可能是舊值言津,即可能將活躍的連接判定超時(shí),然后被干掉悬槽。

有讀者此時(shí)可能想到了這樣一個(gè)方法,將lastTime加個(gè)volatile修飾初婆,是的,這樣確實(shí)解決了問(wèn)題磅叛,不過(guò),作為服務(wù)端兆龙,很多時(shí)候?qū)π阅苁怯幸蟮那枚旅鎭?lái)看下在我電腦上測(cè)出的一組數(shù)據(jù),測(cè)試代碼如下腋寨,供參考

public class PerformanceTest {
    private static long i;
    private volatile static long vt;
    private static final int TEST_SIZE = 10000000;

    public static void main(String[] args) {
        long time = System.nanoTime();
        for (int n = 0; n < TEST_SIZE; n++)
            vt = System.currentTimeMillis();
        System.out.println(-time + (time = System.nanoTime()));
        for (int n = 0; n < TEST_SIZE; n++)
            i = System.currentTimeMillis();
        System.out.println(-time + (time = System.nanoTime()));
        for (int n = 0; n < TEST_SIZE; n++)
            synchronized (PerformanceTest.class) {
            }
        System.out.println(-time + (time = System.nanoTime()));
        for (int n = 0; n < TEST_SIZE; n++)
            vt++;
        System.out.println(-time + (time = System.nanoTime()));
        for (int n = 0; n < TEST_SIZE; n++)
            vt = i;
        System.out.println(-time + (time = System.nanoTime()));
        for (int n = 0; n < TEST_SIZE; n++)
            i = vt;
        System.out.println(-time + (time = System.nanoTime()));
        for (int n = 0; n < TEST_SIZE; n++)
            i++;
        System.out.println(-time + (time = System.nanoTime()));
        for (int n = 0; n < TEST_SIZE; n++)
            i = n;
        System.out.println(-time + (time = System.nanoTime()));
    }
}

測(cè)試一千萬(wàn)次萄窜,結(jié)果是(耗時(shí)單位:納秒,包含循環(huán)本身的時(shí)間):
238932949 volatile寫+取系統(tǒng)時(shí)間
144317590 普通寫+取系統(tǒng)時(shí)間
135596135 空的同步塊(synchronized)
80042382 volatile變量自增
15875140 volatile寫
6548994 volatile讀
2722555 普通自增
2949571 普通讀寫

從上面的數(shù)據(jù)看來(lái)键兜,volatile寫+取系統(tǒng)時(shí)間的耗時(shí)是很高的穗泵,取系統(tǒng)時(shí)間的耗時(shí)也比較高,跟一次無(wú)競(jìng)爭(zhēng)的同步差不多了火欧,接下來(lái)分析下如何優(yōu)化該超時(shí)時(shí)機(jī)茎截。

首先:同步問(wèn)題是肯定得考慮的,因?yàn)橛锌缇€程的數(shù)據(jù)操作企锌;另外,取系統(tǒng)時(shí)間的操作比較耗時(shí)陡鹃,能否不在每次刷新時(shí)都取時(shí)間烘浦?因?yàn)樗⑿抡{(diào)用在高負(fù)載的情況下很頻繁闷叉。如果不在刷新時(shí)取時(shí)間脊阴,那又該怎么去判定超時(shí)?

我想到的辦法是嘿期,在refresh方法里品擎,僅設(shè)置一個(gè)volatile的boolean變量reset(這應(yīng)該是成本最小的了吧萄传,因?yàn)橐幚硗絾?wèn)題蜜猾,要么同步塊,要么volatile瓣铣,而volatile讀在此處是沒(méi)什么意義的),對(duì)時(shí)間的掌控交給定時(shí)器來(lái)做梦碗,并為每個(gè)連接維護(hù)一個(gè)計(jì)數(shù)器蓖救,每次加一,如果reset被設(shè)置為true了循捺,則計(jì)數(shù)器歸零,并將reset設(shè)為false(因?yàn)橛?jì)數(shù)器只由定時(shí)器維護(hù)念赶,所以不需要做同步處理恰力,從上面的測(cè)試數(shù)據(jù)來(lái)看,普通變量的操作踩萎,時(shí)間成本是很低的),如果計(jì)數(shù)器超過(guò)某個(gè)值董栽,則判定超時(shí)。 下面給出具體的代碼:

public class Connection {
    int count = 0;
    volatile boolean reset = false;
    public void refresh() {
        if (reset == false)
            reset = true;
    }
}

public class TimeoutTask extends TimerTask {
    public void run() {
        for (Connection c : connections) {
            if (c.reset) {
                c.reset = false;
                c.count = 0;
            } else if (++c.count >= TIMEOUT_COUNT)
                ;// timeout, do something
        }
    }
}

代碼中的TIMEOUT_COUNT 等于超時(shí)時(shí)間除以定時(shí)器的周期袁稽,周期大小既影響定時(shí)器的執(zhí)行頻率擒抛,也會(huì)影響實(shí)際超時(shí)時(shí)間的波動(dòng)范圍(這個(gè)波動(dòng),第一個(gè)方案也存在民泵,也不太可能避免,并且也不需要多么精確)栈妆。

代碼很簡(jiǎn)潔厢钧,下面來(lái)分析一下。

reset加上了volatile早直,所以保證了多線程操作的可見(jiàn)性,雖然有兩個(gè)線程都對(duì)變量有寫操作糕韧,但無(wú)論這兩個(gè)線程怎么穿插執(zhí)行,都不會(huì)影響其邏輯含義萤彩。

再說(shuō)下refresh方法斧拍,為什么我在賦值語(yǔ)句上多加了個(gè)條件?這不是多了一次volatile讀操作嗎愚墓?我是這么考慮的,高負(fù)載下浪册,refresh會(huì)被頻繁調(diào)用,意味著reset長(zhǎng)時(shí)間為true议经,那么加上條件后谴返,就不會(huì)執(zhí)行寫操作了,只有一次讀操作籍救,從上面的測(cè)試數(shù)據(jù)來(lái)看,volatile變量的讀操作的性能是顯著優(yōu)于寫操作的蝙昙。只不過(guò)在reset為false的時(shí)候梧却,多了一次讀操作,但此情況在定時(shí)器的一個(gè)周期內(nèi)最多只會(huì)發(fā)一次烈拒,而且對(duì)高負(fù)載情況下的優(yōu)化顯然更有意義广鳍,所以我認(rèn)為加上條件還是值得的。

轉(zhuǎn)載自:https://blog.csdn.net/u014365133/article/details/78502923

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末吨铸,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子诞吱,更是在濱河造成了極大的恐慌竭缝,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,036評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件握巢,死亡現(xiàn)場(chǎng)離奇詭異松却,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)晓锻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)独撇,“玉大人,你說(shuō)我怎么就攤上這事卵史。” “怎么了以躯?”我有些...
    開封第一講書人閱讀 164,411評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵啄踊,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我址晕,道長(zhǎng),這世上最難降的妖魔是什么斩箫? 我笑而不...
    開封第一講書人閱讀 58,622評(píng)論 1 293
  • 正文 為了忘掉前任乘客,我火速辦了婚禮,結(jié)果婚禮上淀歇,老公的妹妹穿的比我還像新娘。我一直安慰自己牡直,他們只是感情好纳决,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著饵史,像睡著了一般胜榔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上夭织,一...
    開封第一講書人閱讀 51,521評(píng)論 1 304
  • 那天,我揣著相機(jī)與錄音讲竿,去河邊找鬼。 笑死题禀,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的投剥。 我是一名探鬼主播江锨,決...
    沈念sama閱讀 40,288評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼糕篇,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了挑豌?” 一聲冷哼從身側(cè)響起墩崩,我...
    開封第一講書人閱讀 39,200評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎铝阐,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體徘键,經(jīng)...
    沈念sama閱讀 45,644評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡遍蟋,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評(píng)論 3 336
  • 正文 我和宋清朗相戀三年虚青,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片棒厘。...
    茶點(diǎn)故事閱讀 39,953評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡绊谭,死狀恐怖政恍,靈堂內(nèi)的尸體忽然破棺而出达传,到底是詐尸還是另有隱情迫筑,我是刑警寧澤宗弯,帶...
    沈念sama閱讀 35,673評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站辕棚,受9級(jí)特大地震影響邓厕,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜详恼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望挽铁。 院中可真熱鬧敞掘,春花似錦、人聲如沸玖雁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)面殖。三九已至,卻和暖如春脊僚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背辽幌。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工乌企, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人加酵。 一個(gè)月前我還...
    沈念sama閱讀 48,119評(píng)論 3 370
  • 正文 我出身青樓哭当,卻偏偏與公主長(zhǎng)得像钦勘,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子彻采,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理肛响,服務(wù)發(fā)現(xiàn)陨溅,斷路器绍在,智...
    卡卡羅2017閱讀 134,656評(píng)論 18 139
  • 國(guó)家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報(bào)批稿:20170802 前言: 排版 ...
    庭說(shuō)閱讀 10,970評(píng)論 6 13
  • 1偿渡、TCP狀態(tài)linux查看tcp的狀態(tài)命令:1)、netstat -nat 查看TCP各個(gè)狀態(tài)的數(shù)量2)溜宽、lso...
    北辰青閱讀 9,423評(píng)論 0 11
  • 零度打卡Day28 主題學(xué)習(xí):習(xí)慣養(yǎng)成訓(xùn)練營(yíng)第四課 也是最后一章節(jié)課程,向上筆記導(dǎo)圖留攒,明天完成作業(yè)嫉嘀。 今日橙思:雞...
    零度2013閱讀 191評(píng)論 0 0
  • 2015年,大二拭宁,11月,周三杰标。 真的好可悲,要翻日歷我才知道如今的月份腔剂,今天的時(shí)間。大學(xué)生活過(guò)去了兩年桶蝎,我才發(fā)現(xiàn)...
    61c1288ca327閱讀 167評(píng)論 0 0