0x1 啥是epoll?
epoll - I/O event notification facility—事件驱动的io。
在传统的网络编程中,listen,send,recv函数都是阻塞的。在一个过程中你只能乖乖的等着数据的到来才能进行下一步操作,这就非常影响效率了。为了解决阻塞的问题,先后有了select和poll方式来轮询io事件,这两种方式虽然已经提高了效率,但是他们是无差别轮询,还是浪费了一点时间。所以有了epoll,它只将发生变化的io事件通知我们,大大提高了性能,广泛应用于高并发的请求的程序中。
0x2 在python中咋用
python中的select模块提供epoll的操作接口,那么先介绍一下使用epoll的基本步骤:
1.创建一个epoll对象
2.将需要监听的socket注册到epoll对象上
3.询问epoll对象,在某个时间段内是否发生了指定的io事件
4.得到发生了io事件的socket,对这些socket进行读写或者其他操作
5.告诉epoll对象,是否需要修改socket对象的状态或事件,继续进行监控
6.重复3-5,直到完成
7.销毁epoll对象
epoll对象的一些方法:
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
| import select 导入select模块
epoll = select.epoll() 创建一个epoll对象
epoll.register(文件句柄,事件类型) 注册要监控的文件句柄和事件
事件类型:
select.EPOLLIN 可读事件
select.EPOLLOUT 可写事件
select.EPOLLERR 错误事件
select.EPOLLHUP 客户端断开事件
epoll.unregister(文件句柄) 销毁文件句柄
epoll.poll(timeout) 当文件句柄发生变化,则会以列表的形式主动报告给用户进程,timeout
为超时时间,默认为-1,即一直等待直到文件句柄发生变化,如果指定为1
那么epoll每1秒汇报一次当前文件句柄的变化情况,如果无变化则返回空
epoll.fileno() 返回epoll的控制文件描述符(Return the epoll control file descriptor)
epoll.modfiy(fineno,event) fineno为文件描述符 event为事件类型 作用是修改文件描述符所对应的事件
epoll.fromfd(fileno) 从1个指定的文件描述符创建1个epoll对象
epoll.close() 关闭epoll对象的控制文件描述符
|
epoll的一个基于状态触发(level-triggered)的例子:
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
|
import socket import select
EOL1 = b'\n\n' EOL2 = b'\n\r\n' response = b'HTTP/1.0 200 OK\r\nDate: Mon, 1 Jan 2017 10:10:10 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(5) 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()
|
运行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| //服务端 xing@xing:~/pypractice$ python epoll_demo1.py ---------------------------------------- GET / HTTP/1.1 Host: 10.70.27.36:8080 Connection: keep-alive Cache-Control: max-age=0 User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.62 Safari/537.36 Upgrade-Insecure-Requests: 1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9
//客户端: PS C:\Users\STAR_CHEN> http 10.70.27.36:8080 HTTP/1.0 200 OK Content-Length: 13 Content-Type: text/plain Date: Mon, 1 Jan 2017 10:10:10 GMT
Hello, World!
|
epoll基于边沿触发(edge-triggered)的一个例子:
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
|
import socket import select
EOL1 = b'\n\n' EOL2 = b'\n\r\n' response = b'HTTP/1.0 200 OK\r\nDate: Mon, 1 Jan 2017 10:10:10 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(5) serversocket.setblocking(0)
epoll = select.epoll()
epoll.register(serversocket.fileno(), select.EPOLLIN | select.EPOLLOUT)
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(response[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, 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()
|
0x3 总结
采用边沿触发的特点:
当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你!!!这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符!!!
使用边沿触发需要注意,将连接设置为非阻塞,并且一次性将数据读取完。
水平触发的特点:
当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据一次性全部读写完(如读写缓冲区太小),那么下次调用 epoll_wait()时,它还会通知你在上没读写完的文件描述符上继续读写,当然如果你一直不去读写,它会一直通知你!!!如果系统中有大量你不需要读写的就绪文件描述符,而它们每次都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率!!!
参考链接:
http://www.cnblogs.com/yuuyuu/p/5103744.html
http://blog.csdn.net/hehe123456zxc/article/details/52526670
http://blog.csdn.net/voidccc/article/details/8619632
http://blog.csdn.net/q576709166/article/details/8649911 EPOLL中有关事件意义