C++服务器设计(六):设备连接的生命周期管理
生命周期介绍
每一个服务器系统的新连接从建立开始时,均会经历多个阶段。比如连接的建立,登录的验证,退出前的资源释放等。同时在具体的消息处理中,还会遇到不可识别的消息事件,或者消息处理时出现数据错误等。这些场景在每个连接的整个生命过程都可能会经历,而系统用户也会期望在某些场景中完成特定的操作。比如统计每一个新建连接的信息,对不同类型的客户进行验证过程等。
在一般的服务器系统中,用户需要主动为每个连接对象维护当前状态,然后在具体业务场景中加入对当前连接状态的判断,并在每一个改变连接状态的场景中执行相关生命周期的操作。这种自身维护连接生命状态的做法,可能会由于未与业务逻辑分离而导致实际业务处理部分代码的混乱,这也增加了用户开发的复杂度,使实际开发工作出错率大大增加。
而对于每一个不同的消息处理过程中,都有可能在业务逻辑中出现处理出错或不可识别的的数据的情景。这些都需要在每个消息处理的代码中添加大量与实际业务无关的错误处理代码。这同样不利于业务代码的编写,加大了开发者重复的工作量。
综合以上问题,在服务器系统的设计中我们提出了设备生命周期的概念,并通过系统对连接状态和处理错误的管理,提取出了连接的生命周期和错误处理相关的接口。当用户需要对某个状态的连接客户进行相关操作时,只需重写相关状态的接口函数,同时对于所有错误操作,即可在具体业务逻辑中处理,也可通过在对应接口函数中实现统一的错误处理过程。这大大简化和便利了服务器用户的开发过程。
同时联系到之前系统对多设备类型的支持,在具体设计中,需要使用的连接生命周期接口在对应的设备类型对象中实现。不同的设备类型中,对于某个生命周期的接口实现不一定相同。同时子类设备类型会继承父类设备类型中实现的接口,同时在子类中还可以对父类实现的接口进行重写。因此我们可以给不同的设备类型制定不同的生命周期接口。
图4-8 连接生命周期
如图4-8所示显示了一个连接从连接建立到断开的生命周期图,包括临时设备类型的生命周期及登录设备的生命周期。其中标示出了在不同场景中系统提供的相关接口。同时服务器系统保证对于每个连接的生命周期接口的回调都将在分配给该连接对象的单一I/O线程中处理,因此对于单个连接而言无需考虑多线程的问题。
生命周期接口
以下将对这些接口进行具体介绍:
- l onCreate():每当有新连接建立时将会执行该接口回调。因为每个新连接刚建立时均为临时设备类型,因此该接口默认实现在临时设备类型对象中,可以通过对临时设备类型进行重写操作修改该接口的实现。可以在该实现中记录新连接的信息,或者对系统所有新连接信息进行统计。
- l onLoginCheckMsg():每当执行登录消息事件时将会执行该接口回调。该接口默认在登录设备类型对象中实现,可以通过新建具体的登录设备类型对该接口进行重写。可以在该接口实现中进行登录检查操作。比如可以通过查询DB等方式检查登录设备账号是否合法,同时可以为该连接分配对应的设备ID。如果登录检查失败,发送失败回复消息并直接返回false即可。如果检查成功,返回true,系统将进一步进行登录操作,若最终登录成功,将回调onLoginSuccessMsg()接口;若系统检查ID失败,则会回调onLoginFailureMsg()接口。
- l onLoginSuccessMsg():每当执行登录操作且登录成功后将会执行该接口回调。该接口默认在登录设备类型对象中实现,可以通过新建具体的登录设备类型对该接口进行重写。可以在该接口中实现向客户连接发送登录成功的通知。此时该连接将进入登录设备的生命周期,同时能够请求该设备类型的所有消息事件。
- l onLoginFailureMsg():每当连接在onLoginCheckMsg对账号验证成功后,但系统进一步进行登录操作失败后将会执行该接口回调。该接口默认在登录设备类型对象中实现,可以通过新建具体的登录设备类型对该接口进行重写。一般执行该回调表明该连接设置的设备ID与某个以登录的设备ID冲突,表明该账号已登录或给该连接设置的设备ID错误。
- l onLogoutMsg():每当登录的连接客户请求注销消息事件时将会执行该接口回调。该接口默认在登录设备类型对象中实现,可以通过新建具体的登录设备类型对该接口进行重写。当该接口被调用时,表明该连接即将被退出登录操作并被断开,并将调用releaseConnNode()接口用于将该连接客户申请的相关资源释放。可以在该接口实现中回复连接客户通知注销操作成功,即将被关闭。
- l releaseConnNode():每当登录的连接客户进行注销成功,或者连接由于超时或者其它异常原因被迫退出时,将会执行该接口回调。该接口默认在登录设备类型对象中实现,可以通过新建具体的登录设备类型对该接口进行重写。当该接口被调用时,表明连接对象即将或已经被断开连接,如果之前为该连接申请了一些资源,并仍未释放,需要在此回调中进行释放操作。由于在此处连接可能已经处于断开的状态,因此在此回调中不应该再尝试向连接客户发送消息。
- l onOverTime():每当一个连接一段时间内未收到有效消息而超时,将会执行该接口回调。该接口在设备类型对象中实现,在临时设备类型或新建的具体登录设备类型中均可对该接口进行重写。当该接口被调用时,表明该连接对象即将被断开连接。如果是已经登录的连接客户,执行完该调用后将会执行releaseConnNode()进行资源释放。可以在该接口实现中通知连接客户由于连接超时该连接即将被主动断开。
- l onErrTypeMessage():每当某个连接接收到一条消息后,如果在该连接对应的设备类型中找不到该消息事件的处理函数,且在临时设备类型中也找不到该消息事件的处理函数,将会执行该接口回调。该接口在设备类型对象中实现,在临时设备类型或新建的具体登录设备类型中均可对该接口进行重写。当该接口被调用,表明该连接收到了一条无法识别或者权限不不够处理的消息事件。可以在该接口实现中通知连接客户该消息事件无法被处理。
- l onErrRunMessage():每当某个连接接收到一条消息后,如果在对该消息进行处理时,发生了某些错误,导致解析处理错误,将会执行该接口回调。该接口在设备类型对象中实现,在临时设备类型或新建的具体登录设备类型中均可对该接口进行重写。当该连接被调用,表明消息事件处理出错。可以在该接口实现中通知连接客户该消息事件处理错误。
登录流程分析
在不同设备连接的生命周期中,服务器系统为所有管理的设备制定了一套统一的登录机制。整个登录流程如图4-9所示。这个机制既保证了对不同设备的统一管理,同时又提供了三个登录相关的接口供具体设备实现,保证不同设备间登录过程中的差异化处理。
图4-9 登录流程分析
当某个连接用户向服务器请求登录操作时,系统首先检查该连接是否已经登录,如果该连接已经处于登录状态,则直接进入登录成功操作。如果该连接并未登录,系统将根据请求登录消息中传入的类型来为该连接设置设备类型。如果消息中设置的设备类型并不存在,或者并未设置设备类型,系统将默认为该连接设置为登录设备类型(NodeType)。
然后进行登录账号检查操作。此时将进入该连接的具体设备类型的onLoginCheckMsg()回调中。该接口默认实现是不进行任何检查操作,直接返回为True。我们可以根据不同设备对于登录账号检查的要求的不同,在不同设备类型对象中重写该接口实现。比如根据请求登录消息中传来的账号及密码信息,查询数据库进行校验等。同时也可以在此根据账号密码信息给该连接分配一个登录ID,如果未分配,将采用系统分配的临时ID作为登录ID。如果账号检查失败,则该次登录操作失败,直接返回False,并退出整个登录操作。如果账号检查成功,返回True,但这并不意味着该连接已经登录成功,因为该连接并未真正执行登录操作。
账号检查成功后,将进入真正的登录操作中,系统将尝试将该连接信息注册到对应设备类型的连接池中,并将该连接从临时设备类型的连接池中删除。但此时依旧可能出现该账号对应ID已经在其他客户端登录或正在登录的错误,导致系统无法完成该连接的登录操作。如果这种情况产生,将回调onLoginFailureMsg()接口。我们可以通过该接口来通知该连接客户此次登录失败及可能的原因。当执行完该接口后将退出整个登录操作。
如果系统执行登录操作成功后,将会回调执行onLoginSuccessMsg()接口。当该接口被执行时,该次登录操作才算真正的登录成功。因此我们可以通过该接口来通知连接客户此次登录成功的消息。该接口执行完成后,此次登录操作结束。
退出流程分析
不同于登录过程只存在单一的入口,即通过登录消息事件进行。连接的退出操作存在多种可能。比如对于已经登录的连接用户,最正常的方式是通过logout注销消息事件进行主动退出。但是同样存在其他多种可能:比如远程客户并未发送任何消息突然断开了服务器连接的情景,或者连接客户长期没有发送任何消息导致服务器检测到该连接超时等。我们需要对这些退出操作制定统一的管理,防止异常的退出使服务器系统无法及时释放相关资源,影响系统性能。
图4-10 退出流程分析
如图4-10所示,如果为连接客户通过logout注销事件请求退出,系统将执行在登录设备类型中注册的该事件的注销处理函数。在注销处理函数中,将会调用该连接实际设备类型的onLogoutMsg接口,通过该接口可以通知连接客户该连接即将被系统关闭。然后将会调用该连接类型的releaseConnNode接口,释放该连接用户申请的相关资源。最后系统将会对该连接执行实际退出操作,强制将该连接从连接池中移除,并且对连接进行关闭。
如果连接客户并未请求注销事件,而是直接断开了与服务器的连接。此时服务器系统将通过该连接套接字的read系统调用返回为0检测到该连接已经异常退出。由于此时连接已经断开,因此并无再次通知连接客户的必要,因此系统将直接调用该连接类型的releaseConnNode接口,释放该连接用户申请的相关资源。并执行实际退出操作,强制将该连接从连接池中移除,并且对连接进行关闭。
如果是服务器通过超时机制检测到某个连接超时,系统将对该连接执行超时处理。在超时处理中,系统将通过线程任务队列保证在该连接的线程中执行连接超时处理函数,此时将会调用该连接类型的onOverTime接口,留给用户处理连接超时的场景。然后同样releaseConnNode接口,释放该连接用户申请的相关资源。并执行实际退出操作,强制将该连接从连接池中移除,并且对连接进行关闭。
以上三种情形中均会调用releaseConnNode接口,因此在该接口的实现中我们应该保证兼容这些情景,只进行连连接用户申请的相关资源的释放工作。其他具体场景相关的处理工作再在其他相关接口中实现。