日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

java心跳 谁发谁_java 心跳机制

發(fā)布時間:2023/12/14 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java心跳 谁发谁_java 心跳机制 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

什么是心跳包?

心跳包就是在客戶端和服務(wù)器間定時通知對方自己狀態(tài)的一個自己定義的命令字,按照一定的時間間隔發(fā)送,類似于心跳,所以叫做心跳包。

用來判斷對方(設(shè)備,進程或其它網(wǎng)元)是否正常運行,采用定時發(fā)送簡單的通訊包,如果在指定時間段內(nèi)未收到對方響應(yīng),則判斷對方已經(jīng)離線。用于檢測TCP的異常斷開。基本原因是服務(wù)器端不能有效的判斷客戶端是否在線,也就是說,服務(wù)器無法區(qū)分客戶端是長時間在空閑,還是已經(jīng)掉線的情況。所謂的心跳包就是客戶端定時發(fā)送簡單的信息給服務(wù)器端告訴它我還在而已。代碼就是每隔幾分鐘發(fā)送一個固定信息給服務(wù)端,服務(wù)端收到后回復(fù)一個固定信息如果服務(wù)端幾分鐘內(nèi)沒有收到客戶端信息則視客戶端斷開。

比如有些通信軟件長時間不使用,要想知道它的狀態(tài)是在線還是離線就需要心跳包,定時發(fā)包收包。發(fā)包方:可以是客戶也可以是服務(wù)端,看哪邊實現(xiàn)方便合理,一般是客戶端。服務(wù)器也可以定時發(fā)心跳下去。一般來說,出于效率的考慮,是由客戶端主動向服務(wù)器端發(fā)包,而不是服務(wù)器向客戶端發(fā)。客戶端每隔一段時間發(fā)一個包,使用TCP的,用send發(fā),使用UDP的,用sendto發(fā),服務(wù)器收到后,就知道當前客戶端還處于“活著”的狀態(tài),否則,如果隔一定時間未收到這樣的包,則服務(wù)器認為客戶端已經(jīng)斷開,進行相應(yīng)的客戶端斷開邏輯處理。

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

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

如果應(yīng)用是基于TCP的,可以簡單地通過SO_KEEPALIVE實現(xiàn)心跳。TCP在設(shè)置的KeepAlive定時器到達時向?qū)Χ税l(fā)一個檢測TCP segment,如果沒收到ACK或RST,嘗試幾次后,就認為對端已經(jīng)不存在,最后通知應(yīng)用程序。這里有個缺點是,Server主動發(fā)出檢測包,對性能有點影響。

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

Client啟動一個定時器,不斷發(fā)心跳;

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

Server啟動一個定時器,判斷Client是否存在,判斷方法這里列兩種:時間差和簡單標志。

1. 時間差策略

收到一個心跳后,記錄當前時間(記為recvedTime)。

判斷定時器時間到達,計算多久沒收到心跳的時間(T)=當前時間 - recvedTime(上面記錄的時間)。如果T大于某個設(shè)定值,就可以認為Client超時了。

2. 簡單標志

收到一個心跳后,設(shè)置連接標志為true;

判斷定時器時間到達,查看所有的標志,false的,認為對端超時了;true的將其設(shè)成false。

上面這種方法比上面簡單一些,但檢測某個Client是否離線的誤差有點大。

您還有心跳嗎?超時機制分析

問題描述

在C/S模式中,有時我們會長時間保持一個連接,以避免頻繁地建立連接,但同時,一般會有一個超時時間,在這個時間內(nèi)沒發(fā)起任何請求的連接會被斷開,以減少負載,節(jié)約資源。并且該機制一般都是在服務(wù)端實現(xiàn),因為client強制關(guān)閉或意外斷開連接,server端在此刻是感知不到的,如果放到client端實現(xiàn),在上述情況下,該超時機制就失效了。本來這問題很普通,不太值得一提,但最近在項目中看到了該機制的一種糟糕的實現(xiàn),故在此深入分析一下。

問題分析及解決方案

