溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

C#中怎么實(shí)現(xiàn)Scoket心跳機(jī)制

發(fā)布時(shí)間:2021-08-06 16:11:08 來源:億速云 閱讀:173 作者:Leah 欄目:編程語言

這篇文章給大家介紹C#中怎么實(shí)現(xiàn)Scoket心跳機(jī)制,內(nèi)容非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。

TCP網(wǎng)絡(luò)長連接

手機(jī)能夠使用聯(lián)網(wǎng)功能是因?yàn)槭謾C(jī)底層實(shí)現(xiàn)了TCP/IP協(xié)議,可以使手機(jī)終端通過無線網(wǎng)絡(luò)建立TCP連接。TCP協(xié)議可以對上層網(wǎng)絡(luò)提供接口,使上層網(wǎng)絡(luò)數(shù)據(jù)的傳輸建立在“無差別”的網(wǎng)絡(luò)之上。

建立起一個(gè)TCP連接需要經(jīng)過“三次握手”:第一次握手:客戶端發(fā)送syn包(syn=j)到服務(wù)器,并進(jìn)入SYN_SEND狀態(tài),等待服務(wù)器確認(rèn);第二次握手:服務(wù)器收到syn包,必須確認(rèn)客戶的SYN(ack=j+1),同時(shí)自己也發(fā)送一個(gè)SYN包(syn=k),即SYN+ACK包,此時(shí)服務(wù)器進(jìn)入SYN_RECV狀態(tài);第三次握手:客戶端收到服務(wù)器的SYN+ACK包,向服務(wù)器發(fā)送確認(rèn)包ACK(ack=k+1),此包發(fā)送完畢,客戶端和服務(wù)器進(jìn)入ESTABLISHED狀態(tài),完成三次握手。

握手過程中傳送的包里不包含數(shù)據(jù),三次握手完畢后,客戶端與服務(wù)器才正式開始傳送數(shù)據(jù)。理想狀態(tài)下,TCP連接一旦建立,在通信雙方中的任何一方主動(dòng)關(guān)閉連接之前,TCP 連接都將被一直保持下去。斷開連接時(shí)服務(wù)器和客戶端均可以主動(dòng)發(fā)起斷開TCP連接的請求,斷開過程需要經(jīng)過“四次握手”(過程就不細(xì)寫了,就是服務(wù)器和客戶端交互,最終確定斷開)

什么是心跳

剛才說到長連接建立連接后,理想狀態(tài)下是不會(huì)斷開的,但是由于網(wǎng)絡(luò)問題,可能導(dǎo)致一方斷開后,另一方仍然在發(fā)送數(shù)據(jù),或者有些客戶端長時(shí)間不發(fā)送消息,服務(wù)器還維持這他的客戶端不必要的引用,增加了服務(wù)器的負(fù)荷。因此我們引入了心跳機(jī)制。

心跳包之所以叫心跳包是因?yàn)椋核裥奶粯用扛艄潭〞r(shí)間發(fā)一次,以此來告訴服務(wù)器,這個(gè)客戶端還活著。事實(shí)上這是為了保持長連接,至于這個(gè)包的內(nèi)容,是沒有什么特別規(guī)定的,不過一般都是很小的包,或者只包含包頭的一個(gè)空包。

總的來說,心跳包主要也就是用于長連接的?;詈蛿嗑€處理。一般的應(yīng)用下,判定時(shí)間在30-40秒比較不錯(cuò)。如果實(shí)在要求高,那就在6-9秒。

怎么發(fā)送心跳?

心跳包的發(fā)送,通常有兩種技術(shù)

方法1:應(yīng)用層自己實(shí)現(xiàn)的心跳包

由應(yīng)用程序自己發(fā)送心跳包來檢測連接是否正常,大致的方法是:服務(wù)器在一個(gè) Timer事件中定時(shí) 向客戶端發(fā)送一個(gè)短小精悍的數(shù)據(jù)包,然后啟動(dòng)一個(gè)低級(jí)別的線程,在該線程中不斷檢測客戶端的回應(yīng), 如果在一定時(shí)間內(nèi)沒有收到客戶端的回應(yīng),即認(rèn)為客戶端已經(jīng)掉線;同樣,如果客戶端在一定時(shí)間內(nèi)沒 有收到服務(wù)器的心跳包,則認(rèn)為連接不可用。

方法2:TCP的KeepAlive?;顧C(jī)制

因?yàn)橐紤]到一個(gè)服務(wù)器通常會(huì)連接多個(gè)客戶端,因此由用戶在應(yīng)用層自己實(shí)現(xiàn)心跳包,代碼較多 且稍顯復(fù)雜,而利用TCP/IP協(xié)議層為內(nèi)置的KeepAlive功能來實(shí)現(xiàn)心跳功能則簡單得多。 不論是服務(wù)端還是客戶端,一方開啟KeepAlive功能后,就會(huì)自動(dòng)在規(guī)定時(shí)間內(nèi)向?qū)Ψ桨l(fā)送心跳包, 而另一方在收到心跳包后就會(huì)自動(dòng)回復(fù),以告訴對方我仍然在線。 因?yàn)殚_啟KeepAlive功能需要消耗額外的寬帶和流量,所以TCP協(xié)議層默認(rèn)并不開啟KeepAlive功 能,盡管這微不足道,但在按流量計(jì)費(fèi)的環(huán)境下增加了費(fèi)用,另一方面,KeepAlive設(shè)置不合理時(shí)可能會(huì) 因?yàn)槎虝旱木W(wǎng)絡(luò)波動(dòng)而斷開健康的TCP連接。并且,默認(rèn)的KeepAlive超時(shí)需要7,200,000 MilliSeconds, 即2小時(shí),探測次數(shù)為5次。對于很多服務(wù)端應(yīng)用程序來說,2小時(shí)的空閑時(shí)間太長。因此,我們需要手工開啟KeepAlive功能并設(shè)置合理的KeepAlive參數(shù)。

