前言: 在看 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 模式的實現;