TCP網(wǎng)絡(luò)長連接
目前創(chuàng)新互聯(lián)公司已為上1000+的企業(yè)提供了網(wǎng)站建設(shè)、域名、網(wǎng)頁空間、網(wǎng)站托管維護、企業(yè)網(wǎng)站設(shè)計、東湖網(wǎng)站維護等服務(wù),公司將堅持客戶導(dǎo)向、應(yīng)用為本的策略,正道將秉承"和諧、參與、激情"的文化,與客戶和合作伙伴齊心協(xié)力一起成長,共同發(fā)展。
手機能夠使用聯(lián)網(wǎng)功能是因為手機底層實現(xiàn)了TCP/IP協(xié)議,可以使手機終端通過無線網(wǎng)絡(luò)建立TCP連接。TCP協(xié)議可以對上層網(wǎng)絡(luò)提供接口,使上層網(wǎng)絡(luò)數(shù)據(jù)的傳輸建立在“無差別”的網(wǎng)絡(luò)之上。
建立起一個TCP連接需要經(jīng)過“三次握手”:
第一次握手:客戶端發(fā)送syn包(syn=j)到服務(wù)器,并進入SYN_SEND狀態(tài),等待服務(wù)器確認;
第二次握手:服務(wù)器收到syn包,必須確認客戶的SYN(ack=j+1),同時自己也發(fā)送一個SYN包(syn=k),即SYN+ACK包,此時服務(wù)器進入SYN_RECV狀態(tài);
第三次握手:客戶端收到服務(wù)器的SYN+ACK包,向服務(wù)器發(fā)送確認包ACK(ack=k+1),此包發(fā)送完畢,客戶端和服務(wù)器進入ESTABLISHED狀態(tài),完成三次握手。
握手過程中傳送的包里不包含數(shù)據(jù),三次握手完畢后,客戶端與服務(wù)器才正式開始傳送數(shù)據(jù)。理想狀態(tài)下,TCP連接一旦建立,在通信雙方中的任何一方主動關(guān)閉連接之前,TCP 連接都將被一直保持下去。斷開連接時服務(wù)器和客戶端均可以主動發(fā)起斷開TCP連接的請求,斷開過程需要經(jīng)過“四次握手”(過程就不細寫了,就是服務(wù)器和客戶端交互,最終確定斷開)
什么是心跳
剛才說到長連接建立連接后,理想狀態(tài)下是不會斷開的,但是由于網(wǎng)絡(luò)問題,可能導(dǎo)致一方斷開后,另一方仍然在發(fā)送數(shù)據(jù),或者有些客戶端長時間不發(fā)送消息,服務(wù)器還維持這他的客戶端不必要的引用,增加了服務(wù)器的負荷。因此我們引入了心跳機制。
心跳包之所以叫心跳包是因為:它像心跳一樣每隔固定時間發(fā)一次,以此來告訴服務(wù)器,這個客戶端還活著。事實上這是為了保持長連接,至于這個包的內(nèi)容,是沒有什么特別規(guī)定的,不過一般都是很小的包,或者只包含包頭的一個空包。
總的來說,心跳包主要也就是用于長連接的保活和斷線處理。一般的應(yīng)用下,判定時間在30-40秒比較不錯。如果實在要求高,那就在6-9秒。
怎么發(fā)送心跳?
心跳包的發(fā)送,通常有兩種技術(shù)
方法1:應(yīng)用層自己實現(xiàn)的心跳包
由應(yīng)用程序自己發(fā)送心跳包來檢測連接是否正常,大致的方法是:服務(wù)器在一個 Timer事件中定時 向客戶端發(fā)送一個短小精悍的數(shù)據(jù)包,然后啟動一個低級別的線程,在該線程中不斷檢測客戶端的回應(yīng), 如果在一定時間內(nèi)沒有收到客戶端的回應(yīng),即認為客戶端已經(jīng)掉線;同樣,如果客戶端在一定時間內(nèi)沒 有收到服務(wù)器的心跳包,則認為連接不可用。
方法2:TCP的KeepAlive保活機制
因為要考慮到一個服務(wù)器通常會連接多個客戶端,因此由用戶在應(yīng)用層自己實現(xiàn)心跳包,代碼較多 且稍顯復(fù)雜,而利用TCP/IP協(xié)議層為內(nèi)置的KeepAlive功能來實現(xiàn)心跳功能則簡單得多。 不論是服務(wù)端還是客戶端,一方開啟KeepAlive功能后,就會自動在規(guī)定時間內(nèi)向?qū)Ψ桨l(fā)送心跳包, 而另一方在收到心跳包后就會自動回復(fù),以告訴對方我仍然在線。 因為開啟KeepAlive功能需要消耗額外的寬帶和流量,所以TCP協(xié)議層默認并不開啟KeepAlive功 能,盡管這微不足道,但在按流量計費的環(huán)境下增加了費用,另一方面,KeepAlive設(shè)置不合理時可能會 因為短暫的網(wǎng)絡(luò)波動而斷開健康的TCP連接。并且,默認的KeepAlive超時需要7,200,000 MilliSeconds, 即2小時,探測次數(shù)為5次。對于很多服務(wù)端應(yīng)用程序來說,2小時的空閑時間太長。因此,我們需要手工開啟KeepAlive功能并設(shè)置合理的KeepAlive參數(shù)。
心跳檢測步驟:
1客戶端每隔一個時間間隔發(fā)生一個探測包給服務(wù)器
2客戶端發(fā)包時啟動一個超時定時器
3服務(wù)器端接收到檢測包,應(yīng)該回應(yīng)一個包
4如果客戶機收到服務(wù)器的應(yīng)答包,則說明服務(wù)器正常,刪除超時定時器
5如果客戶端的超時定時器超時,依然沒有收到應(yīng)答包,則說明服務(wù)器掛了
C#實現(xiàn)的一個簡單的心跳
using System; using System.Collections.Generic; using System.Threading; namespace ConsoleApplication1 { // 客戶端離線委托 public delegate void ClientOfflineHandler(ClientInfo client); // 客戶端上線委托 public delegate void ClientOnlineHandler(ClientInfo client); public class Program { ////// 客戶端離線提示 /// /// private static void ClientOffline(ClientInfo clientInfo) { Console.WriteLine(String.Format("客戶端{0}離線,離線時間:\t{1}", clientInfo.ClientID, clientInfo.LastHeartbeatTime)); } ////// 客戶端上線提示 /// /// private static void ClientOnline(ClientInfo clientInfo) { Console.WriteLine(String.Format("客戶端{0}上線,上線時間:\t{1}", clientInfo.ClientID, clientInfo.LastHeartbeatTime)); } static void Main() { // 服務(wù)端 Server server = new Server(); // 服務(wù)端離線事件 server.OnClientOffline += ClientOffline; // 服務(wù)器上線事件 server.OnClientOnline += ClientOnline; // 開啟服務(wù)器 server.Start(); // 模擬100個客戶端 DictionarydicClient = new Dictionary (); for (Int32 i = 0; i < 100; i++) { // 這里傳入server只是為了方便而已 Client client = new Client(i + 1, server); dicClient.Add(i + 1, client); // 開啟客戶端 client.Start(); } System.Threading.Thread.Sleep(1000); while (true) { Console.WriteLine("請輸入要離線的ClientID,輸入0則退出程序:"); String clientID = Console.ReadLine(); if (!String.IsNullOrEmpty(clientID)) { Int32 iClientID = 0; Int32.TryParse(clientID, out iClientID); if (iClientID > 0) { Client client; if (dicClient.TryGetValue(iClientID, out client)) { // 客戶端離線 client.Offline = true; } } else { return; } } } } } /// /// 服務(wù)端 /// public class Server { public event ClientOfflineHandler OnClientOffline; public event ClientOnlineHandler OnClientOnline; private Dictionary_DicClient; /// /// 構(gòu)造函數(shù) /// public Server() { _DicClient = new Dictionary(100); } /// /// 開啟服務(wù)端 /// public void Start() { // 開啟掃描離線線程 Thread t = new Thread(new ThreadStart(ScanOffline)); t.IsBackground = true; t.Start(); } ////// 掃描離線 /// private void ScanOffline() { while (true) { // 一秒掃描一次 System.Threading.Thread.Sleep(1000); lock (_DicClient) { foreach (Int32 clientID in _DicClient.Keys) { ClientInfo clientInfo = _DicClient[clientID]; // 如果已經(jīng)離線則不用管 if (!clientInfo.State) { continue; } // 判斷最后心跳時間是否大于3秒 TimeSpan sp = System.DateTime.Now - clientInfo.LastHeartbeatTime; if (sp.Seconds >= 3) { // 離線,觸發(fā)離線事件 if (OnClientOffline != null) { OnClientOffline(clientInfo); } // 修改狀態(tài) clientInfo.State = false; } } } } } ////// 接收心跳包 /// /// 客戶端ID public void ReceiveHeartbeat(Int32 clientID) { lock (_DicClient) { ClientInfo clientInfo; if (_DicClient.TryGetValue(clientID, out clientInfo)) { // 如果客戶端已經(jīng)上線,則更新最后心跳時間 clientInfo.LastHeartbeatTime = System.DateTime.Now; } else { // 客戶端不存在,則認為是新上線的 clientInfo = new ClientInfo(); clientInfo.ClientID = clientID; clientInfo.LastHeartbeatTime = System.DateTime.Now; clientInfo.State = true; _DicClient.Add(clientID, clientInfo); // 觸發(fā)上線事件 if (OnClientOnline != null) { OnClientOnline(clientInfo); } } } } } ////// 客戶端 /// public class Client { public Server Server; public Int32 ClientID; public Boolean Offline; ////// 構(gòu)造函數(shù) /// /// /// public Client(Int32 clientID, Server server) { ClientID = clientID; Server = server; Offline = false; } ////// 開啟客戶端 /// public void Start() { // 開啟心跳線程 Thread t = new Thread(new ThreadStart(Heartbeat)); t.IsBackground = true; t.Start(); } ////// 向服務(wù)器發(fā)送心跳包 /// private void Heartbeat() { while (!Offline) { // 向服務(wù)端發(fā)送心跳包 Server.ReceiveHeartbeat(ClientID); System.Threading.Thread.Sleep(1000); } } } ////// 客戶端信息 /// public class ClientInfo { // 客戶端ID public Int32 ClientID; // 最后心跳時間 public DateTime LastHeartbeatTime; // 狀態(tài) public Boolean State; } }
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持創(chuàng)新互聯(lián)。