1 写在前面
这是一种应用于事件驱动的端口监控和服务多路化的面向对象封装器。
这种方法早期版本出现在1993年2月发表的C++ Report上。
本文探讨了1993年C++ Report中提出的一种方法,特别是如何使用面向对象技术封装操作系统进程间通信,如select和poll,以创建更安全、可移植的事件多路化框架。
文章介绍了反应堆模式,它简化了事件驱动分布式系统的编程,提供了高级抽象,并减少了直接使用系统调用的复杂性和错误。
2 简单的历史
那是描述使用C++面向对象技术封装操作系统进程间通信服务的一个系列的第三篇。
第一篇文章解释了面向对象包装器的主要原则和动机,它简化了开发正确的,简明的,可移植的以及高效的应用程序。
第二篇文章描述了一种被称为IPC SAP的面向对象包装器,它封装了BSD socket 和System V TLI 系统调用应用程序编程接口。
IPC SAP 使应用程序可以通过类型安全的面向对象的接口访问本地的和远程的IPC协议族,比如TCP/IP。
第三篇文章介绍了一种对于I/O端口监控和基于定时器的事件通知的由select 和poll系统调用提供的机制的面向对象包装器。
select 和poll都允许应用程指定一个在一个或者多个I/O描述符上等待不同类型的输入输出事件的超时时间间隔,
select 和poll检测某个I/O 或者定时器事件的发生并且分发这些事件到适当的应用程序。
和其他许多操作系统应用程序编程接口一样,事件多路化系统非常复杂,容易出错,不易移植而且不容易扩展。
一种被称为反应堆的可扩展的面向对象框架被开发出来以克服这些限制。
反应堆模式提供了一系列的高级的编程抽象,它简化了事件驱动的分布式系统的设计和实现。
反应堆模式同时也将开发人员从容易出错的现存事件多路复用编程接口中解脱出来,同时改善了应用程序在不同的操作系统变种间的可移植性。
反应堆模式和IPC SAP类包装器有些不同,IPC SAP 添加了一个相对薄的面向对象封装在BSD socket and System V TLI应用程序编程接口之上。
另一方面,反应堆模式提供了一系列更加丰富的抽象相对于直接的select 和 poll所提供的。
特别的,反应堆模式整合了基于I/O的端口监控和基于定时器的事件通知为多路化应用程序服务提供一种通用的框架。
端口监控被用于事件驱动的同时为许多并发连接进行I/O的网络服务器。
由于这些服务器必须处理多个连接,对于单一的连接提供无限时间的阻塞I/O是不够灵活的。
同样的,基于定时器的应用程序编程接口使应用程序可以注册一些操作,它们通过一个由反应堆集中控制的定时器周期性或者非周期性的激活。
它有两个主要部分:
第一部分(在这篇文章介绍)描述一个分布式的日志系统,
它激发了有效的事件多路化的需求,检查了其他的几个替代方案,
评价了这些替代方案的优势和劣势,并且将他们和反应堆模式进行比较。
第二部分(出现在后来发表的C++ Report上)集中在反应堆模式的面向对象设计方面。
另外,它讨论了分布式日志系统的设计和实现。
这个例子精确的说明了反应堆模式是如何简化事件驱动的分布式应用程序的开发的。
3 例子:分布式日志系统
为了演示事件多路复用机制的功能,这一节描述了一个处理从多个源头同时接收消息的事件驱动的I/O分布式日志系统。
分布式日志系统为应用程序提供几个服务,这些服务通过网络环境并发的操作。
首先,它为记录某些状态信息提供一个集中的位置以简化对于分布式应用程序行为的管理和跟踪。
为了完成这个功能,客户端的守护进程给发出去的日志记录打上时间戳从而可以按照时间顺序跟踪和重建多个并发进程在不同主机上的执行顺序。
第二,这个功能也使按优先级投递日志记录称为可能。
这些记录被客户端守护进程按他们的重要性接收和转发,而不是按照他们最初产生的顺序。
集中多个分布式应用程序的产生日志到一个单一的服务器也是有用的,因为这使对输出设备比如控制台,打印机,文件或者网络管理数据库的访问得以串行化。
相比之下,如果没有这样的集中设备,对于由多个并发进程组成的应用程序的监控和调试都将变得很困难。
比如,一个普通的C stdio 库的子函数(比如fputs 和 printf)的输出,如果被多进程或者多线程同时调用,当显示在窗口或者控制台的时候经常会纠缠在一起。
这个分布式日志系统被设计为使用客户端/服务器架构。也就是 CS 架构。
服务器日志守护进程搜集,格式化并且输出从客户端日志守护进程转发过来的日志记录,这些客户端日志守护进程运行在多个主机上遍布于一个局域或者广域网。
日志服务器的输出可能被重定向到各种设备,比如打印机,永久存储或者日志管理控制台。
日志系统的进程间通信结构设计几个层次的多路化。
比如,网络上的每一个客户端主机包含多个应用程序进程(比如 P1;P2;P3),他们都可能参与分布式日志系统。
每个参与进程都使用如图1的方框中所描述的应用程序日志API进行格式化调试跟踪或者错误诊断输入到日志记录系统中。
一条日志记录是一个包含几个头域和一个有效载荷的最大值接近1K字节的对象。
当Log Msg::log API被应用程序调用的时候, 它预先考虑将当前进程的ID和程序名字放进记录。
然后使用基于记录的命名管道IPC机制多路化这些组合日志记录到一个单独的运行在每个主机上的日志客户端的守护进程上。
这个客户端守护进程为日志记录预先准备一个时间戳,然后发起一个远程的IPC服务(比如 TCP或者RPC)以多路化这个日志记录到一个运行在一个指定的网络主机上的守护进程中。
服务器运行在一种事件驱动的方式下,同时处理来自多个客户端守护进程的日志记录。
由于参与的应用程序产生日志的行为不同,日志记录可以来自于任意的客户端并且以任意的时间间隔到达服务器端。
一个单独的TCP流连接在客户端日志守护进程和指定的服务器端日志守护进程之间被创建出来。
每一个客户端的连接在服务器端以一个唯一的I/O描述符表示。
另外,服务器也维护着一个专一的I/O描述符以接受从客户端守护进程发过来的想要参与分布式日志系统的连接请求。
在连接建立期间,服务器缓存客户端的主机名(在日志服务器守护进程的图中以椭圆说明),并且使用这些信息在格式化好的日志记录准备输出到输出设备中标识客户端。
分布式日志系统的完整的设计和实现在[3]中进行没描述。
这篇文章的剩余部分通过探索几个其他的处理多路I/O的替代方案描述背景材料。
4 操作系统事件多路化
现代操作系统比如UNIX,Windows NT, 和 OS/2都提供几种技术允许应用程序运行在多个描述符上的同时的I/O。
这部分描述4种替代方案并且比较和对比他们的优势和劣势。
为了集中讨论,每个替代方案都被赋予上面第二部分中讨论的分布式日志系统的特性。
特别的,每个部分表现为一个服务器端日志守护进程的使用替代技术的实现骨架。
为了节省空间并且是描述更加清楚,这些例子利用面向对象的IPC SAP套接字封装器库,这个库在先前的C++ Report文章中有描述。
处理日志记录的函数也被所有的服务端守护进程调用。
这个函数负责接受和处理日志记录并且将他们写到适当的输出设备上。
任何需要串行化访问输出设备的同步机制也在这个函数中进行操作。
通常并发的多进程和多线程是相近的都使开发更加复杂,因为输出必须串行化以避免争夺日志记录从所有的分离的进程中产生。
为了完成这个目标,这个并发服务器守护进程通过某种形式的同步机制(比如信号量,锁或者其他IPC机制比如FIFOs或者消息队列)来处理日志记录子进程。
5 小结
这里介绍了为了理解现存的UNIX处理多路网络I/O应用程序的机制的行为,优势和劣势的必要的背景资料。
一个被称为反应堆的面向对象的封装器被开发出来以封装和克服select 和poll 事件多路化系统调用的限制。
反应堆的面向对象设计和实现在这篇文章(在下一篇C++ Report上出现)的第二部分进行更加详细的讨论。
除了描述类的关系和继承层次,下一篇文章将介绍一个涉及分布式日志系统的可扩展的示例。
这个示例说明了反应堆是如何简化事件驱动的处理多个客户端同时连接的网络服务器开发的。
同步指的是等待I/O操作的完成。
异步指的是不必等待I/O操作完成,直接返回,等到第二阶段完成后,内核主动通知进程结束。
所以区别,就是阻塞/非阻塞对应的是一个当前的一个状态,同步/异步着眼于整个大局或操作。
twisted 用到 了select/poll/epoll. 又取名 基于事件的异步框架。
它使用异步I/O,使用reactor 模式处理事件循环。
使用deferred处理回调函数。
用户可以自己写一个回调函数,放入deferred list中,twsited在事件通知就绪后调用这些回调函数,执行完毕,返回结果。
本文参考:
https://docs.twisted.org/en/stable/