Tornado是什么?
Facebook发布了开源网络服务器框架Tornado,该平台基于Facebook刚刚收购的社交聚合网站FriendFeed的实时信息服务开发而来.Tornado由Python编写,是一款轻量级的Web服务器,同时又是一个开发框架。采用非阻塞I/O模型(epoll),主要是为了应对高并发 访问量而被开发出来,尤其适用于comet应用。
1, python 中嵌入 C 语言学习
案例 tornado 如何调用系统 epoll.c [#include <sys/epoll.h>]
Tornado的tornado-x/epoll.c 并不是系统的epoll.c, 而是对系统的epoll.c 的内部函数进行调用
python嵌入c需要三个内容:
1, c文件对外部提供的函数名称
2, 提供python调用的模块映射 (Our method declararations)
3, 初始化c模块的函数 (Module initialization)
写一个简单的例子:
/* epoll.c */
/* 无参数形式 */
Static PyObject*epoll_wait(void) /* void表示不需要形参*/
{
Return Py_None;
}
/* 需要参数形式 */
Static PyObject*epoll_wait(PyObject *self , PyObject * args)
{
/*解析参数方式 */
If(!PyArg_ParseTuple(args,”iii”,&a,&b,&c))
{
Return Py_None;
}
/* 然后就可以使用传入的参数值*/
ReutnrPy_None;
}
/* 模块映射 */
StaticPyMothedDef EpollMothed[] =
{
/*
struct PyMethodDef {
//提供给python调用的函数名称
char* ml_name;
//函数地址
PyCFunction ml_meth;
//是否携带参数 METH_NOARGS METH_VARARGS
intml_flags;
//文本描述信息
char*ml_doc;
};
*/
{“epoll_create”,(PyCFunction)epoll_create,METH_NOARGS,”文本描述”},
{“epoll_wait”,(PyCFunction)epoll_wait,METH_VARARGS,”文本描述”},
{NULL,NULL,0,NULL}
}
/* 初始化模块 */
PyMODINIT_FUNCinitepoll(void)
{
Py_InitModule(“epoll”,EpollMothed);
}
2, tornado 框架学习
Tornado 框架内部利用了很多回调函数,重写,这样设计框架主要是为了得到更多了应用拓展性、使用框架时可以使用自己封装的函数进行处理, 同样类的重写同样是为了更好的拓展
整体流程图如下:
Tornado 基础框架就是 TCPserver 类, 在这个类中实现socket epoll 异步I/O 模型
第一步:TCPserver首先会进行一些初始化工作
第二步:类中的listen函数 主要处理 创建socket, 绑定bind socket ,然后监听listen端口, 并且能够同时监听多个端口 sockets.append(s), 监听完毕以后就开始等待客户端的请求accept(), 当接收到了一个socket连接以后, TCPserver通过一个回调函数_handle_connection(connect,address) 来绑定到一个stream对象, 这个stream对象就是用来处理socket的异步处理,这时基本框架基本就成型了, 最后就是一个重写函数handle_stream(stream, address) , 当我们使用tornado框架进行应用的时候, 我们就需要重写这个函数来进行我们自己的数据包处理,比如http协议/自定义协议等等
第三步: 通过ioloop.start 启动对每个socket 进行事件监听, 当某个已经建立连接的socket状态改变时就调用相应回调函数进行处理
源码解读
Epoll事件处理解读:
1. ioLoop.py 232H def start(self):
2.
3. //初始化
4. self._handlers= {}
5. self._events= {}
6. //检测事件并且返回事件信息
7. event_pairs= self._impl.poll(poll_timeout)
8. // 外层循环不断检测socket事件
9. whileTrue:
10. //用于添加事件
11. self._events.update(event_pairs)
12.
13. //当有事件时
14. while self._events:
15. //获取事件的文件描述符及事件类型
16. fd, events =self._events.popitem()
17. try:
18. //进行事件处理
19. self._handlers[fd](fd,events)
20. except (OSError, IOError), e:
21. //异常处理
22. if e.args[0] ==errno.EPIPE:
23. # Happens when theclient closes the connection
24. pass
25. else:
26. logging.error("Exceptionin I/O handler for fd %d",
27. fd,exc_info=True)
28. except Exception:
29. logging.error("Exceptionin I/O handler for fd %d",
30. fd,exc_info=True)
添加句柄函数解读:
IoLoop.py 179H 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) self._impl.register(fd, events | self.ERROR)
|
为握手成功的socket添加处理函数解读:nettutil.py 306行
def add_accept_handler(sock, callback, io_loop=None): """Adds an ``IOLoop`` event handler to accept new connections on ``sock``.
When a connection is accepted, ``callback(connection, address)`` will be run (``connection`` is a socket object, and ``address`` is the address of the other end of the connection). Note that this signature is different from the ``callback(fd, events)`` signature used for ``IOLoop`` handlers. """ if io_loop is None: io_loop = IOLoop.instance() def accept_handler(fd, events): while True: try: connection, address = sock.accept() except socket.error, e: if e.args[0] in (errno.EWOULDBLOCK, errno.EAGAIN): return raise callback(connection, address) // 这里的callback就是调用下面的_handle_connection函数, 这样对我们理解就好多了
|
def _handle_connection(self, connection, address): if self.ssl_options is not None: assert ssl, "Python 2.6+ and OpenSSL required for SSL" try: connection = ssl.wrap_socket(connection, server_side=True, do_handshake_on_connect=False, **self.ssl_options) except ssl.SSLError, err: if err.args[0] == ssl.SSL_ERROR_EOF: return connection.close() else: raise except socket.error, err: if err.args[0] == errno.ECONNABORTED: return connection.close() else: raise try: if self.ssl_options is not None: stream = SSLIOStream(connection, io_loop=self.io_loop) else: stream = IOStream(connection, io_loop=self.io_loop) self.handle_stream(stream, address) except Exception: logging.error("Error in connection callback", exc_info=True) |
异步流处理函数解读:
这个函数是我们需要操作字节流的时候才会使用, 比如stream.read_bytes, stream.write等等,
参数二state 就是我们需要使用的读写状态, 当我们使用stream.read_bytes进行读取缓冲区的时候,add_handler 会通过对特定的socket的缓冲区添加_handle_events进行相应的处理
def _add_io_state(self, state):
if self.socket is None: # connection has been closed, so there can be no future events return if self._state is None: self._state = ioloop.IOLoop.ERROR | state with stack_context.NullContext(): self.io_loop.add_handler( self.socket.fileno(), self._handle_events, self._state) elif not self._state & state: self._state = self._state | state self.io_loop.update_handler(self.socket.fileno(), self._state) |
一旦socket可读写, 就调用回调函数_handle_events处理IO
事件. 打开iostream.py,定位到_handle_events
31. def _handle_events(self, fd, events):
32. if not self.socket:
33. logging.warning("Got events for closed stream %d", fd)
34. return
35. try:
36. if events & self.io_loop.READ:
37. self._handle_read()
38. if not self.socket:
39. return
40. if events & self.io_loop.WRITE:
41. if self._connecting:
42. self._handle_connect()
43. self._handle_write()
44. if not self.socket:
45. return
46. if events & self.io_loop.ERROR:
47. self.io_loop.add_callback(self.close)
48. return
49. state = self.io_loop.ERROR
50. if self.reading():
51. state |= self.io_loop.READ
52. if self.writing():
53. state |= self.io_loop.WRITE
54. if state != self._state:
55. self._state = state
56. self.io_loop.update_handler(self.socket.fileno(), self._state)
57. except:
58. logging.error("Uncaught exception, closing connection.",
59. exc_info=True)
60. self.close()
61. raise
读取字节流解读:iostream.py 392
当我们使用stream.read_bytes()函数的时候实际最终就是调用了这个函数处理,函数中self._run_callback函数第一个参数是一个回调函数,第二个参数返回读取的字节流, 我们就是通过第一个参数的回调函数来处理读取的字节流进行拓展处理
1. def _read_from_buffer(self):
2. """Attempts to completethe currently-pending read from the buffer.
3.
4. Returns True if the read was completed.
5. """
6. if self._read_bytes is not None:
7. if self._streaming_callback is notNone and self._read_buffer_size:
8. bytes_to_consume =min(self._read_bytes, self._read_buffer_size)
9. self._read_bytes -=bytes_to_consume
10. self._run_callback(self._streaming_callback,
11. self._consume(bytes_to_consume))
I/O模型 Epoll/poll/select区别:
Linux中异步IO等待无非就三个系统调用:select, poll和epoll。很多人无法理解三种调用的区别,或不够了解,今天就结合Linuxkernel code详细描述三个的区别!
select:
select 的限制就是最大1024个fd,可以查看kernel中的posix_types.h,里面定义了fdset数据结构,显然select不适合poll大量fd的场景(如webserver)。
include/linux/posix_types.h :
1. #undef __NFDBITS
2. #define __NFDBITS (8 * sizeof(unsigned long))
3.
4. #undef __FD_SETSIZE
5. #define __FD_SETSIZE 1024
6.
7. #undef __FDSET_LONGS
8. #define __FDSET_LONGS (__FD_SETSIZE/__NFDBITS)
9.
10. #undef __FDELT
11. #define __FDELT(d) ((d) / __NFDBITS)
12.
13. #undef __FDMASK
14. #define __FDMASK(d) (1UL << ((d) % __NFDBITS))
15.
16. typedef struct {
17. unsigned long fds_bits [__FDSET_LONGS];
18. } __kernel_fd_set;
poll:
poll相对于select改进了fdset size的限制,poll没有再使用fdset数组结构,反而使用了pollfd,这样用户可以自定义非常大的pollfd数组,这个pollfd数组在kernel中的表现形式是poll_list链表,这样就不存在了1024的限制了,除此之外poll相比select无太大区别。
1. int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds,
2. struct timespec *end_time)
3. {
4. struct poll_wqueues table;
5. int err = -EFAULT, fdcount, len, size;
6. /* Allocate small arguments on the stack to save memory and be
7. faster - use long to make sure the buffer is aligned properly
8. on 64 bit archs to avoid unaligned access */
9. long stack_pps[POLL_STACK_ALLOC/sizeof(long)];
10. struct poll_list *const head = (struct poll_list *)stack_pps;
11. struct poll_list *walk = head;
12. unsigned long todo = nfds;
13.
14. if (nfds > rlimit(RLIMIT_NOFILE))
15. return -EINVAL;
16.
17. len = min_t(unsigned int, nfds, N_STACK_PPS);
18. for (;;) {
19. walk->next = NULL;
20. walk->len = len;
21. if (!len)
22. break;
23.
24. if (copy_from_user(walk->entries, ufds + nfds-todo,
25. sizeof(struct pollfd) * walk->len))
26. goto out_fds;
27.
28. todo -= walk->len;
29. if (!todo)
30. break;
31.
32. len = min(todo, POLLFD_PER_PAGE);
33. size = sizeof(struct poll_list) + sizeof(struct pollfd) * len;
34. walk = walk->next = kmalloc(size, GFP_KERNEL);
35. if (!walk) {
36. err = -ENOMEM;
37. goto out_fds;
38. }
39. }
epoll:
select与poll的共同点是fd有数据后kernel会遍历所有fd,找到有效fd后初始化相应的revents,用户空间程序须再次遍历整个fdset,以找到有效的fd,这样实际上就遍历了两次fd数组表,对于极大量fd的情况,这样的性能非常不好,请看一下do_poll代码:
1. static int do_poll(unsigned int nfds, struct poll_list *list,
2. struct poll_wqueues *wait, struct timespec *end_time)
3. {
4. poll_table* pt = &wait->pt;
5. ktime_t expire, *to = NULL;
6. int timed_out = 0, count = 0;
7. unsigned long slack = 0;
8.
9. /* Optimise the no-wait case */
10. if (end_time && !end_time->tv_sec && !end_time->tv_nsec) {
11. pt = NULL;
12. timed_out = 1;
13. }
14.
15. if (end_time && !timed_out)
16. slack = select_estimate_accuracy(end_time);
17.
18. for (;;) {
19. struct poll_list *walk;
20.
21. for (walk = list; walk != NULL; walk = walk->next) {
22. struct pollfd * pfd, * pfd_end;
23.
24. pfd = walk->entries;
25. pfd_end = pfd + walk->len;
26. for (; pfd != pfd_end; pfd++) {
27. /*
28. * Fish for events. If we found one, record it
29. * and kill the poll_table, so we don't
30. * needlessly register any other waiters after
31. * this. They'll get immediately deregistered
32. * when we break out and return.
33. */
34. if (do_pollfd(pfd, pt)) {
35. count++;
36. pt = NULL;
37. }
38. }
39. }
epoll的出现解决了这种问题,那么epoll是如何做到的呢? 我们知道select, poll和epoll都是使用waitqueue调用callback函数去wakeup你的异步等待线程的,如果设置了timeout的话就起一个hrtimer,select和poll的callback函数并没有做什么事情,但epoll的waitqueue callback函数把当前的有效fd加到ready list,然后唤醒异步等待进程,所以你的epoll函数返回的就是这个ready list, ready list中包含所有有效的fd,这样一来kernel不用去遍历所有的fd,用户空间程序也不用遍历所有的fd,而只是遍历返回有效fd链表,所以epoll自然比select和poll更适合大数量fd的场景。
1. static int ep_send_events(struct eventpoll *ep,
2. struct epoll_event __user *events, int maxevents)
3. {
4. struct ep_send_events_data esed;
5.
6. esed.maxevents = maxevents;
7. esed.events = events;
8.
9. return ep_scan_ready_list(ep, ep_send_events_proc, &esed);
10. }
现在大家应该明白select,poll和epoll的区别了吧!有人问既然select和poll有这么明显的缺陷,为什么不改掉kernel中的实现呢? 原因很简单,后向ABI兼容,select和poll的ABI无法返回ready list,只能返回整个fd数组,所以用户只得再次遍历整个fd数组以找到哪些fd是有数据的。
epoll还包括“Level-Triggered” 和“Edge-Triggered”,这两个概念在这里就不多赘述了,因为"manepoll"里面解释的非常详细,还有使用epoll的example。