逐步实现TCP服务端Step04-3:封装netio主逻辑

之前对代码做整理封装的时候,并没过多地考虑netio,当时的关注点在s上,只对s做了封装,现在对netio做类似的处理。

s与netio的主逻辑中有不少类似的地方,如使用共享内存,操作c2s_code_queue和s2c_code_queue等等。这些基础操作应被整合在Control类中,由于Control这个名字在处理s的时候已被使用,这里需要改下名字,将对应于s的Control类改为SControl,对应于netio的Control类命名为NControl,而Control则为它们共同的父类。

无论s还是netio,大流程可概括为三步:初始化,准备运行,运行。

  • 初始化/Init:见全局函数Init,主要是创建共享内存,构建基础对象,如Control对象。这个步骤中除创建Control对象不可整合到Control类中之外,其它都可以。因为不能让Control自己创建自己。

  • 准备运行/PrepareToRun:这里主要是加载一些配置信息,目前并未涉及配置文件的加载功能,可把对消息的注册放在这里。

  • 运行/Run:实际产生效用的代码放在这里,对s来说就是循环调用GetClientMessages和DispatchMessages方法。

下面是父类Control设计方案:

  • shared_memory_:这块共享内存是专门用于构造Control对象本身的,对s来说,仅c2s_code_queue和s2c_code_queue支持恢复还不够,s中的那些用户对象,负责管理用户的对象以及消息队列等都需要支持恢复。不然,仅恢复两个code队列的话又有什么意义呢。不过,对于netio的Control来说,这个属性是多余的,因为netio只负责通信,由NControl管理的主要是TCPSocket对象,当netio进程关闭时,TCP连接都将丢失,netio重启后,client也必须要重新连进来才行。让NControl对象支持基于共享内存的恢复其实没什么意义,只要c2s_code_queue和s2c_code_queue支持即可。

    由于Control重载了new运算,在new的时候使用shared_memory_提供的内存来构造对象,对于NControl这种不使用shared_memory_内存的类来说,需要自己重载new运算符,在new中使用原始的::operator new来完成内存分配。

  • c2s_shm_key_/s2c_shm_key_:记录共享内存的key值。

  • max_code_buf_len_:原MAX_CODE_BUF_LEN值。
  • c2s_code_queue_/s2c_code_queue_:两个code队列。
  • c2s_shm_/s2c_shm_;两个code队列对应的共享内存对象。

  • operator new:使用shared_memory_提供的内存空间来构造Control对象。

  • Init:这个是对Control自身的初始化,不同于下面的OnInit方法。这个Init中主要完成了对c2s和s2c两个code队列的创建。并在最后基于shared_memory_(如果是基于它构造Control对象的话)的启动模式(初始化模式或恢复模式)来调用OnInit或OnRecover方法。由于这个两个方法都是虚方法,因此,在运行时实际触发的是子类方法。
  • OnInit:这个Init实际是子类提供(override)的一个Init过程。以On开头命名的方式在MFC中很常见,这表示它是一个“事件”的响应函数。当父类的Init方法判定当前的Control对象需要做初始化时,它就调用OnInit方法,否则调用OnRecover方法。对于子类的开发者而言,他应该理解为,初始化或恢复事件发生了,我要在OnInit或OnRecover方法中添加我需要的处理逻辑,以响应这个事件。

    要注意的是:子类必须要在适当的时机显式调用Control::Init方法,不然的话,上述机制就无法实现。而且,c2s和s2c这两个code队列也是由父类的Init方法进行创建的,因此让它运行是必要的。

    在子类的构造函数最后对Control::Init发起调用是不错的时机。

  • OnRecover:上面已经提到,不过对于netio的Control来说,这个是多余的。

  • PrepareToRun:准备运行。语义上,它应是Run之前要执行的最后一个方法,主要用于加载一些配置。
  • Run:执行核心流程,就是之前图中描述的那些步骤。

这个Control类并没有把CountSize方法抽象出来,而是由子类自己去实现。原因有两个,一是它本身是静态方法,抽象静态方法语法本身就不支持,而且从语义上也讲不通。二是确实没必要,CountSize的目的是计算类占用的空间大小,以分配足够的空间去构造其对象。对Control类来说,其占用的空间主要来自于子类的贡献,父类占据的空间在子类中用sizeof就可以算出。当然,父类中还有几个指针成员, 它们所指向的那些空间,其尺寸是不能通过sizeof获得的。不过,不用担心,这部分内存均由shared_memory_来分配,子类管好自己就行了。

这是分别使用NControl和SControl实现的netio和s,其中netio的监听端口写死在了PrepareToRun中,以后可以把这个端口号放在配置文件中,在PrepareToRun中加载。

新增及改动的文件:


<==  index  ==>