服務(wù)端一般會保持很多個連接,所以,一般是創(chuàng)建一個定時器,定時檢查所有連接中哪些連接超時了。此外我們要做的是,當收到客戶端發(fā)來的數(shù)據(jù)時,怎么去刷新該連接的超時信息?

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

public class Connection {

private long lastTime;

public void refresh() {

lastTime = System.currentTimeMillis();

}

public long getLastTime() {

return lastTime;

}

//......

}

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

然后在定時器里,用當前時間跟每個連接的getLastTime()作比較,來判定超時:

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)看出問題來了,那就是內(nèi)存可見性問題,調(diào)用refresh方法的線程跟執(zhí)行定時器的線程肯定不是一個線程,那run方法中讀到的lastTime就可能是舊值,即可能將活躍的連接判定超時,然后被干掉。

有讀者此時可能想到了這樣一個方法,將lastTime加個volatile修飾,是的,這樣確實解決了問題,不過,作為服務(wù)端,很多時候?qū)π阅苁怯幸蟮?#xff0c;下面來看下在我電腦上測出的一組數(shù)據(jù),測試代碼如下,供參考

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()));

}

}

測試一千萬次,結(jié)果是(耗時單位:納秒,包含循環(huán)本身的時間):

238932949 volatile寫+取系統(tǒng)時間

144317590 普通寫+取系統(tǒng)時間

135596135 空的同步塊(synchronized)

80042382 volatile變量自增

15875140 volatile寫

6548994 volatile讀

2722555 普通自增

2949571 普通讀寫

從上面的數(shù)據(jù)看來,volatile寫+取系統(tǒng)時間的耗時是很高的,取系統(tǒng)時間的耗時也比較高,跟一次無競爭的同步差不多了,接下來分析下如何優(yōu)化該超時時機。

首先:同步問題是肯定得考慮的,因為有跨線程的數(shù)據(jù)操作;另外,取系統(tǒng)時間的操作比較耗時,能否不在每次刷新時都取時間?因為刷新調(diào)用在高負載的情況下很頻繁。如果不在刷新時取時間,那又該怎么去判定超時?

我想到的辦法是,在refresh方法里,僅設(shè)置一個volatile的boolean變量reset(這應(yīng)該是成本最小的了吧,因為要處理同步問題,要么同步塊,要么volatile,而volatile讀在此處是沒什么意義的),對時間的掌控交給定時器來做,并為每個連接維護一個計數(shù)器,每次加一,如果reset被設(shè)置為true了,則計數(shù)器歸零,并將reset設(shè)為false(因為計數(shù)器只由定時器維護,所以不需要做同步處理,從上面的測試數(shù)據(jù)來看,普通變量的操作,時間成本是很低的),如果計數(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 等于超時時間除以定時器的周期,周期大小既影響定時器的執(zhí)行頻率,也會影響實際超時時間的波動范圍(這個波動,第一個方案也存在,也不太可能避免,并且也不需要多么精確)。

代碼很簡潔,下面來分析一下。

reset加上了volatile,所以保證了多線程操作的可見性,雖然有兩個線程都對變量有寫操作,但無論這兩個線程怎么穿插執(zhí)行,都不會影響其邏輯含義。

再說下refresh方法,為什么我在賦值語句上多加了個條件?這不是多了一次volatile讀操作嗎?我是這么考慮的,高負載下,refresh會被頻繁調(diào)用,意味著reset長時間為true,那么加上條件后,就不會執(zhí)行寫操作了,只有一次讀操作,從上面的測試數(shù)據(jù)來看,volatile變量的讀操作的性能是顯著優(yōu)于寫操作的。只不過在reset為false的時候,多了一次讀操作,但此情況在定時器的一個周期內(nèi)最多只會發(fā)一次,而且對高負載情況下的優(yōu)化顯然更有意義,所以我認為加上條件還是值得的。

————————————-

補充一下:一般情況下,也可用特定的心跳包來刷新,而不是每次收到消息都刷新,這樣一來,刷新頻率就很低了,也就沒必要太在乎性能開銷。

總結(jié)

以上是生活随笔為你收集整理的java心跳 谁发谁_java 心跳机制的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。