基于Windows Socket 的网络通信中的心跳机制原理
????在采用TCP 連接的C/S 結構的系統中,當通信的一方正常關閉或退出時,另一方能收到相應的連接
斷開的通知,然后進行必要的處理;但如果任意一方發生所謂的“非優雅斷開”,如:意外崩潰、死機、
拔掉網線或路由器故障時,另一方無法得知TCP 連接已經失效,除非繼續在此連接上不斷地發送數據,
經過若干時間后導致錯誤返回。但在很多時候,更希望服務器端和客戶端都能及時有效地檢測到網絡連
接的非正常斷開,然后完成一些必要的清理工作并把錯誤報告給用戶。
????如何及時有效地檢測到通信一方的非正常斷開,采用的方法是通過通信的一方或雙方發送心跳包來
告訴對方網絡通信是否正常或已斷開。
1. 心跳原理
????在基于電路交換的網絡中,有專用的控制信令通道,能夠及時發現通路斷開、故障,而TCP/IP 網絡
中,鏈路的連通只在連接雙方記錄狀態,物理通道內不存在一個實際的連接鏈路,通信的雙方只能定時
發送簡單的信息給另一方,并根據超時來判斷線路是長時間空閑還是已斷開。這種通過每隔一定時間發
送一個固定信息給對方,對方收到后回復一個固定信息,告訴對方“我還在”的方式非常類似于心跳,所
發送的這種簡單信息就稱為“心跳包”。
心跳包的發送,通常有兩種技術:一種是由用戶在應用層實現的心跳包,另一種是由TCP 協議層提
供的KeepAlive 。
2. 應用層自己實現的心跳包
????由應用程序自己發送心跳包來檢測連接是否正常,大致的方法是:服務器在一個Timer 事件中定時
向客戶端發送一個短小精悍的數據包,然后啟動一個低級別的線程,在該線程中不斷檢測客戶端的回應,
如果在一定時間內沒有收到客戶端的回應,即認為客戶端已經掉線;同樣,如果客戶端在一定時間內沒
有收到服務器的心跳包,則認為連接不可用。
?
3. TCP 的KeepAlive 保活機制
????因為要考慮到一個服務器通常會連接多個客戶端,因此由用戶在應用層自己實現心跳包,代碼較多
且稍顯復雜,而利用TCP/IP 協議層為內置的KeepAlive 功能來實現心跳功能則簡單得多。
不論是服務端還是客戶端,一方開啟KeepAlive 功能后,就會自動在規定時間內向對方發送心跳包,
而另一方在收到心跳包后就會自動回復,以告訴對方我仍然在線。
因為開啟KeepAlive 功能需要消耗額外的寬帶和流量,所以TCP 協議層默認并不開啟KeepAlive 功
能,盡管這微不足道,但在按流量計費的環境下增加了費用,另一方面,KeepAlive 設置不合理時可能會
因為短暫的網絡波動而斷開健康的TCP 連接。并且,默認的KeepAlive 超時需要7,200,000 MilliSeconds,
即2 小時,探測次數為5 次。對于很多服務端應用程序來說,2 小時的空閑時間太長。因此,我們需要手
工開啟KeepAlive 功能并設置合理的KeepAlive 參數。
?
目前主要有三種方法來實現用戶掉線檢測:SO_KEEPALIVE ,SIO_KEEPALIVE_VALS 和Heart-Beat線程。
下面我就上面的三種方法來做一下介紹。
(1)SO_KEEPALIVE 機制?
????????這是socket庫提供的功能,設置接口是setsockopt API:
???BOOL??bSet=TRUE;
???setsockopt(hSocket,SOL_SOCKET,SO_KEEPALIVE,(const char*)&bSet,sizeof(BOOL));
???????根據MSDN的文檔,如果為socket設置了KEEPALIVE選項,TCP/IP棧在檢測到對方掉線后,
???任何在該socket上進行的調用(發送/接受調用)就會立刻返回,錯誤號是WSAENETRESET ;
???同時,此后的任何在該socket句柄的調用會立刻失敗,并返回WSAENOTCONN錯誤。
???該機制的缺點也很明顯:
?????????默認設置是空閑2小時才發送一個“保持存活探測分節”,不能保證實時檢測!
???當然也可以修改時間間隔參數,但是會影響到所有打開此選項的套接口!
?????????關聯了完成端口的socket可能會忽略掉該套接字選項。
(2)SIO_KEEPALIVE_VALS 機制?
?????????這是從彭博兄那里學到一個機制拉,設置接口是WSAIoctl API:
?????DWORD dwError = 0L ;
?????tcp_keepalive sKA_Settings = {0}, sReturned = {0} ;
?????sKA_Settings.onoff = 1 ;
?????sKA_Settings.keepalivetime = 5500 ; // Keep Alive in 5.5 sec.
?????sKA_Settings.keepaliveinterval = 3000 ; // Resend if No-Reply?
?????if (WSAIoctl(skNewConnection, SIO_KEEPALIVE_VALS, &sKA_Settings,
??????????sizeof(sKA_Settings), &sReturned, sizeof(sReturned), &dwBytes,
??????????NULL, NULL) != 0)
?????{
???????????dwError = WSAGetLastError() ;
?????}
?????實現時需要添加tcp_keepalive and SIO_KEEPALIVE_VALS的定義文件MSTCPiP.h?
?????該選項不同于SO_KEEPALIVE 機制的就是它是針對單個連接的,對系統其他的套接
?????口并不影響。
????????針對完成端口的socket,設置了SIO_KEEPALIVE_VALS后,激活包由TCP STACK來負責。
?????當網絡連接斷開后,TCP STACK并不主動告訴上層的應用程序,但是當下一次RECV或者SEND操作
?????進行后,馬上就會返回錯誤告訴上層這個連接已經斷開了.如果檢測到斷開的時候,在這個連接
?????上有正在PENDING的IO操作,則馬上會失敗返回.
?????該機制的缺點:
?????????????不通用啦。MS的API只能用于Windows拉。不過,呵呵用彭博兄的評論就是:
?????優雅一些^_^.
????
(3)Heart-Beat線程
????????沒說的。自己寫一個后臺線程,實現Heart-Beat包,客戶端受到該包后,立刻返回相應的反饋??包。
????該方法的好處是通用,但缺點就是會改變現有的通訊協議!
有開發網絡應用經歷的人都知道,網絡中的接收和發送數據都是使用WINDOWS中的SOCKET進行實現。但是如果此套接字已經斷開,那發送數據和接收數據的時候就一定會有問題。可是如何判斷這個套接字是否還可以使用呢?
有人一定想到使用Send函數中的返回結果來進行判斷。如果返回的長度和自己發送出去的長度一致,那就說明這個套接字是可用的,否則此套接字一定出現了問題。但是我們并不是無時無刻的發送數據呀。如何解決呢?
其實TCP中已經為我們實現了一個叫做心跳的機制。如果你設置了心跳,那TCP就會在一定的時間(比如你設置的是3秒鐘)內發送你設置的次數的心跳(比如說2次),并且此信息不會影響你自己定義的協議。
在VC中實現心跳的例子很多,可是在DLEPHI中一直沒有相應的代碼。下面我是我使用DELPHI編寫的關于心跳的代碼(以IOCP為例),希望對大家有幫助。
定義心跳常量
const
??IOC_IN???????????????=$80000000;
??IOC_VENDOR???????????=$18000000;
??IOC_out??????????????=$40000000;
??SIO_KEEPALIVE_VALS???=IOC_IN or IOC_VENDOR or 4;
var
??inKeepAlive,OutKeepAlive:TTCP_KEEPALIVE;
實現代碼是在Acceptsc:= WSAAccept(Listensc, nil, nil, nil, 0);代碼的后面加入:
??????opt:=1;
??????if setsockopt(Acceptsc,SOL_SOCKET,SO_KEEPALIVE,@opt,sizeof(opt))=SOCKET_ERROR then
??????begin
????????closesocket(Acceptsc);
??????end;
??????inKeepAlive.onoff:=1;
??????//設置3秒鐘時間間隔
inKeepAlive.keepalivetime:=3000;
??????//設置每3秒中發送1次的心跳
??????inKeepAlive.keepaliveinterval:=1;
??????insize:=sizeof(TTCP_KEEPALIVE);
??????outsize:=sizeof(TTCP_KEEPALIVE);
??????if WSAIoctl(Accept,SIO_KEEPALIVE_VALS,@inKeepAlive,insize,@outKeepAlive,outsize,@outByte,nil,nil)=SOCKET_ERROR then
??????begin
????????closesocket(Acceptsc);
??????end;
如果加入以上的代碼以后,系統會每3秒中加入一次的心跳。并且如果客戶端斷線以后(網線斷),函數GetQueuedCompletionStatus會返回FALSE。
if (GetQueuedCompletionStatus(CompletionPort, BytesTransferred,DWORD(PerHandleData), POverlapped(PerIoData), INFINITE) = False) then
????????begin
???????????//在這里處理客戶端斷線信息。
continue;
????????end;
以上就是我使用心跳的方法,此方法我已經在我的網絡游戲中使用。情況穩定!
網絡程序容易出現死連接,連接以經無效了,但是連接狀態還是ESTABLISHED狀態,如何識別連接以經失效就需要使用
?
?
------------------------------------------------------------------------------------------------------
下面是方法2的一段可用代碼(經過僧僧鞋子 測試 可用!!!)
---------------------------------------------------------------------------------------
保活機制(心跳機制)下面是一個代碼效果還是挺好的,感謝cooldiyer的資料
typedef struct STcpKeepAlive2???
{???
????DWORD onoff;???
????DWORD keepalivetime;???
????DWORD keepaliveinterval;???
???????
}TCP_KEEP_ALIVE2;???
??
VOID SetKeepLive(SOCKET m_Socket)???
{???
????const char chOpt = 1; // True???
????// Set KeepAlive 開啟保活機制, 防止服務端產生死連接???
????if (setsockopt(m_Socket, SOL_SOCKET, SO_KEEPALIVE, (char *)&chOpt, sizeof(chOpt)) == 0)???
????{???
????????// 設置超時詳細信息???
????????TCP_KEEP_ALIVE2 klive;???
????????klive.onoff = 1; // 啟用保活???
????????klive.keepalivetime = 1000 * 15; // 3分鐘超時 Keep Alive???
????????klive.keepaliveinterval = 1000 * 5; // 重試間隔為5秒 Resend if No-Reply???
????????WSAIoctl???
????????????(???
????????????m_Socket,????
????????????SIO_KEEPALIVE_VALS,???
????????????&klive,???
????????????sizeof(TCP_KEEP_ALIVE2),???
????????????NULL,???
????????????0,???
????????????(unsigned long *)&chOpt,???
????????????0,???
????????????NULL???
????????????);???
????}???
}
?
其中SIO_KEEPALIVE_VALS的值定義為:?const int SIO_KEEPALIVE_VALS ?=IOC_IN | IOC_VENDOR | 4;
?
注:SIO_KEEPALIVE_VALS??只有在winsock2下才可使用
超強干貨來襲 云風專訪:近40年碼齡,通宵達旦的技術人生總結
以上是生活随笔為你收集整理的基于Windows Socket 的网络通信中的心跳机制原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 静态函数调用非静态函数的小例子
- 下一篇: 长连接和Keepalive