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

在Internet上采用TCP进行通信的系统,都会遇到一个令人头疼的问题,就是“掉线”。而“TCP掉线”这个问题远比我们通常所能想象的要复杂的多 -- 网络拓扑纷繁复杂、而从始节点A到终节点B之间可能要经过N多的交换机、路由器、防火墙等等硬件设备,每个硬件设备的相关设定也不统一,再加上网络中可能出现的拥塞、延迟等,使得我们在编程时,处理掉线也非常棘手。

一.从程序的角度看待TCP掉线

TCP掉线的原因可能多种多样、不一而足,比如,客人的电脑突然断电、OS崩溃、路由器重启、网线接触不良、因为P2P下载软件而导致网络资源短缺、Internet网络的不稳定等等,但是从程序的角度来说,我们可以总结为两种情况:

1.程序能立即感知的掉线。

也就是说客户端一掉线,服务器端的某个读写对应的TCP连接的线程就抛出异常,这种情况相对容易处理。而ESFramework针对这种情况,会触发IUserManager的SomeOneDisconnected事件,来通知我们的应用程序。

event CbGeneric<UserData> SomeOneDisconnected;

2.程序不能立即感知的掉线。

我们都知道,TCP连接的建立,需要经过三次握手;而TCP连接的断开,需要经过四次挥手。     

掉线通常没什么大不了的,掉就掉了呗,只要四次挥手顺利完成后,服务器和客户端分别做一些善后处理就可以。

麻烦的事情在于,连接在没有机会完成4次挥手时已经断开了(比如当客人的电脑系统死机,或客人电脑与服务器之间的某处物理网线断开),而服务端以为客户端还正常在线,而客户端也自以为还正常在线。这种程序对现实状态的错误判断有可能引发诸多悲剧,比如,在此情况下,客户端发一个指令给服务器,服务器因为没有收到而一直处于等待指令的状态;而客户端了,以为服务器已经收到了,也就一直处于等待服务端回复的状态,如果程序的其它部分需要依据当前的状态来做后续的操作,那就可能出问题,因为程序对当前连接状态的判断是错误的。

毫无疑问,这种对连接状态错误的判断所持续的时间越久,带来可能的危害就越大。当然,如果我们不做任何额外的处理措施,服务器到最后也能感受到客户端的掉线,但是,这个时间可能已经过去了几分钟甚至几十分钟。对于大多数应用来说,这是不可忍受的。 所以,针对这种不能立即感知掉线的情况,我们要做的补救措施,就是帮助程序尽快地获知tcp连接已断开的信息。

首先,我们可以在Socket上通过Socket.IOControl方法设置KeepAliveValues,来控制底层TCP的保活机制,比如,设定2秒钟检测一次,超过10秒检测失败时抛出异常。

byte[] inOptionValues = FillKeepAliveStruct(1, 10000, 2000);
   socket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null);

据我们的经验,这种设定可以解决一部分问题,但是仍然会有一些连接在断开后,远远超过10秒才被感知掉。所以,这个补救措施还是远远不够的。我们还需要在应用层加入我们自己的TCP连接状态检测机制,这种机制就是通常所说的“心跳”。

二.“心跳”机制

心跳机制的原理很简单:客户端每隔N秒向服务端发送一个心跳消息,服务端收到心跳消息后,回复同样的心跳消息给客户端。如果服务端或客户端在M秒(M>N)内都没有收到包括心跳消息在内的任何消息,即心跳超时,我们就认为目标TCP连接已经断开了。

由于不同的应用程序对感知TCP掉线的灵敏度不一样,所以,N和M的值就可以设定的不一样。灵敏度要求越高,N和M就要越小;灵敏度要求越低,N和M就可以越大。而要求灵敏度越高,也是有代价的,那就是需要更频繁地发送心跳消息,如果有几千个连接同时频繁地发送心跳消息,那么其所消耗的资源也是不能忽略的。

当然,网络环境(如延迟的大小)的好坏,也对会对N和M的值的设定产生影响,比如,网络延迟较大,那么N与M之间的差值也应该越大(比如,M是N的3倍)。否则,可能会产生误判 -- 即TCP连接没有断开,只是因为网络延迟大才及时没收到心跳消息,我们却认为连接已经断开了。

