逐步实现TCP服务端Step02-3:继续改进

经过改造,主流程中的RecvBytes、GetOneMessage和SendBytes都不会引起阻塞。只有ProcessOneMessage存在阻塞的可能。

ProcessOneMessage中有潜在的“长操作”,例如:磁盘I/O、大量的计算等。若服务端在Process一个消息的时候进行了这种“长操作”,那么主流程就没法快速的进行下去了。

考虑到数据的收发都是在这个主流程成中完成的。Process“卡住了”,就意味着数据收发工作也无法进行了,虽然数据收发本身并非“长操作”,但它们还是要做无谓的等待。

要保证数据收发工作的畅通,就要将消息处理逻辑与数据收发逻辑进行解耦:把ProcessOneMessage拆出来放到另外的线程中处理,这个线程可以隶属于当前进程,也可以是其他进程中的线程。

解耦之后的两个模块,一个负责通信(communicator),一个负责处理业务(processor)。这两者之间需要通过一种媒介来进行“交流”,这个媒介就是“消息队列”。 准备两个消息队列:c2s_msg_queue存放来自客户的消息(由communicator生产),s2c_msg_queue存放来自服务端的消息(由processor生产)。

还有,前面多次用到了“字节”、“消息”和“数据”这三个词,这里需要明确一下它们的意义。通过socket接口从TCP那里拿到或向其投递的就是一系列的“字节”。这些字节通过应用层的解析就得到了“消息”,对“消息”进行反解析,就得到了一堆“字节”,对应用来说,“消息”是有实际意义的。最后,“数据”是一个笼统的说法,这些原始的字节以及应用消息都可以被叫做“数据”。

蓝色的部分之前提到过,是专门用来存放后续字节个数的空间。“字节”和“消息”的指代内容已在图中标出。“消息”encode后得到一堆字节,字节decode后便得到“消息”。这里把一堆字节(一个或者多个)叫做一个code,对于一个消息来说,其对应的code就是图中红色框出的部分。虽然前置的蓝色字节并非是构成消息的必要元素,但确是解析消息的必要部分。

概念已经明确,现在继续研究communicator和processor。上面说到communicator的职责是通信,processor则负责业务处理。处理业务需要基于“消息”,而通信则不需要,communicator就像是邮递员,processor则是看信或者写信的人。既然如此,communicator与processor通信的队列中就不应该放“消息”而是未经解析的“code”,这样更合理些。处理“消息”的工作让processor自己做就好了,communicator没必要去解析消息。把msg_queue改成code_queue,其实这两种queue中本质上放的都是一堆字节,主要是强调communicator不需要做解析消息的工作: 在存放code的时候,code队列需要记录每个code的长度,做法是在每个code前再增加一些字节(红色部分)进行存放,这些字节对队列的使用者是透明的,由队列来维护:

大概就是这个样子,后面将分别讨论communicator与processor在同一进程和不在同一进程的情况。

<==  index  ==>