epollpython
㈠ 怎么通俗理解python epoll
首先我们来定义流的概念,一个流可以是文件,socket,pipe等等可以进行I/O操作的内核对象。不管是文件,还是套接字,还是管道,我们都可以把他们看作流。
现在我们来讨论I/O的操作,通过read,我们可以从流中读入数据;通过write,我们可以往流写入数据。现在假定一个情形,我们需要从流中读数据,但是流中还没有数据,(典型的例子为,客户端要从socket读如数据,但是服务器还没有把数据传回来),这时候该怎么办?
阻塞:阻塞是个什么概念呢?比如某个时候你在等快递,但是你不知道快递什么时候过来,而且你没有别的事可以干(或者说接下来的事要等快递来了才能做);那么你可以去睡觉了,因为你知道快递把货送来时一定会给你打个电话(假定一定能叫醒你)。
非阻塞忙轮询:接着上面等快递的例子,如果用忙轮询的方法,那么你需要知道快递员的手机号,然后每分钟给他挂个电话:“你到了没?”
很明显一般人不会用第二种做法,不仅显很无脑,浪费话费不说,还占用了快递员大量的时间。
大部分程序也不会用第二种做法,因为第一种方法经济而简单,经济是指消耗很少的CPU时间,如果线程睡眠了,就掉出了系统的调度队列,暂时不会去瓜分CPU宝贵的时间片了。
为了了解阻塞是如何进行的,我们来讨论缓冲区,以及内核缓冲区,最终把I/O事件解释清楚。缓冲区的引入是为了减少频繁I/O操作而引起频繁的系统调用(你知道它很慢的),当你操作一个流时,更多的是以缓冲区为单位进行操作,这是相对于用户空间而言。对于内核来说,也需要缓冲区。
假设有一个管道,进程A为管道的写入方,B为管道的读出方。一开始内核缓冲区是空的,B作为读出方,被阻塞着。然后首先A往管道写入,这时候内核缓冲区由空的状态变到非空状态,内核就会产生一个事件告诉B该醒来了,这个事件姑且称之为“缓冲区非空”。但是“,缓冲区非空”事件通知B后,B却还没有读出数据;且内核许诺了不能把写入管道中的数据丢掉这个时候,A写入的数据会滞留在内核缓冲区中,如果内核也缓冲区满了,B仍未开始读数据,最终内核缓冲区会被填满,这个时候会产生一个I/O事件,告诉进程A,你该等等(阻塞)了,我们把这个事件定义为“缓冲区满”。后来B终于开始读数据了,于是内核的缓冲区空了出来,这时候内核会告诉A,内核缓冲区有空位了,你可以从长眠中醒来了,继续写数据了,我们把这个事件叫做“缓冲区非满”。也许事件“缓冲区非满“已经通知了A,但是A也没有数据写入了,而B继续读出数据,知道内核缓冲区空了。这个时候内核就告诉B,你需要阻塞了!,我们把这个时间定为“缓冲区空”。
这四个情形涵盖了四个I/O事件,缓冲区满,缓冲区空,缓冲区非空,缓冲区非满(注都是说的内核缓冲区,且这四个术语都是我生造的,仅为解释其原理而造)。这四个I/O事件是进行阻塞同步的根本。(如果不能理解“同步”是什么概念,请学习操作系统的锁,信号量,条件变量等任务同步方面的相关知识)。
然后我们来说说阻塞I/O的缺点:阻塞I/O模式下,一个线程只能处理一个流的I/O事件。如果想要同时处理多个流,要么多进程(fork),要么多线程(pthread_create),很不幸这两种方法效率都不高。
现在我们再来考虑一下”非阻塞忙轮询“的I/O方式,我们发现我们可以同时处理多个流了(把一个流从阻塞模式切换到非阻塞模式再此不予讨论):
[java]view plain
whiletrue{
active_stream[]=epoll_wait(epollfd)
foriinactive_stream[]{
readorwritetill
}
}
[java]view plain
whiletrue{
active_stream[]=epoll_wait(epollfd)
foriinactive_stream[]{
readorwritetill
}
}
㈡ mac os x 下的python 为什么没有epoll
介绍
从2.6版本开始, python 提供了使用linux epoll 的功能. 这篇文章通过3个例子来大致介绍如何使用它. 欢迎提问和反馈.
阻塞式socket通讯
第一个例子是一个简单的python3.0版本的服务器代码, 监听8080端口的http请求, 打印结果到命令行, 回应http response给客户端.
行 9: 建立服务器的socket
行 10: 允许11行的bind()操作, 即使其他程序也在监听同样的端口. 不然的话, 这个程序只能在其他程序停止使用这个端口之后的1到2分钟后才能执行.
行 11: 绑定socket到这台机器上所有IPv4地址上的8080端口.
行 12: 告诉服务器开始响应从客户端过来的连接请求.
行 14: 程序会一直停在这里, 直到建立了一个连接. 这个时候, 服务器socket会建立一个新的socket, 用来和客户端通讯. 这个新的socket是accept()的返回值, address对象标示了客户端的IP地址和端口.
行 15-17: 接收数据, 直到一个完整的http请求被接收完毕. 这是一个简单的http服务器实现.
行 18: 为了方便验证, 打印客户端过来的请求到命令行.
行 19: 发送回应.
行 20-22: 关闭连接, 以及服务器的监听socket.
python官方 HOWTO 里面有具体如何使用socket编程的描述.
1 import socket
2
3 EOL1 = b'\n\n'
4 EOL2 = b'\n\r\n'
5 response = b'HTTP/1.0 200 OK\r\nDate: Mon, 1 Jan 1996 01:01:01 GMT\r\n'
6 response += b'Content-Type: text/plain\r\nContent-Length: 13\r\n\r\n'
7 response += b'Hello, world!'
8
9 serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
10 serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
11 serversocket.bind(('0.0.0.0', 8080))
12 serversocket.listen(1)
13
14 connectiontoclient, address = serversocket.accept()
15 request = b''
16 while EOL1 not in request and EOL2 not in request:
17 request += connectiontoclient.recv(1024)
18 print(request.decode())
19 connectiontoclient.send(response)
20 connectiontoclient.close()
21
22 serversocket.close()
第2个例子, 我们在15行加上了一个循环, 用来循环处理客户端请求, 直到我们中断这个过程(在命令行下面输入键盘中断, 比如Ctrl-C). 这个例子更明显地表示出来了, 服务器socket并没有用来做数据处理, 而是接受服务器过来的连接, 然后建立一个新的socket, 用来和客户端通讯.
最后的23-24行确保服务器的监听socket最后总是close掉, 即使出现了异常.
1 import socket
2
3 EOL1 = b'\n\n'
4 EOL2 = b'\n\r\n'
5 response = b'HTTP/1.0 200 OK\r\nDate: Mon, 1 Jan 1996 01:01:01 GMT\r\n'
6 response += b'Content-Type: text/plain\r\nContent-Length: 13\r\n\r\n'
7 response += b'Hello, world!'
8
9 serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
10 serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
11 serversocket.bind(('0.0.0.0', 8080))
12 serversocket.listen(1)
13
14 try:
15 while True:
16 connectiontoclient, address = serversocket.accept()
17 request = b''
18 while EOL1 not in request and EOL2 not in request:
19 request += connectiontoclient.recv(1024)
20 print('-'*40 + '\n' + request.decode()[:-2])
21 connectiontoclient.send(response)
22 connectiontoclient.close()
23 finally:
24 serversocket.close()
异步socket和linux epoll的优势
第2个例子里面的socket采用的是阻塞方式, 因为python解释器在出现事件之前都处在停止状态. 16行的accept()一直阻塞, 直到新的连接进来. 19行的recv()也是一直阻塞, 直到从客户端收到数据(或者直到没有数据可以接收). 21行的send()也一直阻塞, 直到所有需要发送给客户端的数据都交给了linux内核的发送队列.
当一个程序采用阻塞socket的时候, 它经常采用一个线程(甚至一个进程)一个socket通讯的模式. 主线程保留服务器监听socket, 接受进来的连接, 一次接受一个连接, 然后把生成的socket交给一个分离的线程去做交互. 因为一个线程只和一个客户端通讯, 在任何位置的阻塞都不会造成问题. 阻塞本身不会影响其他线程的工作.
多线程阻塞socket模式代码清晰, 但是有几个缺陷, 可能很难确保线程间资源共享工作正常, 可能在只有一个CPU的机器上效率低下.
C10K(单机1万连接问题!) 探讨了其他处理并行socket通讯的模式. 一种是采用异步socket. socket不会阻塞, 直到特定事件发生. 程序在异步socket上面进行一个特定操作, 并且立即得到一个结果, 不管执行成功或者失败. 然后让程序决定下一步怎么做. 因为异步socket是非阻塞的, 我们可以不采用多线程. 所有的事情都可以在一个线程里面完成. 虽然这种模式有它需要面对的问题, 它对于特定程序来说还是不错的选择. 也可以和多线程合起来使用: 单线程的异步socket可以当作服务器上面处理网络的一个模块, 而线程可以用来访问阻塞式的资源, 比如数据库.
Linux 2.6有一些方式来管理异步socket, python API能够用的有3种: select, poll和epoll. epoll和poll比select性能更好, 因为python程序不需要为了特定的事件去查询单独的socket, 而是依赖操作系统来告诉你什么socket产生了什么事件. epoll比poll性能更好, 因为它不需要每次python程序查询的时候, 操作系统都去检查所有的socket, 在事件产生的时候, linux跟踪他们, 然后在python程序调用的时候, 返回具体的列表. 所以epoll在大量(上千)并行连接下, 是一种更有效率, 伸缩性更强的机制. 图示.
采用epoll的异步socket编程示例
采用epoll的程序一般这样操作:
建立一个epoll对象
告诉epoll对象, 对于一些socket监控一些事件.
问epoll, 从上次查询以来什么socket产生了什么事件.
针对这些socket做特定操作.
告诉epoll, 修改监控socket和/或监控事件.
重复第3步到第5步, 直到结束.
销毁epoll对象.
采用异步socket的时候第3步重复了第2步的事情. 这里的程序更复杂, 因为一个线程需要和多个客户端交互.
行 1: select模块带有epoll功能
行 13: 因为socket默认是阻塞的, 我们需要设置成非阻塞(异步)模式.
行 15: 建立一个epoll对象.
行 16: 注册服务器socket, 监听读取事件. 服务器socket接收一个连接的时候, 产生一个读取事件.
行 19: connections表映射文件描述符(file descriptors, 整型)到对应的网络连接对象上面.
行 21: epoll对象查询一下是否有感兴趣的事件发生, 参数1说明我们最多等待1秒的时间. 如果有对应事件发生, 立刻会返回一个事件列表.
行 22: 返回的events是一个(fileno, event code)tuple列表. fileno是文件描述符, 是一个整型数.
行 23: 如果是服务器socket的事件, 那么需要针对新的连接建立一个socket.
行 25: 设置socket为非阻塞模式.
行 26: 注册socket的read(EPOLLIN)事件.
行 31: 如果读取事件发生, 从客户端读取新数据.
行 33: 一旦完整的http请求接收到, 取消注册读取事件, 注册写入事件(EPOLLOUT), 写入事件在能够发送数据回客户端的时候产生.
行 34: 打印完整的http请求, 展示即使通讯是交错的, 数据本身是作为一个完整的信息组合和处理的.
行 35: 如果写入事件发生在一个客户端socket上面, 我们就可以发送新数据到客户端了.
行s 36-38: 一次发送一部分返回数据, 直到所有数据都交给操作系统的发送队列.
行 39: 一旦所有的返回数据都发送完, 取消监听读取和写入事件.
行 40: 如果连接被明确关闭掉, 这一步是可选的. 这个例子采用这个方法是为了让客户端首先断开, 告诉客户端没有数据需要发送和接收了, 然后让客户端断开连接.
行 41: HUP(hang-up)事件表示客户端断开了连接(比如 closed), 所以服务器这端也会断开. 不需要注册HUP事件, 因为它们都会标示到注册在epoll的socket.
行 42: 取消注册.
行 43: 断开连接.
行s 18-45: 在这里的异常捕捉的作用是, 我们的例子总是采用键盘中断来停止程序执行.
行s 46-48: 虽然开启的socket不需要手动关闭, 程序退出的时候会自动关闭, 明确写出来这样的代码, 是更好的编码风格.
㈢ python2.7怎么实现异步
改进之前
之前,我的查询步骤很简单,就是:
前端提交查询请求 --> 建立数据库连接 --> 新建游标 --> 执行命令 --> 接受结果 --> 关闭游标、连接
这几大步骤的顺序执行。
这里面当然问题很大:
建立数据库连接实际上就是新建一个套接字。这是进程间通信的几种方法里,开销最大的了。
在“执行命令”和“接受结果”两个步骤中,线程在阻塞在数据库内部的运行过程中,数据库连接和游标都处于闲置状态。
这样一来,每一次查询都要顺序的新建数据库连接,都要阻塞在数据库返回结果的过程中。当前端提交大量查询请求时,查询效率肯定是很低的。
第一次改进
之前的模块里,问题最大的就是第一步——建立数据库连接套接字了。如果能够一次性建立连接,之后查询能够反复服用这个连接就好了。
所以,首先应该把数据库查询模块作为一个单独的守护进程去执行,而前端app作为主进程响应用户的点击操作。那么两条进程怎么传递消息呢?翻了几天Python文档,终于构思出来:用队列queue作为生产者(web前端)向消费者(数据库后端)传递任务的渠道。生产者,会与SQL命令一起,同时传递一个管道pipe的连接对象,作为任务完成后,回传结果的渠道。确保,任务的接收方与发送方保持一致。
作为第二个问题的解决方法,可以使用线程池来并发获取任务队列中的task,然后执行命令并回传结果。
第二次改进
第一次改进的效果还是很明显的,不用任何测试手段。直接点击页面链接,可以很直观地感觉到反应速度有很明显的加快。
但是对于第二个问题,使用线程池还是有些欠妥当。因为,CPython解释器存在GIL问题,所有线程实际上都在一个解释器进程里调度。线程稍微开多一点,解释器进程就会频繁的切换线程,而线程切换的开销也不小。线程多一点,甚至会出现“抖动”问题(也就是刚刚唤醒一个线程,就进入挂起状态,刚刚换到栈帧或内存的上下文,又被换回内存或者磁盘),效率大大降低。也就是说,线程池的并发量很有限。
试过了多进程、多线程,只能在单个线程里做文章了。
Python中的asyncio库
Python里有大量的协程库可以实现单线程内的并发操作,比如Twisted、Gevent等等。Python官方在3.5版本里提供了asyncio库同样可以实现协程并发。asyncio库大大降低了Python中协程的实现难度,就像定义普通函数那样就可以了,只是要在def前面多加一个async关键词。async def函数中,需要阻塞在其他async def函数的位置前面可以加上await关键词。
import asyncio
async def wait():
await asyncio.sleep(2)
async def execute(task):
process_task(task)
await wait()
continue_job()
async def函数的执行稍微麻烦点。需要首先获取一个loop对象,然后由这个对象代为执行async def函数。
loop = asyncio.get_event_loop()
loop.run_until_complete(execute(task))
loop.close()
loop在执行execute(task)函数时,如果遇到await关键字,就会暂时挂起当前协程,转而去执行其他阻塞在await关键词的协程,从而实现协程并发。
不过需要注意的是,run_until_complete()函数本身是一个阻塞函数。也就是说,当前线程会等候一个run_until_complete()函数执行完毕之后,才会继续执行下一部函数。所以下面这段代码并不能并发执行。
for task in task_list:
loop.run_until_complete(task)
对与这个问题,asyncio库也有相应的解决方案:gather函数。
loop = asyncio.get_event_loop()
tasks = [asyncio.ensure_future(execute(task))
for task in task_list]
loop.run_until_complete(asyncio.gather(*tasks))
loop.close()
当然了,async def函数的执行并不只有这两种解决方案,还有call_soon与run_forever的配合执行等等,更多内容还请参考官方文档。
Python下的I/O多路复用
协程,实际上,也存在上下文切换,只不过开销很轻微。而I/O多路复用则完全不存在这个问题。
目前,Linux上比较火的I/O多路复用API要算epoll了。Tornado,就是通过调用C语言封装的epoll库,成功解决了C10K问题(当然还有Pypy的功劳)。
在Linux里查文档,可以看到epoll只有三类函数,调用起来比较方便易懂。
创建epoll对象,并返回其对应的文件描述符(file descriptor)。
int epoll_create(int size);
int epoll_create1(int flags);
控制监听事件。第一个参数epfd就对应于前面命令创建的epoll对象的文件描述符;第二个参数表示该命令要执行的动作:监听事件的新增、修改或者删除;第三个参数,是要监听的文件对应的描述符;第四个,代表要监听的事件。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
等候。这是一个阻塞函数,调用者会等候内核通知所注册的事件被触发。
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
int epoll_pwait(int epfd, struct epoll_event *events,
int maxevents, int timeout,
const sigset_t *sigmask);
在Python的select库里:
select.epoll()对应于第一类创建函数;
epoll.register(),epoll.unregister(),epoll.modify()均是对控制函数epoll_ctl的封装;
epoll.poll()则是对等候函数epoll_wait的封装。
Python里epoll相关API的最大问题应该是在epoll.poll()。相比于其所封装的epoll_wait,用户无法手动指定要等候的事件,也就是后者的第二个参数struct epoll_event *events。没法实现精确控制。因此只能使用替代方案:select.select()函数。
根据Python官方文档,select.select(rlist, wlist, xlist[, timeout])是对Unix系统中select函数的直接调用,与C语言API的传参很接近。前三个参数都是列表,其中的元素都是要注册到内核的文件描述符。如果想用自定义类,就要确保实现了fileno()方法。
其分别对应于:
rlist: 等候直到可读
wlist: 等候直到可写
xlist: 等候直到异常。这个异常的定义,要查看系统文档。
select.select(),类似于epoll.poll(),先注册文件和事件,然后保持等候内核通知,是阻塞函数。
实际应用
Psycopg2库支持对异步和协程,但和一般情况下的用法略有区别。普通数据库连接支持不同线程中的不同游标并发查询;而异步连接则不支持不同游标的同时查询。所以异步连接的不同游标之间必须使用I/O复用方法来协调调度。
所以,我的大致实现思路是这样的:首先并发执行大量协程,从任务队列中提取任务,再向连接池请求连接,创建游标,然后执行命令,并返回结果。在获取游标和接受查询结果之前,均要阻塞等候内核通知连接可用。
其中,连接池返回连接时,会根据引用连接的协程数量,返回负载最轻的连接。这也是自己定义AsyncConnectionPool类的目的。
我的代码位于:bottle-blog/dbservice.py
存在问题
当然了,这个流程目前还一些问题。
首先就是每次轮询拿到任务之后,都会走这么一个流程。
获取连接 --> 新建游标 --> 执行任务 --> 关闭游标 --> 取消连接引用
本来,最好的情况应该是:在轮询之前,就建好游标;在轮询时,直接等候内核通知,执行相应任务。这样可以减少轮询时的任务量。但是如果协程提前对应好连接,那就不能保证在获取任务时,保持各连接负载均衡了。
所以这一块,还有工作要做。
还有就是epoll没能用上,有些遗憾。
以后打算写点C语言的内容,或者用Python/C API,或者用Ctypes包装共享库,来实现epoll的调用。
最后,请允许我吐槽一下Python的epoll相关文档:简直太弱了!!!必须看源码才能弄清楚功能。
㈣ python http怎么加入epoll
介绍
从2.6版本开始, python 提供了使用linux epoll 的功能. 这篇文章通过3个例子来大致介绍如何使用它. 欢迎提问和反馈.
阻塞式socket通讯
第一个例子是一个简单的python3.0版本的服务器代码, 监听8080端口的http请求, 打印结果到命令行, 回应http response给客户端.
行 9: 建立服务器的socket
行 10: 允许11行的bind()操作, 即使其他程序也在监听同样的端口. 不然的话, 这个程序只能在其他程序停止使用这个端口之后的1到2分钟后才能执行.
行 11: 绑定socket到这台机器上所有IPv4地址上的8080端口.
行 12: 告诉服务器开始响应从客户端过来的连接请求.
行 14: 程序会一直停在这里, 直到建立了一个连接. 这个时候, 服务器socket会建立一个新的socket, 用来和客户端通讯. 这个新的socket是accept()的返回值, address对象标示了客户端的IP地址和端口.
行 15-17: 接收数据, 直到一个完整的http请求被接收完毕. 这是一个简单的http服务器实现.
行 18: 为了方便验证, 打印客户端过来的请求到命令行.
行 19: 发送回应.
行 20-22: 关闭连接, 以及服务器的监听socket.
Example1:
import socketEOL1 = b'\n\n'EOL2 = b'\n\r\n'response = b'HTTP/1.0 200 OK\r\nDate: Mon, 1 Jan 1996 01:01:01 GMT\r\n'response += b'Content-Type: text/plain\r\nContent-Length: 13\r\n\r\n'response += b'Hello, world!'serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
serversocket.bind(('0.0.0.0', 8080))
serversocket.listen(1)
connectiontoclient, address = serversocket.accept()
request = b''while EOL1 not in request and EOL2 not in request:
request += connectiontoclient.recv(1024)print(request.decode())
connectiontoclient.send(response)
connectiontoclient.close()
serversocket.close()
第2个例子, 我们在15行加上了一个循环, 用来循环处理客户端请求, 直到我们中断这个过程(在命令行下面输入键盘中断, 比如Ctrl-C). 这个例子更明显地表示出来了, 服务器socket并没有用来做数据处理, 而是接受服务器过来的连接, 然后建立一个新的socket, 用来和客户端通讯.
最后的23-24行确保服务器的监听socket最后总是close掉, 即使出现了异常.
Example 2:
import socketEOL1 = b'\n\n'EOL2 = b'\n\r\n'response = b'HTTP/1.0 200 OK\r\nDate: Mon, 1 Jan 1996 01:01:01 GMT\r\n'response += b'Content-Type: text/plain\r\nContent-Length: 13\r\n\r\n'response += b'Hello, world!'serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
serversocket.bind(('0.0.0.0', 8080))
serversocket.listen(1)
try: while True:
connectiontoclient, address = serversocket.accept()
request = b''
while EOL1 not in request and EOL2 not in request:
request += connectiontoclient.recv(1024) print('-'*40 + '\n' + request.decode()[:-2])
connectiontoclient.send(response)
connectiontoclient.close()
finally:
serversocket.close()24
异步socket和linux epoll的优势
第2个例子里面的socket采用的是阻塞方式, 因为python解释器在出现事件之前都处在停止状态. 16行的accept()一直阻塞, 直到新的连接进来. 19行的recv()也是一直阻塞, 直到从客户端收到数据(或者直到没有数据可以接收). 21行的send()也一直阻塞, 直到所有需要发送给客户端的数据都交给了linux内核的发送队列.
当一个程序采用阻塞socket的时候, 它经常采用一个线程(甚至一个进程)一个socket通讯的模式. 主线程保留服务器监听socket, 接受进来的连接, 一次接受一个连接, 然后把生成的socket交给一个分离的线程去做交互. 因为一个线程只和一个客户端通讯, 在任何位置的阻塞都不会造成问题. 阻塞本身不会影响其他线程的工作.
多线程阻塞socket模式代码清晰, 但是有几个缺陷, 可能很难确保线程间资源共享工作正常, 可能在只有一个CPU的机器上效率低下.
C10K(单机1万连接问题!) 探讨了其他处理并行socket通讯的模式. 一种是采用异步socket. socket不会阻塞, 直到特定事件发生. 程序在异步socket上面进行一个特定操作, 并且立即得到一个结果, 不管执行成功或者失败. 然后让程序决定下一步怎么做. 因为异步socket是非阻塞的, 我们可以不采用多线程. 所有的事情都可以在一个线程里面完成. 虽然这种模式有它需要面对的问题, 它对于特定程序来说还是不错的选择. 也可以和多线程合起来使用: 单线程的异步socket可以当作服务器上面处理网络的一个模块, 而线程可以用来访问阻塞式的资源, 比如数据库.
Linux 2.6有一些方式来管理异步socket, python API能够用的有3种: select, poll和epoll. epoll和poll比select性能更好, 因为python程序不需要为了特定的事件去查询单独的socket, 而是依赖操作系统来告诉你什么socket产生了什么事件. epoll比poll性能更好, 因为它不需要每次python程序查询的时候, 操作系统都去检查所有的socket, 在事件产生的时候, linux跟踪他们, 然后在python程序调用的时候, 返回具体的列表. 所以epoll在大量(上千)并行连接下, 是一种更有效率, 伸缩性更强的机制.
采用epoll的异步socket编程示例
采用epoll的程序一般这样操作:
建立一个epoll对象
告诉epoll对象, 对于一些socket监控一些事件.
问epoll, 从上次查询以来什么socket产生了什么事件.
针对这些socket做特定操作.
告诉epoll, 修改监控socket和/或监控事件.
重复第3步到第5步, 直到结束.
销毁epoll对象.
采用异步socket的时候第3步重复了第2步的事情. 这里的程序更复杂, 因为一个线程需要和多个客户端交互.
行 1: select模块带有epoll功能
行 13: 因为socket默认是阻塞的, 我们需要设置成非阻塞(异步)模式.
行 15: 建立一个epoll对象.
行 16: 注册服务器socket, 监听读取事件. 服务器socket接收一个连接的时候, 产生一个读取事件.
行 19: connections表映射文件描述符(file descriptors, 整型)到对应的网络连接对象上面.
行 21: epoll对象查询一下是否有感兴趣的事件发生, 参数1说明我们最多等待1秒的时间. 如果有对应事件发生, 立刻会返回一个事件列表.
行 22: 返回的events是一个(fileno, event code)tuple列表. fileno是文件描述符, 是一个整型数.
行 23: 如果是服务器socket的事件, 那么需要针对新的连接建立一个socket.
行 25: 设置socket为非阻塞模式.
行 26: 注册socket的read(EPOLLIN)事件.
行 31: 如果读取事件发生, 从客户端读取新数据.
行 33: 一旦完整的http请求接收到, 取消注册读取事件, 注册写入事件(EPOLLOUT), 写入事件在能够发送数据回客户端的时候产生.
行 34: 打印完整的http请求, 展示即使通讯是交错的, 数据本身是作为一个完整的信息组合和处理的.
行 35: 如果写入事件发生在一个客户端socket上面, 我们就可以发送新数据到客户端了.
行s 36-38: 一次发送一部分返回数据, 直到所有数据都交给操作系统的发送队列.
行 39: 一旦所有的返回数据都发送完, 取消监听读取和写入事件.
行 40: 如果连接被明确关闭掉, 这一步是可选的. 这个例子采用这个方法是为了让客户端首先断开, 告诉客户端没有数据需要发送和接收了, 然后让客户端断开连接.
行 41: HUP(hang-up)事件表示客户端断开了连接(比如 closed), 所以服务器这端也会断开. 不需要注册HUP事件, 因为它们都会标示到注册在epoll的socket.
行 42: 取消注册.
行 43: 断开连接.
行s 18-45: 在这里的异常捕捉的作用是, 我们的例子总是采用键盘中断来停止程序执行.
行s 46-48: 虽然开启的socket不需要手动关闭, 程序退出的时候会自动关闭, 明确写出来这样的代码, 是更好的编码风格.
Example 3:
import socket, selectEOL1 = b'\n\n'EOL2 = b'\n\r\n'response = b'HTTP/1.0 200 OK\r\nDate: Mon, 1 Jan 1996 01:01:01 GMT\r\n'response += b'Content-Type: text/plain\r\nContent-Length: 13\r\n\r\n'response += b'Hello, world!'serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
serversocket.bind(('0.0.0.0', 8080))
serversocket.listen(1)
serversocket.setblocking(0)
epoll = select.epoll()
epoll.register(serversocket.fileno(), select.EPOLLIN)
try:
connections = {}; requests = {}; responses = {} while True:
events = epoll.poll(1) for fileno, event in events: if fileno == serversocket.fileno():
connection, address = serversocket.accept()
connection.setblocking(0)
epoll.register(connection.fileno(), select.EPOLLIN)
connections[connection.fileno()] = connection
requests[connection.fileno()] = b''
responses[connection.fileno()] = response
elif event & select.EPOLLIN:
requests[fileno] += connections[fileno].recv(1024) if EOL1 in requests[fileno] or EOL2 in requests[fileno]:
epoll.modify(fileno, select.EPOLLOUT) print('-'*40 + '\n' + requests[fileno].decode()[:-2])
elif event & select.EPOLLOUT:
byteswritten = connections[fileno].send(responses[fileno])
responses[fileno] = responses[fileno][byteswritten:] if len(responses[fileno]) == 0:
epoll.modify(fileno, 0)
connections[fileno].shutdown(socket.SHUT_RDWR)
elif event & select.EPOLLHUP:
epoll.unregister(fileno)
connections[fileno].close()
del connections[fileno]
finally:
epoll.unregister(serversocket.fileno())
epoll.close()
serversocket.close()
epoll有2种模式, 边沿触发(edge-triggered)和状态触发(level-triggered). 边沿触发模式下, epoll.poll()在读取/写入事件发生的时候只返回一次, 程序必须在后续调用epoll.poll()之前处理完对应事件的所有的数据. 当从一个事件中获取的数据被用完了, 更多在socket上的处理会产生异常. 相反, 在状态触发模式下面, 重复调用epoll.poll()只会返回重复的事件, 直到所有对应的数据都处理完成. 一般情况下不产生异常.
比如, 一个服务器socket注册了读取事件, 边沿触发程序需要调用accept建立新的socket连接直到一个socket.error错误产生, 然后状态触发下只需要处理一个单独的accept(), 然后继续epoll查询新的事件来判断是否有新的accept需要操作.
例子3采用默认的状态触发模式, 例子4展示如何用边沿触发模式. 例子4中的25, 36和45行引入了循环, 直到错误产生(或者所有的数据都处理完了), 32, 38 和48行捕捉socket异常. 最后16, 28, 41 和51行添加EPOLLET mask用来设置边沿触发.
Example 4:
import socket, selectEOL1 = b'\n\n'EOL2 = b'\n\r\n'response = b'HTTP/1.0 200 OK\r\nDate: Mon, 1 Jan 1996 01:01:01 GMT\r\n'response += b'Content-Type: text/plain\r\nContent-Length: 13\r\n\r\n'response += b'Hello, world!'serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
serversocket.bind(('0.0.0.0', 8080))
serversocket.listen(1)
serversocket.setblocking(0)
epoll = select.epoll()
epoll.register(serversocket.fileno(), select.EPOLLIN | select.EPOLLET)
try:
connections = {}; requests = {}; responses = {} while True:
events = epoll.poll(1) for fileno, event in events: if fileno == serversocket.fileno():
try: while True:
connection, address = serversocket.accept()
connection.setblocking(0)
epoll.register(connection.fileno(), select.EPOLLIN | select.EPOLLET)
connections[connection.fileno()] = connection
requests[connection.fileno()] = b''
responses[connection.fileno()] = response
except socket.error:
pass
elif event & select.EPOLLIN:
try: while True:
requests[fileno] += connections[fileno].recv(1024)
except socket.error:
pass if EOL1 in requests[fileno] or EOL2 in requests[fileno]:
epoll.modify(fileno, select.EPOLLOUT | select.EPOLLET) print('-'*40 + '\n' + requests[fileno].decode()[:-2])
elif event & select.EPOLLOUT:
try: while len(responses[fileno]) > 0:
byteswritten = connections[fileno].send(responses[fileno])
responses[fileno] = responses[fileno][byteswritten:]
except socket.error:
pass if len(responses[fileno]) == 0:
epoll.modify(fileno, select.EPOLLET)
connections[fileno].shutdown(socket.SHUT_RDWR)
elif event & select.EPOLLHUP:
epoll.unregister(fileno)
connections[fileno].close()
del connections[fileno]
finally:
epoll.unregister(serversocket.fileno())
epoll.close()
serversocket.close()
因为比较类似, 状态触发经常用在转换采用select/poll模式的程序上面, 边沿触发用在程序员不需要或者不希望操作系统来管理事件状态的场合上面.
除了这两种模式以外, socket经常注册为EPOLLONESHOT event mask, 当用到这个选项的时候, 事件只有效一次, 之后会自动从监控的注册列表中移除.
㈤ Python三大web框架分别是什么 哪个更好
【导读】目前,Python比较火的三大web框架有Django、Flask和Tornado,要论这三个Web框架哪个更好的话,建议一点,Django帮我们事先搭建了好多,上手会快一些,学习的话可以先从Django学起,然后再学习Flask和Tornado,下面我们就来具体了解一下Python三大web框架的详情。
1、Django
Django是一个开放源代码的Web应用框架,由Python写成。采用了MTV的框架模式,即模型M,模板T和视图V。它最初是被开发来用于管理劳伦斯出版集团旗下的一些以新闻内容为主的网站的,即是CMS(内容管理系统)软件。
2、Flask
Flask是一个使用Python编写的轻量级Web应用框架。其 WSGI工具箱采用Werkzeug ,模板引擎则使用 Jinja2
。Flask使用BSD授权。
Flask也被称为 “microframework” ,因为它使用简单的核心,用 extension
增加其他功能。Flask没有默认使用的数据库、窗体验证工具。
Flask 很轻,花很少的成本就能够开发一个简单的网站。非常适合初学者学习。Flask 框架学会以后,可以考虑学习插件的使用。例如使用 WTForm +
Flask-WTForm 来验证表单数据,用 SQLAlchemy + Flask-SQLAlchemy 来对你的数据库进行控制。
3、Tornado
Tornado是一种 Web 服务器软件的开源版本。Tornado 和现在的主流 Web 服务器框架(包括大多数 Python
的框架)有着明显的区别:它是非阻塞式服务器,而且速度相当快。
得利于其 非阻塞的方式和对epoll的运用,Tornado 每秒可以处理数以千计的连接,因此 Tornado 是实时 Web 服务的一个
理想框架。
关于Python三大web框架的简单介绍,就给大家分享到这里了,当然学习是永无止境的,学习一项技能更是受益终身,所以,只要肯努力学,什么时候开始都不晚,希望大家抓紧时间进行学习吧。
㈥ 干货分享!Python三大web框架简单介绍
1、Django
Django是一个开放源代码的Web应用框架,由Python写成。采用了MTV的框架模式,即模型M,模板T和视图V。它最初是被开发来用于管理劳伦斯出版集团旗下的一些以新闻内容为主的网站的,即是CMS(内容管理系统)软件。
2、Flask
Flask是一个使用 Python 编写的轻量级 Web 应用框架。其 WSGI 工具箱采用 Werkzeug ,模板引擎则使用 Jinja2 。Flask使用 BSD 授权。
Flask也被称为 “microframework” ,因为它使用简单的核心,用 extension 增加其他功能。Flask没有默认使用的数据库、窗体验证工具。
Flask 很轻,花很少的成本就能够开发一个简单的网站。非常适合初学者学习。Flask 框架学会以后,可以考虑学习插件的使用。例如使用 WTForm + Flask-WTForm 来验证表单数据,用 SQLAlchemy + Flask-SQLAlchemy 来对你的数据库进行控制。
3、Tornado
Tornado是一种 Web 服务器软件的开源版本。Tornado 和现在的主流 Web 服务器框架(包括大多数 Python 的框架)有着明显的区别:它是非阻塞式服务器,而且速度相当快。
得利于其 非阻塞的方式和对epoll的运用,Tornado 每秒可以处理数以千计的连接,因此 Tornado 是实时 Web 服务的一个 理想框架。
关于干货分享!Python三大web框架简单介绍,环球青藤小编就和大家分享到这里了,学习是永无止境的,学习一项技能更是受益终身,所以,只要肯努力学,什么时候开始都不晚。如果您还想继续了解关于python编程的学习方法及素材等内容,可以点击本站其他文章学习。