java udp心跳机制_心跳包机制整理汇总
【背景】
現(xiàn)需要實(shí)現(xiàn)這樣的功能:有多個客戶端連著同一個服務(wù)器。服務(wù)器和客戶端之間需要“互相”知道彼此的連接狀態(tài)。比如在某一時刻,服務(wù)器需要知道當(dāng)前有多少個客戶端正在和其通信;某一個時刻,某個客戶端需要知道自己是否和服務(wù)器保持連接。如果在某一時刻,一個客戶端關(guān)閉了,服務(wù)端應(yīng)能及時感覺到;同樣,如果服務(wù)端被關(guān)閉,所有的客戶端應(yīng)能及時感覺到,并作出一些反應(yīng)。
1.從程序的角度看待TCP掉線
TCP掉線的原因可能多種多樣、不一而足,比如,客人的電腦突然斷電、OS崩潰、路由器重啟、網(wǎng)線接觸不良、因?yàn)镻2P下載軟件而導(dǎo)致網(wǎng)絡(luò)資源短缺、Internet網(wǎng)絡(luò)的不穩(wěn)定等等,但是從程序的角度來說,我們可以總結(jié)為兩種情況:程序能立即感知的掉線和程序不能立即感知的掉線。
程序能立即感知的掉線:也就是說客戶端一掉線,服務(wù)器端的某個讀寫對應(yīng)的TCP連接的線程就會拋出異常,這種情況相對容易處理。
程序不能立即感知的掉線:我們都知道,TCP連接的建立,需要經(jīng)過三次握手;而TCP連接的斷開,需要經(jīng)過四次揮手。掉線通常沒什么大不了的,掉就掉了唄,只要四次揮手順利完成后,服務(wù)器和客戶端分別做一些善后處理就可以。
麻煩的事情在于,連接在沒有機(jī)會完成4次揮手時已經(jīng)斷開了(比如當(dāng)客人的電腦系統(tǒng)死機(jī),或客人電腦與服務(wù)器之間的某處物理網(wǎng)線斷開),而服務(wù)端以為客戶端還正常在線,而客戶端也自以為還正常在線。這種程序?qū)ΜF(xiàn)實(shí)狀態(tài)的錯誤判斷有可能引發(fā)諸多悲劇。比如,在此情況下,客戶端發(fā)一個指令給服務(wù)器,服務(wù)器因?yàn)闆]有收到而一直處于等待指令的狀態(tài);而客戶端了,以為服務(wù)器已經(jīng)收到了,也就一直處于等待服務(wù)端回復(fù)的狀態(tài)。如果程序的其它部分需要依據(jù)當(dāng)前的狀態(tài)來做后續(xù)的操作,那就可能會出問題,因?yàn)槌绦驅(qū)Ξ?dāng)前連接狀態(tài)的判斷是錯誤的。
毫無疑問,這種對連接狀態(tài)錯誤的判斷所持續(xù)的時間越久,帶來可能的危害就越大。當(dāng)然,如果我們不做任何額外的處理措施,服務(wù)器到最后也能感受到客戶端的掉線,但是,這個時間可能已經(jīng)過去了幾分鐘甚至幾十分鐘。對于大多數(shù)應(yīng)用來說,這是不可忍受的。 所以,針對這種不能立即感知掉線的情況,我們要做的補(bǔ)救措施,就是幫助程序盡快地獲知tcp連接已斷開的信息。
首先,我們可以在Socket上通過Socket.IOControl方法設(shè)置KeepAliveValues,來控制底層TCP的保活機(jī)制,比如,設(shè)定2秒鐘檢測一次,超過10秒檢測失敗時拋出異常。
byte[]?inOptionValues?=?FillKeepAliveStruct(1,?10000,?2000);
socket.IOControl(IOControlCode.KeepAliveValues,?inOptionValues,?null);
據(jù)我們的經(jīng)驗(yàn),這種設(shè)定可以解決一部分問題,但是仍然會有一些連接在斷開后,遠(yuǎn)遠(yuǎn)超過10秒才被感知掉。所以,這個補(bǔ)救措施還是遠(yuǎn)遠(yuǎn)不夠的。我們還需要在應(yīng)用層加入我們自己的TCP連接狀態(tài)檢測機(jī)制,這種機(jī)制就是通常所說的“心跳”。
2."心跳"機(jī)制
心跳機(jī)制的原理很簡單:客戶端每隔N秒向服務(wù)端發(fā)送一個心跳消息,服務(wù)端收到心跳消息后,回復(fù)同樣的心跳消息給客戶端。如果服務(wù)端或客戶端在M秒(M>N)內(nèi)都沒有收到包括心跳消息在內(nèi)的任何消息,即心跳超時,我們就認(rèn)為目標(biāo)TCP連接已經(jīng)斷開了。
由于不同的應(yīng)用程序?qū)Ω兄猅CP掉線的靈敏度不一樣,所以,N和M的值就可以設(shè)定的不一樣。靈敏度要求越高,N和M就要越小;靈敏度要求越低,N和M就可以越大。而要求靈敏度越高,也是有代價的,那就是需要更頻繁地發(fā)送心跳消息,如果有幾千個連接同時頻繁地發(fā)送心跳消息,那么其所消耗的資源也是不能忽略的。
當(dāng)然,網(wǎng)絡(luò)環(huán)境(如延遲的大小)的好壞,也對會對N和M的值的設(shè)定產(chǎn)生影響,比如,網(wǎng)絡(luò)延遲較大,那么N與M之間的差值也應(yīng)該越大(比如,M是N的3倍)。否則,可能會產(chǎn)生誤判 -- 即TCP連接沒有斷開,只是因?yàn)榫W(wǎng)絡(luò)延遲大才及時沒收到心跳消息,我們卻認(rèn)為連接已經(jīng)斷開了。
ESFramework內(nèi)置了心跳機(jī)制,當(dāng)心跳超時時,服務(wù)端會觸發(fā)IUserManager的SomeOneTimeOuted事件,來通知我們的應(yīng)用程序。
在服務(wù)器端,UserManager通過ESBasic.Threading.Application.HeartBeatChecker來對心跳進(jìn)行檢測,而HeartBeatChecker的SurviveSpanInSecs屬性可以用于設(shè)置我們所描述的M值。
在客戶端,則通過ESPlus.Application.Basic.Passive.HeartBeater來向服務(wù)器定時發(fā)送心跳消息,而HeartBeater的DetectSpanInSecs屬性可以用于設(shè)置N值。
當(dāng)我們在使用Rapid引擎時,Rapid引擎已經(jīng)將心跳機(jī)制的組件為我們組裝好了。由于RapidServerEngine和RapidPassiveEngine沒有暴露出HeartBeatChecker和HeartBeater,所以,我們不能直接通過HeartBeatChecker和HeartBeater設(shè)定M和N的值,但是,RapidServerEngine和RapidPassiveEngine分別提供了HeartbeatTimeoutInSecs屬性和HeartBeatSpanInSecs屬性來間接地設(shè)定M和N。
3.必須關(guān)閉掉線的TCP連接
無論是普通掉線(立即感知)還是心跳超時掉線(非立即感知),都需要關(guān)閉對應(yīng)的TCP連接以釋放系統(tǒng)資源。
ITcpServerEngine接口提供了CloseOneConnection方法以關(guān)閉目標(biāo)連接。
///
/// 主動關(guān)閉連接,將觸發(fā)SomeOneDisconnected事件。
///
void CloseOneConnection(UserAddress adderss, DisconnectedType disconnectedType);
當(dāng)普通掉線時,ITcpServerEngine會自動關(guān)閉了TCP連接;但是,當(dāng)心跳超時掉線時,我們需要自己手動關(guān)閉對應(yīng)的連接。幸運(yùn)的是,ESPlus.Application.Basic空間下的組件會自動幫我們關(guān)閉超時掉線的連接。所以,使用Rapid引擎的我們也不用再自己手動關(guān)閉超時掉線的TCP連接了。
另外要提醒一點(diǎn),當(dāng)TCP連接超時掉線時,使用Rapid引擎的服務(wù)端會首先觸發(fā)IUserManager的SomeOneTimeOuted事件,接著再觸發(fā)IUserManager的SomeOneDisconnected事件(由于ESPlus調(diào)用CloseOneConnection方法時觸發(fā))。
4.UDP與"心跳"
前面介紹的都是關(guān)于TCP的掉線的問題,下面我們看看UDP。
由于UDP是無連接的協(xié)議,所以,當(dāng)我們在使用ESFramework的UDP引擎的時候,幾乎肯定是需要配備心跳機(jī)制的,使用心跳消息確認(rèn)客戶端還在線,以保證服務(wù)端不會過早釋放對應(yīng)的Session或長期保留已失效的Session。
ESFramework中的心跳機(jī)制相關(guān)的組件是與協(xié)議無關(guān)的,所以既可以用于TCP應(yīng)用,也可用于UDP應(yīng)用。
在ESFramework 開發(fā)手冊(04) -- 可靠的P2P 一文中介紹的P2P通道如果是基于UDP的,則ESPlus內(nèi)部也啟動了心跳機(jī)制,以保證在基于UDP的P2P通道斷開時,ESPlus能盡快感知,并關(guān)閉對應(yīng)的P2P通道。
5.關(guān)閉心跳機(jī)制
比如,在LAN中進(jìn)行通信的分布式系統(tǒng),由于網(wǎng)絡(luò)延遲和意外掉線的幾率微乎其微,所以,可以考慮關(guān)閉心跳機(jī)制。再比如,當(dāng)我們斷點(diǎn)調(diào)試客戶端程序時,由于斷點(diǎn)時間太久,服務(wù)端會判斷為客戶端已經(jīng)心跳超時掉線了,在這種情況下,也可以關(guān)閉心跳機(jī)制。那么如何關(guān)閉心跳機(jī)制了?可以這樣做:
將RapidPassiveEngine的HeartBeatSpanInSecs屬性設(shè)置為0。這樣客戶端就不會發(fā)送定時的心跳消息了。
將RapidServerEngine的HeartbeatTimeoutInSecs屬性設(shè)置為小于等于0。這表示服務(wù)端將不再做心跳超時檢查。
【思考】
看到這個需求,直觀上的反應(yīng)就是在服務(wù)端維護(hù)一個在線列表。當(dāng)服務(wù)端的監(jiān)聽器監(jiān)聽到一個連接,就把該連接對應(yīng)的客戶端信息加入這個在線列表。這樣就完成了對上線狀況的記錄。但下一個問題是如何讓服務(wù)器知道客戶端的離線狀況呢?我們可能會想到,讓客戶端在關(guān)閉前發(fā)送一個消息到服務(wù)端,服務(wù)端收到消息后就把客戶端置為離線狀態(tài)。但是,在更多情況下,客戶端并不是這么“友好”地關(guān)閉的。應(yīng)用程序崩潰、網(wǎng)絡(luò)連接被重置、機(jī)器死機(jī)等情況下,客戶端來不及發(fā)送“離線通知”給服務(wù)端就掛掉了。這時,需要有一套機(jī)制,能讓服務(wù)端和客戶端彼此對對方的在線狀態(tài)保持清醒。
【概念】
何謂“心跳”??心跳就是指“活著”的客戶端或服務(wù)端每隔一定的時間就互相發(fā)送接收一個消息,告訴對方自己“活著”。當(dāng)客戶端或服務(wù)端超過一定的時間間隔尚未收到對方的“心跳”消息,就認(rèn)為對方“死了”。這就是“心跳機(jī)制”的核心思想。
【設(shè)計實(shí)現(xiàn)】
在客戶端,除了?UI?外,需要三個線程在后臺工作。
1,自動連接的線程。該線程可以實(shí)現(xiàn)每隔指定時間就檢查一次連接狀態(tài),如果發(fā)現(xiàn)當(dāng)前是“離線”狀態(tài),就自動發(fā)起向服務(wù)端的一次連接。
1?????????private?void?ThreadConnect()
2?????????{
3?????????????do
4?????????????{
5
6?????????????????if?(!_bConnected)
7?????????????????{
8?????????????????????_bConnected?=?_sender.Connect(_ip,?_port);
9
10?????????????????????if?(_bConnected)
11?????????????????????{
12
13?????????????????????????Thread?threadSendAndReceivePulseMessage?=?new?Thread(new?ThreadStart(ThreadSendAndReceivePulseMessage));
14?????????????????????????threadSendAndReceivePulseMessage.IsBackground?=?true;
15?????????????????????????threadSendAndReceivePulseMessage.Start();
16
17?????????????????????????Thread?threadCheckPulseCount?=?new?Thread(new?ThreadStart(ThreadCheckPulseCount));
18?????????????????????????threadCheckPulseCount.IsBackground?=?true;
19?????????????????????????threadCheckPulseCount.Start();
20
21?????????????????????????_pulseCount?=?0;
22
23?????????????????????????OnConnected(new?EventArgs());
24?????????????????????}
25
26?????????????????}
27?????????????????Thread.Sleep(_connectInterval);
28
29?????????????}
30?????????????while?(_bWorking?&&?_bAutoReconnect);
31?????????}
2,收發(fā)“心跳”消息的線程。該線程和服務(wù)端進(jìn)行收發(fā)心跳消息。注意每收到服務(wù)器發(fā)來的消息,應(yīng)將心跳計數(shù)器置零。心跳計數(shù)器的含義是已經(jīng)隔了多少個心跳周期沒收到心跳消息了。
1?????????private?void?ThreadSendAndReceivePulseMessage()
2?????????{
3?????????????while?(_bWorking?&&?_bConnected)
4?????????????{
5
6?????????????????string?recv?=??_sender.Receive(64);
7
8
9?????????????????if?(recv?==?"PULSE")
10?????????????????{
11?????????????????????_pulseCount?=?0;
12
13?????????????????????_sender.Send("ALIVE");
14?????????????????}
15?????????????????else
16?????????????????{
17?????????????????????_bConnected?=?false;
18?????????????????????_sender.Close();
19
20?????????????????}
21?????????????????Thread.Sleep(10);
22?????????????}
23?????????}
3,檢查心跳計數(shù)器的值的線程。該線程每隔指定的時間間隔就檢查一次心跳計數(shù)器,當(dāng)發(fā)現(xiàn)已經(jīng)超過指定心跳周期(比如3次)未接收到心跳消息,就認(rèn)為是離線了,則進(jìn)行相應(yīng)的處理。
1?????????private?void?ThreadCheckPulseCount()
2?????????{
3?????????????while?(_bWorking?&&?_bConnected)
4?????????????{
5?????????????????Thread.Sleep(_pulseInterval);
6
7?????????????????_pulseCount++;
8
9?????????????????if?(_pulseCount?>?_maxPulseCount)
10?????????????????{
11?????????????????????_bConnected?=?false;
12?????????????????????_sender.Close();
13?????????????????}
14
15?????????????????if?(!_bConnected)
16?????????????????{
17?????????????????????OnDisconnected(new?EventArgs());
18?????????????????}
19?????????????}
20?????????}
在服務(wù)端,設(shè)計思想類似,需要維護(hù)一個“在線列表”,并及時和客戶端通信,此處省略代碼。
總結(jié)
以上是生活随笔為你收集整理的java udp心跳机制_心跳包机制整理汇总的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于同态加密体制的安全多方计算
- 下一篇: 后疫情时代的酒旅业,让用户“安心”成为行