温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

C#中怎么实现Scoket心跳机制

发布时间:2021-08-06 16:11:08 来源:亿速云 阅读:177 作者:Leah 栏目:编程语言

这篇文章给大家介绍C#中怎么实现Scoket心跳机制,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。

TCP网络长连接

手机能够使用联网功能是因为手机底层实现了TCP/IP协议,可以使手机终端通过无线网络建立TCP连接。TCP协议可以对上层网络提供接口,使上层网络数据的传输建立在“无差别”的网络之上。

建立起一个TCP连接需要经过“三次握手”:第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。

握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。断开连接时服务器和客户端均可以主动发起断开TCP连接的请求,断开过程需要经过“四次握手”(过程就不细写了,就是服务器和客户端交互,最终确定断开)

什么是心跳

刚才说到长连接建立连接后,理想状态下是不会断开的,但是由于网络问题,可能导致一方断开后,另一方仍然在发送数据,或者有些客户端长时间不发送消息,服务器还维持这他的客户端不必要的引用,增加了服务器的负荷。因此我们引入了心跳机制。

心跳包之所以叫心跳包是因为:它像心跳一样每隔固定时间发一次,以此来告诉服务器,这个客户端还活着。事实上这是为了保持长连接,至于这个包的内容,是没有什么特别规定的,不过一般都是很小的包,或者只包含包头的一个空包。

总的来说,心跳包主要也就是用于长连接的保活和断线处理。一般的应用下,判定时间在30-40秒比较不错。如果实在要求高,那就在6-9秒。

怎么发送心跳?

心跳包的发送,通常有两种技术

方法1:应用层自己实现的心跳包

由应用程序自己发送心跳包来检测连接是否正常,大致的方法是:服务器在一个 Timer事件中定时 向客户端发送一个短小精悍的数据包,然后启动一个低级别的线程,在该线程中不断检测客户端的回应, 如果在一定时间内没有收到客户端的回应,即认为客户端已经掉线;同样,如果客户端在一定时间内没 有收到服务器的心跳包,则认为连接不可用。

方法2:TCP的KeepAlive保活机制

因为要考虑到一个服务器通常会连接多个客户端,因此由用户在应用层自己实现心跳包,代码较多 且稍显复杂,而利用TCP/IP协议层为内置的KeepAlive功能来实现心跳功能则简单得多。 不论是服务端还是客户端,一方开启KeepAlive功能后,就会自动在规定时间内向对方发送心跳包, 而另一方在收到心跳包后就会自动回复,以告诉对方我仍然在线。 因为开启KeepAlive功能需要消耗额外的宽带和流量,所以TCP协议层默认并不开启KeepAlive功 能,尽管这微不足道,但在按流量计费的环境下增加了费用,另一方面,KeepAlive设置不合理时可能会 因为短暂的网络波动而断开健康的TCP连接。并且,默认的KeepAlive超时需要7,200,000 MilliSeconds, 即2小时,探测次数为5次。对于很多服务端应用程序来说,2小时的空闲时间太长。因此,我们需要手工开启KeepAlive功能并设置合理的KeepAlive参数。

心跳检测步骤:

1客户端每隔一个时间间隔发生一个探测包给服务器2客户端发包时启动一个超时定时器3服务器端接收到检测包,应该回应一个包4如果客户机收到服务器的应答包,则说明服务器正常,删除超时定时器5如果客户端的超时定时器超时,依然没有收到应答包,则说明服务器挂了

C#实现的一个简单的心跳

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("客户端{0}离线,离线时间:\t{1}", clientInfo.ClientID, clientInfo.LastHeartbeatTime));    }     /// <summary>    /// 客户端上线提示    /// </summary>    /// <param name="clientInfo"></param>    private static void ClientOnline(ClientInfo clientInfo)    {      Console.WriteLine(String.Format("客户端{0}上线,上线时间:\t{1}", clientInfo.ClientID, clientInfo.LastHeartbeatTime));    }     static void Main()    {      // 服务端      Server server = new Server();       // 服务端离线事件      server.OnClientOffline += ClientOffline;       // 服务器上线事件      server.OnClientOnline += ClientOnline;       // 开启服务器      server.Start();       // 模拟100个客户端      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>  /// 服务端  /// </summary>  public class Server  {    public event ClientOfflineHandler OnClientOffline;    public event ClientOnlineHandler OnClientOnline;     private Dictionary<Int32, ClientInfo> _DicClient;     /// <summary>    /// 构造函数    /// </summary>    public Server()    {      _DicClient = new Dictionary<Int32, ClientInfo>(100);          }     /// <summary>    /// 开启服务端    /// </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];             // 如果已经离线则不用管            if (!clientInfo.State)            {              continue;            }             // 判断最后心跳时间是否大于3秒            TimeSpan sp = System.DateTime.Now - clientInfo.LastHeartbeatTime;            if (sp.Seconds >= 3)            {              // 离线,触发离线事件              if (OnClientOffline != null)              {                OnClientOffline(clientInfo);              }               // 修改状态              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))        {          // 如果客户端已经上线,则更新最后心跳时间          clientInfo.LastHeartbeatTime = System.DateTime.Now;        }        else        {          // 客户端不存在,则认为是新上线的          clientInfo = new ClientInfo();          clientInfo.ClientID = clientID;          clientInfo.LastHeartbeatTime = System.DateTime.Now;          clientInfo.State = true;           _DicClient.Add(clientID, clientInfo);           // 触发上线事件          if (OnClientOnline != null)          {            OnClientOnline(clientInfo);          }        }      }    }  }   /// <summary>  /// 客户端  /// </summary>  public class Client  {    public Server Server;    public Int32 ClientID;    public Boolean Offline;     /// <summary>    /// 构造函数    /// </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>    /// 向服务器发送心跳包    /// </summary>    private void Heartbeat()    {      while (!Offline)      {        // 向服务端发送心跳包        Server.ReceiveHeartbeat(ClientID);                 System.Threading.Thread.Sleep(1000);      }    }  }   /// <summary>  /// 客户端信息  /// </summary>  public class ClientInfo  {    // 客户端ID    public Int32 ClientID;     // 最后心跳时间    public DateTime LastHeartbeatTime;     // 状态    public Boolean State;  }}

关于C#中怎么实现Scoket心跳机制就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI