C#之实现Scoket心跳机制
C#(6) 
版权声明:本文为博主原创文章,转载请注明出处。
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心跳机制的更多相关文章
- uni-app中websocket的使用 断开重连、心跳机制
前言 最近关于H5和APP的开发中使用到了webSocket,由于web/app有时候会出现网络不稳定或者服务端主动断开,这时候导致消息推送不了的情况,需要客户端进行重连.查阅资料后发现了一个心跳机制 ...
- rabbitmq 的心跳机制&应用
官方文档说: If a consumer dies (its channel is closed, connection is closed, or TCP connection is lost) w ...
- zookeeper心跳机制流程梳理
zookeeper心跳机制流程梳理 Processor链Chain protected void setupRequestProcessors() { RequestProcessor finalPr ...
- 一个Socket连接管理池(心跳机制)
一个Socket连接管理池(心跳机制) http://cuisuqiang.iteye.com/blog/1489661
- ESFramework 开发手册(07) -- 掉线与心跳机制(转)
虽然我们前面已经介绍完了ESFramework开发所需掌握的各种基础设施,但是还不够.想要更好地利用ESFramework这一利器,有些背景知识是我们必须要理解的.就像本文介绍的心跳机制,在严峻的In ...
- 判定生死的心跳机制 --ESFramework 4.0 快速上手(07)
在Internet上采用TCP进行通信的系统,都会遇到一个令人头疼的问题,就是"掉线".而"TCP掉线"这个问题远比我们通常所能想象的要复杂的多 -- 网络拓扑 ...
- 转 互联网推送服务原理:长连接+心跳机制(MQTT协议)
http://blog.csdn.net/zhangzeyuaaa/article/details/39028369 目录(?)[-] 无线移动网络的特点 android系统的推送和IOS的推送有什么 ...
- netty心跳机制测试
netty中有比较完善的心跳机制,(在基础server版本基础上[netty基础--基本收发])添加少量代码即可实现对心跳的监测和处理. 1 server端channel中加入心跳处理机制 // Id ...
- Java: server/client 心跳机制实现 示例
心跳机制 心跳机制是定时发送一个自定义的结构体(心跳包),让对方知道自己还活着,以确保连接的有效性的机制. 大部分CS的应用需要心跳机制.心跳机制一般在Server和Client都要实现,两者实现原理 ...
随机推荐
- Android直播实现srs流媒体服务器部署
链接:http://blog.csdn.net/mr_sk/article/details/71086885 在配置文件中加入 pid 参数执行
- SQL注入之Sqli-labs系列第十九关(基于头部的Referer POST报错注入)
开始挑战第十八关(Header Injection- Referer- Error Based- string) 先来说下HTTP Referer: HTTP Referer是header的一部分,当 ...
- C语音,关于可变参数的宏定义
typedef char * va_list; // TC中定义为void* //为了满足需要内存对齐的系统 #define _INTSIZEOF(n) ((sizeof(n)+sizeof(int) ...
- 如何让jpa 持久化时不校验指定字段
源文:https://www.toocruel.net/jpa-validate/ 怎么让jpa 持久化时不校验指定字段 本文提供全流程,中文翻译. Chinar 坚持将简单的生活方式,带给世人!(拥 ...
- Gym 101889:2017Latin American Regional Programming Contest(寒假自训第14场)
昨天00.35的CF,4点才上床,今天打的昏沉沉的,WA了无数发. 题目还是满漂亮的. 尚有几题待补. C .Complete Naebbirac's sequence 题意:给定N个数,他们在1到K ...
- linux下使用小票打印
linux下使用小票打印 打印机: Xprinter XP-58IIH指令支持: ESC/POS接口: USB, 蓝牙 Linux系统: Centos7 蓝牙配对很快, 配对好后就是连接状态. 但很快 ...
- 【HDOJ3018】【一笔画问题】【欧拉回路+并查集】
http://acm.hdu.edu.cn/showproblem.php?pid=3018 Ant Trip Time Limit: 2000/1000 MS (Java/Others) Me ...
- exception in thread "http-apr-80-exec-24" java.lang.OutOfMemoryError:PermGen...
今天客户说项目访问不了了,我急忙看了下告警,发现上报:“exception in thread "http-apr-80-exec-24" java.lang.OutOfMemor ...
- 浅谈log4j-5-读取properties文件(转自godtrue)
#### 在代码中配置log4j环境的方式,我们已经见识过了,是不是感觉比较麻烦,我们试试使用配置文件的方式是否使您的应用程序更加的灵活.# Log4j支持两种配置文件格式,一种是XML格式的文件,一 ...
- PureComponent的作用及一些使用陷阱
默认渲染行为的问题 在React Component的生命周期中,有一个shouldComponentUpdate方法.这个方法默认返回值是true. 这意味着就算没有改变组件的props或者stat ...