看看IOLoop的类组织结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
| - - - IOLoop - - - __init__( self ,
impl = None ) - - - instance( cls ) - - - initialized( cls ) - - - add_handler( self ,
fd, handler, events) - - - update_handler( self ,
fd, events) - - - remove_handler( self ,
fd) - - - set_blocking_signal_threshold( self ,
seconds, action) - - - set_blocking_log_threshold( self ,
seconds) - - - log_stack( self ,
signal, frame) - - - start( self ) - - - stop( self ) - - - running( self ) - - - add_timeout( self ,
deadline, callback) - - - remove_timeout( self ,
timeout) - - - add_callback( self ,
callback) - - - _wake( self ) - - - _run_callback( self ,
callback) - - - handle_callback_exception( self ,
callback) - - - _read_waker( self ,
fd, events) - - - _set_nonblocking( self ,
fd) - - - _set_close_exec( self ,
fd) - - - | |
从上一章的Demo里面可以看到最重要的对外提供的方法有
0.instance() @classmethod
1.add_handler(...)
2.start()
类似于传统的事件驱动方式,这里的使用方式也很简单
从IOLoop类中看起:
先是自己定义了几个EPOLL的宏,就是EPOLL的事件类型
#epoll 的事件类型,类似于这里的宏定义
_EPOLLIN = 0x001
_EPOLLPRI = 0x002
_EPOLLOUT = 0x004
_EPOLLERR = 0x008
_EPOLLHUP = 0x010
_EPOLLRDHUP = 0x2000
_EPOLLONESHOT = (1 << 30)
_EPOLLET = (1 << 31)
# Our events map exactly to the epoll events
#将这几个事件类型重定义一番
NONE = 0
READ = _EPOLLIN
WRITE = _EPOLLOUT
ERROR = _EPOLLERR | _EPOLLHUP | _EPOLLRDHUP
常用的就是三种,READ,WRITE,ERROR
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
#ioloop的构造函数 def __init__( self ,
impl = None ): #选择异步事件循环监听方式,默认是epoll,后面的_impl都是指的是epoll self ._impl = impl or _poll() #自省,查看
self._impl 中是否有 fileno #如果有,就关闭起exec性质 if hasattr ( self ._impl, 'fileno' ): self ._set_close_exec( self ._impl.fileno()) #
_set_close_exec 是一个类方法,下面有定义 #
当 FD_CLOEXEC 设置了以后,exec() 函数执行的时候会自动关闭描述符 """
def _set_close_exec(self, fd): flags
= fcntl.fcntl(fd, fcntl.F_GETFD) fcntl.fcntl(fd,
fcntl.F_SETFD, flags | fcntl.FD_CLOEXEC) """ #handlers
是一个函数集字典 self ._handlers = {} self ._events = {} #回调函数使用的是列表 self ._callbacks = [] #用来记录链接超时 self ._timeouts = [] self ._running = False self ._stopped = False self ._blocking_signal_threshold = None #
Create a pipe that we send bogus data to when we want to wake #
the I/O loop when it is idle #判断是否是
NT 操作系统 if os.name
! = 'nt' : #创建一个管道
,返回的为读写两端的文件描述符 r,
w = os.pipe() #设置为非阻塞 self ._set_nonblocking(r) self ._set_nonblocking(w) self ._set_close_exec(r) self ._set_close_exec(w) #分别以读方式和写方式打开管道 self ._waker_reader = os.fdopen(r, "rb" , 0 ) self ._waker_writer = os.fdopen(w, "wb" , 0 ) else : #如若不是
NT 系统,改用win32 支持的管道类型 self ._waker_reader = self ._waker_writer = win32_support.Pipe() r = self ._waker_writer.reader_fd #将
管道的 read端与 函数 _read_waker 关联,事件类型为 READ #这里也是IO
多路复用的一种机制,将管道的描述符也添加进多路复用的IO 管理 self .add_handler(r, self ._read_waker, self .READ) |
注意最后的几点,将管道描述符的读端也加入事件循环检查,并设置相应的回调函数,这样做的好处是以便事件循环阻塞而没有相应描述符出现,需要在最大timeout时间之前返回,就可以向这个管道发送一个字符,用来终止阻塞在监听阶段的事件循环监听函数。
看看waker是这样定义的:
1
2
3
4
5
|
def _wake( self ): try : self ._waker_writer.write( "x" ) except IOError: pass |
需要唤醒阻塞中的事件循环监听函数的时候,只需要向管道写入一个字符,就可以提前结束循环
instance就是简单的返回一个实例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
def instance( cls ): """Returns
a global IOLoop instance. Most
single-threaded applications have a single, global IOLoop. Use
this method instead of passing around IOLoop instances throughout
your code. A
common pattern for classes that depend on IOLoops is to use a
default argument to enable programs with multiple IOLoops but
not require the argument for simpler applications: class
MyClass(object): def
__init__(self, io_loop=None): self.io_loop
= io_loop or IOLoop.instance() """ if not hasattr ( cls , "_instance" ): cls ._instance = cls () return cls ._instance |
instance()是一个静态方法,代表此IOLoop是一个单实例方法,一个进程只有一个
在add_handler()里面
1
2
3
4
5
6
7
8
|
#将文件描述符发生相应的事件时的回调函数对应 def add_handler( self ,
fd, handler, events): """Registers
the given handler to receive the given events for fd.""" self ._handlers[fd] = stack_context.wrap(handler) #在
epoll 中注册对应事件 #epoll_ctl self ._impl.register(fd,
events | self .ERROR) #更新相应的事件类型 |
可以看到,使用字典的方式,每一个fd就对应一个handler,下次事件循环返回的时候按照返回后的fd列表,依次调用相应的callback
|------
在tornado中,函数是通过stack_context.wrap()包装过,可以用来记录上下文
如果需要调用被包装过的函数,需要调用方法
_run_callback(self, callback)
这个函数将包装过的callback作为参数出入,然后执行函数
1
2
3
4
5
6
7
|
def _run_callback( self ,
callback): try : callback() except (KeyboardInterrupt,
SystemExit): raise except : self .handle_callback_exception(callback) |
当函数执行发生异常时,可以记录下函数执行状态
-------|
_impl.register就是被封装过的epoll的epoll_ctl,参数是EPOLL_CTL_ADD
见同一个文件下的_EPoll类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
class _EPoll( object ): """An
epoll-based event loop using our C module for Python 2.5 systems""" _EPOLL_CTL_ADD = 1 _EPOLL_CTL_DEL = 2 _EPOLL_CTL_MOD = 3 def __init__( self ): self ._epoll_fd = epoll.epoll_create() def fileno( self ): return self ._epoll_fd def register( self ,
fd, events): epoll.epoll_ctl( self ._epoll_fd, self ._EPOLL_CTL_ADD,
fd, events) def modify( self ,
fd, events): epoll.epoll_ctl( self ._epoll_fd, self ._EPOLL_CTL_MOD,
fd, events) def unregister( self ,
fd): epoll.epoll_ctl( self ._epoll_fd, self ._EPOLL_CTL_DEL,
fd, 0 ) def poll( self ,
timeout): return epoll.epoll_wait( self ._epoll_fd, int (timeout * 1000 )) |
总结:这一章讲了IOLoop中的几个重要函数,后面依次会有分析其他方法,还有其中一些细节值得平常注意的。