ESFramework内置了心跳机制,当心跳超时时,服务端会触发IUserManager的SomeOneTimeOuted事件,来通知我们的应用程序。

UserManager通过ESBasic.Threading.Application.HeartBeatChecker来对心跳进行检测,而HeartBeatChecker的SurviveSpanInSecs属性可以用于设置我们所描述的M值。

客户端通过ESPlus.Application.Basic.Passive.HeartBeater来向服务器定时发送心跳消息,而HeartBeater的DetectSpanInSecs属性可以用于设置N值。

当你在使用Rapid引擎时,关于心跳机制的组件已经自动为你组装好了。由于RapidServerEngine和RapidPassiveEngine没有暴露出HeartBeatChecker和HeartBeater,所以,我们不能直接通过HeartBeatChecker和HeartBeater设定M和N的值,但是,RapidServerEngine和RapidPassiveEngine分别提供了TimeoutSpanInSecs属性和HeartBeatSpanInSecs属性来间接地设定M和N。

三.必须关闭掉线的TCP连接

无论是普通掉线(立即感知)还是心跳超时掉线(非立即感知),都需要关闭对应的TCP连接以释放系统资源。ITcpServerEngine接口提供了CloseOneConnection方法以关闭目标连接。

void CloseOneConnection(IUserAddress adderss);

当普通掉线时,ITcpServerEngine会自动关闭了TCP连接;但是,当心跳超时掉线时,我们需要自己手动关闭对应的连接。幸运的是,如果我们使用了ESPlus.Application.Basic命名空间下的组件,ESPlus也会自动帮我们关闭超时掉线的连接。Rapid引擎使用了ESPlus.Application.Basic下的组件,所以,使用Rapid引擎的朋友也不用再自己手动关闭连接了。

另外要提醒一点,如果使用Rapid引擎,那么当连接超时掉线时,服务端会首先触发IUserManager的SomeOneTimeOuted事件,接着再触发IUserManager的SomeOneDisconnected事件(由于ESPlus调用CloseOneConnection方法时触发)。

四.UDP与“心跳”

前面介绍的都是关于TCP的掉线的问题,下面我们看看UDP。

由于UDP是无连接的协议,所以,当我们在使用ESFramework的UDP引擎的时候,几乎肯定是需要配备心跳机制的,使用心跳消息确认客户端还在线,以保证服务端不会过早释放对应的Session或长期保留已失效的Session。

ESFramework中的心跳机制相关的组件是与协议无关的,所以既可以用于TCP应用,也可用于UDP应用。但是,由于对UDP没有进行类似Rapid引擎的封装,所以,如果使用ESFramework的UDP引擎开发应用,则需要手动组装与心跳机制相关的组件了。

时间: 2024-08-29 15:07:36

判定生死的心跳机制 --ESFramework 4.0 快速上手(07)的相关文章

ESFramework 4.0 快速上手(01) -- Rapid引擎

(在阅读该文之前,请先阅读 ESFramework 4.0 概述 ,会对本文的理解更有帮助.) ESFramework/ESPlatform 4.0 的终极目标是为百万级的用户同时在线提供支持,因为强大,所以使用也较为复杂,配置也较多.但是如果我们的应用只是一个中小型的通信应用(同时在线5000人以下),直接使用ESPlatform就有点显得杀鸡用牛刀了.ESPlus.Rapid提供了一种快速的方式,来解决类似中小型的通信应用,以最简洁的方式来使用ESFramework. 使用ESPlus.Ra

ESFramework 4.0 快速上手(06) -- Rapid引擎(续)

<ESFramework 4.0 快速上手>系列介绍的都是如何使用Rapid引擎(快速引擎) -- RapidServerEngine 和 RapidPassiveEngine.其实,大家可以将这两个引擎看作是两个壳,内部包装的才是真正的ESFramework的网络引擎, ESFramework支持很多种网络引擎(客户端/服务端.二进制协议/文本协议.TCP/UDP),而RapidServerEngine和RapidPassiveEngine采用的是基于TCP和二进制协议的服务端引擎和客户端引

离线消息如何实现?-- ESFramework 4.0 快速上手(02)

