• C#之实现Scoket心跳机制


     

    C#之实现Scoket心跳机制

    标签: UnityC#TCPSocket心跳
     分类:
     
     

    目录(?)[+]

     

    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#实现的一个简单的心跳

    [csharp] view plain copy
     
    1. using System;  
    2. using System.Collections.Generic;  
    3. using System.Threading;  
    4.    
    5. namespace ConsoleApplication1  
    6. {  
    7.     // 客户端离线委托  
    8.     public delegate void ClientOfflineHandler(ClientInfo client);  
    9.    
    10.     // 客户端上线委托  
    11.     public delegate void ClientOnlineHandler(ClientInfo client);  
    12.    
    13.     public class Program  
    14.     {  
    15.         /// <summary>  
    16.         /// 客户端离线提示  
    17.         /// </summary>  
    18.         /// <param name="clientInfo"></param>  
    19.         private static void ClientOffline(ClientInfo clientInfo)  
    20.         {  
    21.             Console.WriteLine(String.Format("客户端{0}离线,离线时间: {1}", clientInfo.ClientID, clientInfo.LastHeartbeatTime));  
    22.         }  
    23.    
    24.         /// <summary>  
    25.         /// 客户端上线提示  
    26.         /// </summary>  
    27.         /// <param name="clientInfo"></param>  
    28.         private static void ClientOnline(ClientInfo clientInfo)  
    29.         {  
    30.             Console.WriteLine(String.Format("客户端{0}上线,上线时间: {1}", clientInfo.ClientID, clientInfo.LastHeartbeatTime));  
    31.         }  
    32.    
    33.         static void Main()  
    34.         {  
    35.             // 服务端  
    36.             Server server = new Server();  
    37.    
    38.             // 服务端离线事件  
    39.             server.OnClientOffline += ClientOffline;  
    40.    
    41.             // 服务器上线事件  
    42.             server.OnClientOnline += ClientOnline;  
    43.    
    44.             // 开启服务器  
    45.             server.Start();  
    46.    
    47.             // 模拟100个客户端  
    48.             Dictionary<Int32, Client> dicClient = new Dictionary<Int32, Client>();  
    49.             for (Int32 i = 0; i < 100; i++)  
    50.             {  
    51.                 // 这里传入server只是为了方便而已  
    52.                 Client client = new Client(i + 1, server);  
    53.                 dicClient.Add(i + 1, client);  
    54.    
    55.                 // 开启客户端  
    56.                 client.Start();  
    57.             }  
    58.    
    59.             System.Threading.Thread.Sleep(1000);  
    60.    
    61.             while (true)  
    62.             {  
    63.                 Console.WriteLine("请输入要离线的ClientID,输入0则退出程序:");  
    64.                 String clientID = Console.ReadLine();  
    65.                 if (!String.IsNullOrEmpty(clientID))  
    66.                 {  
    67.                     Int32 iClientID = 0;  
    68.                     Int32.TryParse(clientID, out iClientID);  
    69.                     if (iClientID > 0)  
    70.                     {  
    71.                         Client client;  
    72.                         if (dicClient.TryGetValue(iClientID, out client))  
    73.                         {  
    74.                             // 客户端离线  
    75.                             client.Offline = true;  
    76.                         }  
    77.                     }  
    78.                     else  
    79.                     {  
    80.                         return;  
    81.                     }  
    82.                 }  
    83.             }  
    84.         }  
    85.     }  
    86.    
    87.     /// <summary>  
    88.     /// 服务端  
    89.     /// </summary>  
    90.     public class Server  
    91.     {  
    92.         public event ClientOfflineHandler OnClientOffline;  
    93.         public event ClientOnlineHandler OnClientOnline;  
    94.    
    95.         private Dictionary<Int32, ClientInfo> _DicClient;  
    96.    
    97.         /// <summary>  
    98.         /// 构造函数  
    99.         /// </summary>  
    100.         public Server()  
    101.         {  
    102.             _DicClient = new Dictionary<Int32, ClientInfo>(100);              
    103.         }  
    104.    
    105.         /// <summary>  
    106.         /// 开启服务端  
    107.         /// </summary>  
    108.         public void Start()  
    109.         {  
    110.             // 开启扫描离线线程  
    111.             Thread t = new Thread(new ThreadStart(ScanOffline));  
    112.             t.IsBackground = true;  
    113.             t.Start();  
    114.         }  
    115.    
    116.         /// <summary>  
    117.         /// 扫描离线  
    118.         /// </summary>  
    119.         private void ScanOffline()  
    120.         {  
    121.             while (true)  
    122.             {  
    123.                 // 一秒扫描一次  
    124.                 System.Threading.Thread.Sleep(1000);  
    125.    
    126.                 lock (_DicClient)  
    127.                 {  
    128.                     foreach (Int32 clientID in _DicClient.Keys)  
    129.                     {  
    130.                         ClientInfo clientInfo = _DicClient[clientID];  
    131.    
    132.                         // 如果已经离线则不用管  
    133.                         if (!clientInfo.State)  
    134.                         {  
    135.                             continue;  
    136.                         }  
    137.    
    138.                         // 判断最后心跳时间是否大于3秒  
    139.                         TimeSpan sp = System.DateTime.Now - clientInfo.LastHeartbeatTime;  
    140.                         if (sp.Seconds >= 3)  
    141.                         {  
    142.                             // 离线,触发离线事件  
    143.                             if (OnClientOffline != null)  
    144.                             {  
    145.                                 OnClientOffline(clientInfo);  
    146.                             }  
    147.    
    148.                             // 修改状态  
    149.                             clientInfo.State = false;  
    150.                         }  
    151.                     }  
    152.                 }  
    153.             }  
    154.         }  
    155.    
    156.         /// <summary>  
    157.         /// 接收心跳包  
    158.         /// </summary>  
    159.         /// <param name="clientID">客户端ID</param>  
    160.         public void ReceiveHeartbeat(Int32 clientID)  
    161.         {  
    162.             lock (_DicClient)  
    163.             {  
    164.                 ClientInfo clientInfo;  
    165.                 if (_DicClient.TryGetValue(clientID, out clientInfo))  
    166.                 {  
    167.                     // 如果客户端已经上线,则更新最后心跳时间  
    168.                     clientInfo.LastHeartbeatTime = System.DateTime.Now;  
    169.                 }  
    170.                 else  
    171.                 {  
    172.                     // 客户端不存在,则认为是新上线的  
    173.                     clientInfo = new ClientInfo();  
    174.                     clientInfo.ClientID = clientID;  
    175.                     clientInfo.LastHeartbeatTime = System.DateTime.Now;  
    176.                     clientInfo.State = true;  
    177.    
    178.                     _DicClient.Add(clientID, clientInfo);  
    179.    
    180.                     // 触发上线事件  
    181.                     if (OnClientOnline != null)  
    182.                     {  
    183.                         OnClientOnline(clientInfo);  
    184.                     }  
    185.                 }  
    186.             }  
    187.         }  
    188.     }  
    189.    
    190.     /// <summary>  
    191.     /// 客户端  
    192.     /// </summary>  
    193.     public class Client  
    194.     {  
    195.         public Server Server;  
    196.         public Int32 ClientID;  
    197.         public Boolean Offline;  
    198.    
    199.         /// <summary>  
    200.         /// 构造函数  
    201.         /// </summary>  
    202.         /// <param name="clientID"></param>  
    203.         /// <param name="server"></param>  
    204.         public Client(Int32 clientID, Server server)  
    205.         {  
    206.             ClientID = clientID;  
    207.             Server = server;  
    208.             Offline = false;  
    209.         }  
    210.    
    211.         /// <summary>  
    212.         /// 开启客户端  
    213.         /// </summary>  
    214.         public void Start()  
    215.         {  
    216.             // 开启心跳线程  
    217.             Thread t = new Thread(new ThreadStart(Heartbeat));  
    218.             t.IsBackground = true;  
    219.             t.Start();  
    220.         }  
    221.    
    222.         /// <summary>  
    223.         /// 向服务器发送心跳包  
    224.         /// </summary>  
    225.         private void Heartbeat()  
    226.         {  
    227.             while (!Offline)  
    228.             {  
    229.                 // 向服务端发送心跳包  
    230.                 Server.ReceiveHeartbeat(ClientID);  
    231.                    
    232.                 System.Threading.Thread.Sleep(1000);  
    233.             }  
    234.         }  
    235.     }  
    236.    
    237.     /// <summary>  
    238.     /// 客户端信息  
    239.     /// </summary>  
    240.     public class ClientInfo  
    241.     {  
    242.         // 客户端ID  
    243.         public Int32 ClientID;  
    244.    
    245.         // 最后心跳时间  
    246.         public DateTime LastHeartbeatTime;  
    247.    
    248.         // 状态  
    249.         public Boolean State;  
    250.     }  
    251. }  
  • 相关阅读:
    纯js面试题
    js面试题汇总:
    css面试题-圣杯布局
    6.1 React组件 Component的两个问题
    7.1 React异步加载
    vscode插件配置jsx:
    6.2 React renderProps 和 HOC组件优化
    2.5 React高阶组件HOC
    04 Python学习之数据类型-bytes
    03 Python学习之Python基础
  • 原文地址:https://www.cnblogs.com/skyay/p/7527137.html
Copyright © 2020-2023  润新知