2017-05-17 09:58 1716人阅读 评论(0) 收藏 举报
 分类:
Unity(134)  C#(6) 

版权声明:本文为博主原创文章,转载请注明出处。

 

目录(?)[+]

 

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

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Threading;
  4. namespace ConsoleApplication1
  5. {
  6. // 客户端离线委托
  7. public delegate void ClientOfflineHandler(ClientInfo client);
  8. // 客户端上线委托
  9. public delegate void ClientOnlineHandler(ClientInfo client);
  10. public class Program
  11. {
  12. /// <summary>
  13. /// 客户端离线提示
  14. /// </summary>
  15. /// <param name="clientInfo"></param>
  16. private static void ClientOffline(ClientInfo clientInfo)
  17. {
  18. Console.WriteLine(String.Format("客户端{0}离线,离线时间:\t{1}", clientInfo.ClientID, clientInfo.LastHeartbeatTime));
  19. }
  20. /// <summary>
  21. /// 客户端上线提示
  22. /// </summary>
  23. /// <param name="clientInfo"></param>
  24. private static void ClientOnline(ClientInfo clientInfo)
  25. {
  26. Console.WriteLine(String.Format("客户端{0}上线,上线时间:\t{1}", clientInfo.ClientID, clientInfo.LastHeartbeatTime));
  27. }
  28. static void Main()
  29. {
  30. // 服务端
  31. Server server = new Server();
  32. // 服务端离线事件
  33. server.OnClientOffline += ClientOffline;
  34. // 服务器上线事件
  35. server.OnClientOnline += ClientOnline;
  36. // 开启服务器
  37. server.Start();
  38. // 模拟100个客户端
  39. Dictionary<Int32, Client> dicClient = new Dictionary<Int32, Client>();
  40. for (Int32 i = 0; i < 100; i++)
  41. {
  42. // 这里传入server只是为了方便而已
  43. Client client = new Client(i + 1, server);
  44. dicClient.Add(i + 1, client);
  45. // 开启客户端
  46. client.Start();
  47. }
  48. System.Threading.Thread.Sleep(1000);
  49. while (true)
  50. {
  51. Console.WriteLine("请输入要离线的ClientID,输入0则退出程序:");
  52. String clientID = Console.ReadLine();
  53. if (!String.IsNullOrEmpty(clientID))
  54. {
  55. Int32 iClientID = 0;
  56. Int32.TryParse(clientID, out iClientID);
  57. if (iClientID > 0)
  58. {
  59. Client client;
  60. if (dicClient.TryGetValue(iClientID, out client))
  61. {
  62. // 客户端离线
  63. client.Offline = true;
  64. }
  65. }
  66. else
  67. {
  68. return;
  69. }
  70. }
  71. }
  72. }
  73. }
  74. /// <summary>
  75. /// 服务端
  76. /// </summary>
  77. public class Server
  78. {
  79. public event ClientOfflineHandler OnClientOffline;
  80. public event ClientOnlineHandler OnClientOnline;
  81. private Dictionary<Int32, ClientInfo> _DicClient;
  82. /// <summary>
  83. /// 构造函数
  84. /// </summary>
  85. public Server()
  86. {
  87. _DicClient = new Dictionary<Int32, ClientInfo>(100);
  88. }
  89. /// <summary>
  90. /// 开启服务端
  91. /// </summary>
  92. public void Start()
  93. {
  94. // 开启扫描离线线程
  95. Thread t = new Thread(new ThreadStart(ScanOffline));
  96. t.IsBackground = true;
  97. t.Start();
  98. }
  99. /// <summary>
  100. /// 扫描离线
  101. /// </summary>
  102. private void ScanOffline()
  103. {
  104. while (true)
  105. {
  106. // 一秒扫描一次
  107. System.Threading.Thread.Sleep(1000);
  108. lock (_DicClient)
  109. {
  110. foreach (Int32 clientID in _DicClient.Keys)
  111. {
  112. ClientInfo clientInfo = _DicClient[clientID];
  113. // 如果已经离线则不用管
  114. if (!clientInfo.State)
  115. {
  116. continue;
  117. }
  118. // 判断最后心跳时间是否大于3秒
  119. TimeSpan sp = System.DateTime.Now - clientInfo.LastHeartbeatTime;
  120. if (sp.Seconds >= 3)
  121. {
  122. // 离线,触发离线事件
  123. if (OnClientOffline != null)
  124. {
  125. OnClientOffline(clientInfo);
  126. }
  127. // 修改状态
  128. clientInfo.State = false;
  129. }
  130. }
  131. }
  132. }
  133. }
  134. /// <summary>
  135. /// 接收心跳包
  136. /// </summary>
  137. /// <param name="clientID">客户端ID</param>
  138. public void ReceiveHeartbeat(Int32 clientID)
  139. {
  140. lock (_DicClient)
  141. {
  142. ClientInfo clientInfo;
  143. if (_DicClient.TryGetValue(clientID, out clientInfo))
  144. {
  145. // 如果客户端已经上线,则更新最后心跳时间
  146. clientInfo.LastHeartbeatTime = System.DateTime.Now;
  147. }
  148. else
  149. {
  150. // 客户端不存在,则认为是新上线的
  151. clientInfo = new ClientInfo();
  152. clientInfo.ClientID = clientID;
  153. clientInfo.LastHeartbeatTime = System.DateTime.Now;
  154. clientInfo.State = true;
  155. _DicClient.Add(clientID, clientInfo);
  156. // 触发上线事件
  157. if (OnClientOnline != null)
  158. {
  159. OnClientOnline(clientInfo);
  160. }
  161. }
  162. }
  163. }
  164. }
  165. /// <summary>
  166. /// 客户端
  167. /// </summary>
  168. public class Client
  169. {
  170. public Server Server;
  171. public Int32 ClientID;
  172. public Boolean Offline;
  173. /// <summary>
  174. /// 构造函数
  175. /// </summary>
  176. /// <param name="clientID"></param>
  177. /// <param name="server"></param>
  178. public Client(Int32 clientID, Server server)
  179. {
  180. ClientID = clientID;
  181. Server = server;
  182. Offline = false;
  183. }
  184. /// <summary>
  185. /// 开启客户端
  186. /// </summary>
  187. public void Start()
  188. {
  189. // 开启心跳线程
  190. Thread t = new Thread(new ThreadStart(Heartbeat));
  191. t.IsBackground = true;
  192. t.Start();
  193. }
  194. /// <summary>
  195. /// 向服务器发送心跳包
  196. /// </summary>
  197. private void Heartbeat()
  198. {
  199. while (!Offline)
  200. {
  201. // 向服务端发送心跳包
  202. Server.ReceiveHeartbeat(ClientID);
  203. System.Threading.Thread.Sleep(1000);
  204. }
  205. }
  206. }
  207. /// <summary>
  208. /// 客户端信息
  209. /// </summary>
  210. public class ClientInfo
  211. {
  212. // 客户端ID
  213. public Int32 ClientID;
  214. // 最后心跳时间
  215. public DateTime LastHeartbeatTime;
  216. // 状态
  217. public Boolean State;
  218. }
  219. }