在ESFramework 4.0 快速上手一文中,主要介绍了如何使用ESPlus.Rapid命名空间中的引擎来快速地构建基于TCP的网络通信系统,即使是使用ESPlus.Rapid来进行ESFramework快速开发,也还有很多可以介绍的内容,于是,我想再多写几篇文章来说明现实通信系统中的一些常见需求如何使用ESFramework快速实现.本文是为第一篇,介绍离线消息的原理和实现. 一.如何截获离线消息 阅读了ESFramework 4.0 快速上手朋友都知道,一个在线用户给另一个用户发送文本信

如何使用自定义消息?--ESFramework 4.0 快速上手(04)

在ESFramework 4.0 快速上手一文中,我们讲述了如何使用Rapid引擎可以快速地上手ESFramework开发,文中介绍了使用ESPlus.Application.CustomizeInfo命名空间下的类可以发送和处理自定义消息,本文我们就通过一个简单的例子来深入讲解如何使用自定义消息. 例子的场景很简单:假设客户端登陆到服务器之后,要求请求加入某个组,服务端收到该请求后,处理该请求,并给客户端相应的回复 -- 是否加入成功,客户端收到回复后,即可作出相应的处理. 一.定义消息类型和

重登陆模式 --ESFramework 4.0 快速上手(07)

在ESFramework框架中基于TCP的服务端引擎(当然也包括Rapid引擎)都采用了这样一条规则:默认情况下,客户端与服务器成功建立TCP连接以后,服务端会从客户端发过来的第一条消息中取出消息头的UserID属性的值,并将其与对应的TCP连接绑定起来.这样,服务端就知道每一个TCP连接所对应的用户UserID,而当我们要求服务端向某个客户端发送消息时,服务端就知道通过哪个TCP连接进行发送了.TCP连接与UserID是一一对应的,一个TCP连接只能对应一个UserID,同样的,一个UserI

AutoMapper 9.0快速上手,从老版本迁移到9.0+AutoMapper9.0和Autofac的完美结合

.NET模型映射器AutoMapper 9.0发布了,官方宣称不再支持静态方法调用了,老版本的部分API将在升级到9.0后,直接升级包到9.0会编译报错,所以写篇文章记录下AutoMapper新版本的学习过程吧,如果还不知道AutoMapper是什么的,建议先看这篇文章:https://masuit.com/156,或者参考官方文档:https://automapper.readthedocs.io/en/latest/Getting-started.html AutoMapper9.0快速上手

核心梳理——消息处理的骨架流程——ESFramework 4.0 进阶(02)

在ESFramework 4.0 概述一文中,我们提到ESFramework.dll作为通信框架的核心,定义了消息处理的骨架流程,本文我们来详细剖析这个流程以及该骨架中所涉及的各个组件.ESFramework的骨架流程如下图所示: 一.所有的网络引擎都使用同一消息处理骨架流程 ESFramework支持TCP/UDP.二进制协议/文本协议.服务端/客户端组合而成的2x2x2=8种引擎,无论是哪一种引擎,都实现了INetEngine接口,也都使用上图所示的消息处理骨架流程来处理所接收到的所有消息.

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

虽然我们前面已经介绍完了ESFramework开发所需掌握的各种基础设施,但是还不够.想要更好地利用ESFramework这一利器,有些背景知识是我们必须要理解的.就像本文介绍的心跳机制,在严峻的Internet条件下,是通信系统中不可或缺的机制之一. 在Internet上采用TCP进行通信的系统,都会遇到一个令人头疼的问题,就是“掉线”.而“TCP掉线”这个问题远比我们通常所能想象的要复杂的多 —— 网络拓扑纷繁复杂.而从始节点A到终节点B之间可能要经过N多的交换机.路由器.防火墙等等硬件设备

服务器心跳机制

心跳机制是定时发送一个自定义的结构体(心跳包),让对方知道自己还活着,以确保连接的有效性的机制. 应用场景: 在长连接下,有可能很长一段时间都没有数据往来.理论上说,这个连接是一直保持连接的,但是实际情况中,如果中间节点出现什么故障是难以知道的.更要命的是,有的节点(防火墙)会自动把一定时间之内没有数据交互的连接给断掉.在这个时候,就需要我们的心跳包了,用于维持长连接,保活 什么是心跳机制? 就是每隔几分钟发送一个固定信息给服务端,服务端收到后回复一个固定信息如果服务端几分钟内没有收到客户端信息