前言: 在看 tornado v1.0 的服务器部分的源码时,当时傻乎乎的还不懂啥是 reactor 设计模式,看得真心是头痛!那时,只知道一个叫 单例模式的。看来,软件的设计架构还是真心有用的。(这是个套路...)
接下来就简单的分析一下 tornado 中的 reactor pattern, 由于才疏学浅,难免有错,还请指教!
预备知识:知道 socket, epoll 的原理及使用,当然,最好也是看了 tornado 中的服务器部分的源码以及这篇 Reactor: An Object Behavioral Pattern for
Demultiplexing and Dispatching Handles for Synchronous Events
What's Reactor
来自 wikipedia 的简明版:
The reactor design pattern is an event handling pattern for handling service requests delivered concurrently to a service handler by one or more inputs. The service handler then demultiplexes the incoming requests and dispatches them synchronously to the associated request handlers.
一个 Reactor 中通常有几个元素:
Resources
Any resource that can provide input to or consume output from the system.Synchronous Event Demultiplexer
Uses an event loop to block on all resources. The demultiplexer sends the resource to the dispatcher when it is possible to start a synchronous operation on a resource without blocking (Example: a synchronous call to read() will block if there is no data to read. The demultiplexer uses select() on the resource, which blocks until the resource is available for reading. In this case, a synchronous call to read() won't block, and the demultiplexer can send the resource to the dispatcher.)Dispatcher
Handles registering and unregistering of request handlers. Dispatches resources from the demultiplexer to the associated request handler.Request Handler
An application defined request handler and its associated resource.
来自这篇 Reactor 文章的详细版
The Reactor design pattern handles service requests that are
delivered concurrently to an application by one or more
clients. Each service in an application may consist of
serveral methods and is represented by a separate event handler
that is responsible for dispatching service-specific requests.
Dispatching of event handlers is performed by an initiation
dispatcher, which manages the registered event handlers.
Demultiplexing of service requests is performed by a
synchronous event demultiplexer.
Handles
Identify resources that are managed by an OS. These resources commonly include network connections, open files, timers, synchronization objects, etc.Synchronous Event Demultiplexer
Blocks awaiting events to occur on a set of Handles. It returns when it is possible to initiate an operation on a Handle without blocking. A common demultiplexer for I/O events is select, which is an event demultiplexing system call provided by the UNIX and Win32 OS platforms. The select call indicates which Handles can have operations invoked on them synchronously without blocking the application process.Initiation Dispatcher
Defines an interface for registering, removing, and dispatching Event Handlers. Ultimately, the Synchronous Event Demultiplexer is responsible for waiting until new events occur. When it detects new events, it informs the Initiation Dispatcher to call back application-specific event handlers. Common events include connection acceptance events, data input and output events, and timeout events.Event Handler
Specifies an interface consisting of a hook method that abstractly represents the dispatching operation for service-specific events. This method must be implemented by application-specific services.Concrete Event Handler
Implements the hook method, as well as the methods to process these events in an application-specific manner. Applications register Concrete Event Handlers with the Initiation Dispatcher to process certain types of events. When these events arrive, the Initiation Dispatcher calls back the hook method of the appropriate Concrete Event Handler.
Reactor structure in Tornado
由上面对 reactor structure 的理解,进一步推理到 Tornado 中:
handle 或是 resource 就是指一个网络连接 socket;
一个 event 就是这个 socket 监听的类型, 比如 read or write etc;
handler, 在tornado 中就是一个 RequestHandler class. 上面的 Event Handler 对应 tornado 中 RequestHandler 基类,而 Concrete Event Handler 对应 tornado 中用户自定义的继承 RequestHandler 的类, 如下:
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")
Synchronous Event Demultiplexer (或者称它为 Notifier) 就如同 epoll 或者 select, 用于监听注册了特定 event 事件的 handle.
Initiation Dispatcher 就是 tornado 中的 IOLoop.
General Collaborations
各模块之间的协作,如下图所示.
请注意上图最左边纵向的字为: INITIALIZATION MODE, EVENT HANDLING MODE. 这意味着可以将这个模式的工作方式分解成这2个小模块来分析.
Initialization Mode
1 When an application registers a Concrete Event Handler with the Initiation Dispatcher the application indicates the type of event(s) this Event Handler wants the Initiation Dispatcher to notify it about when the event(s) occur on the associated Handle.
2 The Initiation Dispatcher requests each Event Handler to pass back its internal Handle. This Handle identifies the Event Handler to the
OS.3 After all Event Handlers are registered, an application calls handle events to start the Initiation Dispatcher’s event loop. At this point, the Initiation Dispatcher combines the Handle from each registered Event Handler and uses the Synchronous Event Demultiplexer to wait for events to occur on these Handles. For instance, the TCP protocol layer uses the select synchronous event demultiplexing operation to wait for client logging record events to arrive on connected socket Handles.
Event Handling Mode
1 The Synchronous Event Demultiplexer notifies the Initiation Dispatcher when a Handle corresponding to an event source becomes “ready,” e.g., that a TCP socket is “ready for reading.”
2 The Initiation Dispatcher triggers Event Handler hook method in response to events on the ready Handles. When events occur, the Initiation Dispatcher uses the Handles activated by the event sources as “keys” to locate and dispatch the appropriate Event Handler’s hook method.
3 The Initiation Dispatcher calls back to the handle event hook method of the Event Handler to perform application-specific functionality in response to an event. The type of event that occurred can be passed as a parameter to the method and used internally by this method to perform additional service specific demultiplexing and dispatching.
Initialization Mode in Tornado
1 在 Application 类中,生成 url 映射, 一个url对应一个XXXRequestHandler处理方法; 在服务端中,创建了socket对象, 单例模式创建IOLoop对象,然后将socket对象句柄作为key,被封装了的函数_handle_events作为value,添加到IOLoop对象的_handlers字段中(向Initiation Dispatcher 中注册 Handlers, 建议 socket fd 到 handler 的映射)
2 向epoll中注册监听服务端socket对象的读可用事件(向 Synchronous Event Demultiplexer 中注册 handle 监听的 event 类型)
如下为 tornado v1.0.0 代码
class IOLoop(object):
...
def add_handler(self, fd, handler, events):
"""Registers the given handler to receive the given events for fd."""
self._handlers[fd] = handler
self._impl.register(fd, events | self.ERROR)
- 3 开始IOLoop 调用 start() 函数开始Event Loop, epoll 开始监听注册了的事件(对应Initiation Dispatcher 调用 handle_events() );
class IOLoop(object):
...
def start(self):
"""Starts the I/O loop.
The loop will run until one of the I/O handlers calls stop(), which
will make the loop stop after the current event iteration completes.
"""
...
while True:
# Never use an infinite timeout here - it can stall epoll
poll_timeout = 0.2
...
event_pairs = self._impl.poll(poll_timeout)
...
self._events.update(event_pairs)
while self._events:
fd, events = self._events.popitem()
...
self._handlers[fd](fd, events)
...
Event Handling Mode in Tornado
1 当 epoll 监听到一个或多个事件到来时,将其放到 event_pairs 中(如上面代码所示, Synchronous Event Demultiplexer通知Initiation Dispatcher handle 的到来 );
2,3 根据handle 即 socket fd, IOLoop 找到并调用注册了的 XXXRequestHandler 中的hood method, 比如 get, post..给予响应,返回 response 给 client;
Others
Define the Event Handling Interface
对应 tornado 中 RequestHandler 类的接口实现,有两种方法: 一种是 A single-method interface, 另一种是 A multi-method interface; 而 Tornado 中 RequestHandler 采用的是后种即 A multi-method Interface 的设计方法,因为HTTP/1.1 总共就8种方法: GET HEAD POST OPTIONS PUT DELETE TRACE CONNECT;
class RequestHandler(object):
...
def head(self, *args, **kwargs):
raise HTTPError(405)
def get(self, *args, **kwargs):
raise HTTPError(405)
...
Determine the Number of Initiation Dispatchers in an Application
"Many applications can be structured using just one instance of the Reactor pattern. In this case, the Initiation Dispatcher can be implemented as a Singleton".
而 Tornado 中的 IOLoop 就是采用 Singleton 模式的实现;