C#之实现Scoket心跳机制的更多相关文章

  1. uni-app中websocket的使用 断开重连、心跳机制

    前言 最近关于H5和APP的开发中使用到了webSocket,由于web/app有时候会出现网络不稳定或者服务端主动断开,这时候导致消息推送不了的情况,需要客户端进行重连.查阅资料后发现了一个心跳机制 ...

  2. rabbitmq 的心跳机制&应用

    官方文档说: If a consumer dies (its channel is closed, connection is closed, or TCP connection is lost) w ...

  3. zookeeper心跳机制流程梳理

    zookeeper心跳机制流程梳理 Processor链Chain protected void setupRequestProcessors() { RequestProcessor finalPr ...

  4. 一个Socket连接管理池(心跳机制)

    一个Socket连接管理池(心跳机制) http://cuisuqiang.iteye.com/blog/1489661

  5. ESFramework 开发手册(07) -- 掉线与心跳机制(转)

    虽然我们前面已经介绍完了ESFramework开发所需掌握的各种基础设施,但是还不够.想要更好地利用ESFramework这一利器,有些背景知识是我们必须要理解的.就像本文介绍的心跳机制,在严峻的In ...

  6. 判定生死的心跳机制 --ESFramework 4.0 快速上手(07)

    在Internet上采用TCP进行通信的系统,都会遇到一个令人头疼的问题,就是"掉线".而"TCP掉线"这个问题远比我们通常所能想象的要复杂的多 -- 网络拓扑 ...

  7. 转 互联网推送服务原理:长连接+心跳机制(MQTT协议)

    http://blog.csdn.net/zhangzeyuaaa/article/details/39028369 目录(?)[-] 无线移动网络的特点 android系统的推送和IOS的推送有什么 ...

  8. netty心跳机制测试

    netty中有比较完善的心跳机制,(在基础server版本基础上[netty基础--基本收发])添加少量代码即可实现对心跳的监测和处理. 1 server端channel中加入心跳处理机制 // Id ...

  9. Java: server/client 心跳机制实现 示例

    心跳机制 心跳机制是定时发送一个自定义的结构体(心跳包),让对方知道自己还活着,以确保连接的有效性的机制. 大部分CS的应用需要心跳机制.心跳机制一般在Server和Client都要实现,两者实现原理 ...

随机推荐

  1. C# process 隐藏应用程序的进度条

    命令行参数那加上-ibck指定后台运行. string sourceFilepath = "d:\\测试.rar"; string targetFilepath = "d ...

  2. 【leetcode】20-ValidParentheses

    problem Valid Parentheses code class Solution { public: bool isValid(string s) { stack<char> p ...

  3. Java中的国际化

    一.什么是国际化? 国际化是指应用程序运行时,可根据客户端请求来自的国家/地区.语言的不同而显示不同的界面. 二.Java如何实现国际化? Java程序的国际化思路是将程序中的标签.提示等信息放在资源 ...

  4. 求割点 割边 Tarjan

    附上一般讲得不错的博客 https://blog.csdn.net/lw277232240/article/details/73251092 https://www.cnblogs.com/colle ...

  5. 完整的AJAX

    $.ajax({url:"请求路径",data:{"key":前端传入后台处理的数据},type:"post", //请求方式dataTyp ...

  6. 改变radio单选按钮的样式

    <div class="choose_btn"> <input type="radio" name="choose_raido&qu ...

  7. 无用之flask学习

    一.认识flask 1.短小精悍.可扩展性强 的一个web框架 注意:上下文管理机制 2.依赖wsgi:werkzurg from werkzeug.wrappers import Request, ...

  8. linux入门001--帮助支持

    linux入门001--帮助支持====1. 帮助手册提供命令的使用说明:man ls提供基础知识和参考信息,有时会有实例和交叉索引,但是基本没有教程式的文档.就是说,并不会有偏重告诉你那个重要,那个 ...

  9. vue 2.0 vue.set的使用方法

    这里我定义了一个列表数据,我将通过三个不同的按钮来控制列表数据. 首先在列表中动态新增一条数据: <!DOCTYPE html><html><head lang=&quo ...

  10. 【SpringBoot】SpringBoot热部署和配置文件自动注入实战

    ========================3.SpringBoot热部署devtool和配置文件自动注入实战 ============================ 1.SpringBoot2 ...