心跳檢測步驟:

1客戶端每隔一個(gè)時(shí)間間隔發(fā)生一個(gè)探測包給服務(wù)器2客戶端發(fā)包時(shí)啟動(dòng)一個(gè)超時(shí)定時(shí)器3服務(wù)器端接收到檢測包,應(yīng)該回應(yīng)一個(gè)包4如果客戶機(jī)收到服務(wù)器的應(yīng)答包,則說明服務(wù)器正常,刪除超時(shí)定時(shí)器5如果客戶端的超時(shí)定時(shí)器超時(shí),依然沒有收到應(yīng)答包,則說明服務(wù)器掛了

C#實(shí)現(xiàn)的一個(gè)簡單的心跳

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  {    /// <summary>    /// 客戶端離線提示    /// </summary>    /// <param name="clientInfo"></param>    private static void ClientOffline(ClientInfo clientInfo)    {      Console.WriteLine(String.Format("客戶端{(lán)0}離線,離線時(shí)間:\t{1}", clientInfo.ClientID, clientInfo.LastHeartbeatTime));    }     /// <summary>    /// 客戶端上線提示    /// </summary>    /// <param name="clientInfo"></param>    private static void ClientOnline(ClientInfo clientInfo)    {      Console.WriteLine(String.Format("客戶端{(lán)0}上線,上線時(shí)間:\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個(gè)客戶端      Dictionary<Int32, Client> dicClient = new Dictionary<Int32, Client>();      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;          }        }      }    }  }   /// <summary>  /// 服務(wù)端  /// </summary>  public class Server  {    public event ClientOfflineHandler OnClientOffline;    public event ClientOnlineHandler OnClientOnline;     private Dictionary<Int32, ClientInfo> _DicClient;     /// <summary>    /// 構(gòu)造函數(shù)    /// </summary>    public Server()    {      _DicClient = new Dictionary<Int32, ClientInfo>(100);          }     /// <summary>    /// 開啟服務(wù)端    /// </summary>    public void Start()    {      // 開啟掃描離線線程      Thread t = new Thread(new ThreadStart(ScanOffline));      t.IsBackground = true;      t.Start();    }     /// <summary>    /// 掃描離線    /// </summary>    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;            }             // 判斷最后心跳時(shí)間是否大于3秒            TimeSpan sp = System.DateTime.Now - clientInfo.LastHeartbeatTime;            if (sp.Seconds >= 3)            {              // 離線,觸發(fā)離線事件              if (OnClientOffline != null)              {                OnClientOffline(clientInfo);              }               // 修改狀態(tài)              clientInfo.State = false;            }          }        }      }    }     /// <summary>    /// 接收心跳包    /// </summary>    /// <param name="clientID">客戶端ID</param>    public void ReceiveHeartbeat(Int32 clientID)    {      lock (_DicClient)      {        ClientInfo clientInfo;        if (_DicClient.TryGetValue(clientID, out clientInfo))        {          // 如果客戶端已經(jīng)上線,則更新最后心跳時(shí)間          clientInfo.LastHeartbeatTime = System.DateTime.Now;        }        else        {          // 客戶端不存在,則認(rèn)為是新上線的          clientInfo = new ClientInfo();          clientInfo.ClientID = clientID;          clientInfo.LastHeartbeatTime = System.DateTime.Now;          clientInfo.State = true;           _DicClient.Add(clientID, clientInfo);           // 觸發(fā)上線事件          if (OnClientOnline != null)          {            OnClientOnline(clientInfo);          }        }      }    }  }   /// <summary>  /// 客戶端  /// </summary>  public class Client  {    public Server Server;    public Int32 ClientID;    public Boolean Offline;     /// <summary>    /// 構(gòu)造函數(shù)    /// </summary>    /// <param name="clientID"></param>    /// <param name="server"></param>    public Client(Int32 clientID, Server server)    {      ClientID = clientID;      Server = server;      Offline = false;    }     /// <summary>    /// 開啟客戶端    /// </summary>    public void Start()    {      // 開啟心跳線程      Thread t = new Thread(new ThreadStart(Heartbeat));      t.IsBackground = true;      t.Start();    }     /// <summary>    /// 向服務(wù)器發(fā)送心跳包    /// </summary>    private void Heartbeat()    {      while (!Offline)      {        // 向服務(wù)端發(fā)送心跳包        Server.ReceiveHeartbeat(ClientID);                 System.Threading.Thread.Sleep(1000);      }    }  }   /// <summary>  /// 客戶端信息  /// </summary>  public class ClientInfo  {    // 客戶端ID    public Int32 ClientID;     // 最后心跳時(shí)間    public DateTime LastHeartbeatTime;     // 狀態(tài)    public Boolean State;  }}

關(guān)于C#中怎么實(shí)現(xiàn)Scoket心跳機(jī)制就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI