day10-epoll IO多路复用实现单线程支持上万并发代码实例
select的不足
尽管select
用起来挺爽,跨平台的特性。但是select
还是存在一些问题。select
需要遍历监视的文件描述符,并且这个描述符的数组还有最大的限制。随着文件描述符数量的增长,用户态和内核的地址空间的复制所引发的开销也会线性增长。即使监视的文件描述符长时间不活跃了,select
还是会线性扫描。
为了解决这些问题,操作系统又提供了poll
方案,但是poll
的模型和select
大致相当,只是改变了一些限制。目前Linux
最先进的方式是epoll
模型,它使用的是selectors模块。
许多高性能的软件如nginx
, nodejs
都是基于epoll
进行的异步。
selectors模块实现I/O多路复用机制
这个模块允许高层高效的I/O多路复用,建立在select模块的原函数基础之上。
selectors实例
#服务器端 import selectors #基于select模块实现的IO多路复用 import socket sel = selectors.DefaultSelector() #默认选择器使用在当前平台上可以的最有效的实现 def accept(sock, mask): """建立连接,然后交给read函数接收从客户端来的数据(接收数据注册EVENT_READ事件)""" conn, addr = sock.accept() # Should be ready print('accepted', conn, 'from', addr) conn.setblocking(False) #非阻塞模式 #根据平台选择最佳的IO多路机制,比如Linux就会选择epoll sel.register(conn, selectors.EVENT_READ, read) #新连接注册read回调函数 def read(conn, mask): """接收从客户端来的数据,然后判断需要的操作""" data = conn.recv(1000) # Should be ready if data: print('echoing', repr(data), 'to', conn) conn.send(data) # Hope it won't block else: print('closing', conn) #接收到空数据就从事件列表中注销conn的事件,同时关闭conn sel.unregister(conn) conn.close() sock = socket.socket() sock.bind(('localhost', 10000)) sock.listen(100) sock.setblocking(False) #注册事件,只要有连接来就调用accept函数 sel.register(sock, selectors.EVENT_READ, accept) while True: # [(sock),(),()]监听EVENT事件列表 events = sel.select() #默认阻塞。有活动的事件就交给相应的方法处理 print("事件:",events) for key, mask in events: #print(key.data) #accept 找出有活动的绑定函数 #print(key.fileobj) #sock 找出有活动的文件描述符 callback = key.data #1 accept(sock,mask) 2 read(conn,mask) callback(key.fileobj, mask) #key.fileobj = 注册的文件句柄(对象) #运行输出 事件: [(SelectorKey(fileobj=<socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9998)>, fd=4, events=1, data=<function accept at 0x10403aa60>), 1)] accepted <socket.socket fd=7, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9998), raddr=('127.0.0.1', 55795)> from ('127.0.0.1', 55795) 事件: [(SelectorKey(fileobj=<socket.socket fd=7, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9998), raddr=('127.0.0.1', 55795)>, fd=7, events=1, data=<function read at 0x104506488>), 1)] echoing b'1' to <socket.socket fd=7, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9998), raddr=('127.0.0.1', 55795)>
程序详解
生成一个对象
sel = selectors.DefaultSelector()
注册事件
sel.register(sock, selectors.EVENT_READ, accept)
循环事件
while True: # [(sock),(),()]监听 events = sel.select() #默认阻塞。有活动连接就返回活动的连接列表 print("事件:",events) for key, mask in events: #print(key.data) #accept 找出有活动的绑定函数 #print(key.fileobj) #sock 找出有活动的文件描述符 callback = key.data #第1次调用accept(sock,mask), 第2次调用read(conn,mask) callback(key.fileobj, mask) #key.fileobj = 注册的文件句柄(对象)
多并发客户端
#客户端 import socket import sys messages = [ b'This is the message. ', b'It will be sent ', b'in parts.', ] server_address = ('localhost', 10000) # 创建3个tcp/ip的socket连接 socks = [ socket.socket(socket.AF_INET, socket.SOCK_STREAM) for i in range(3)] # 将socket连接到服务器正在监听的端口 print('connecting to %s port %s' % server_address) for s in socks: s.connect(server_address) for message in messages: #在2个socket上发送信息 for s in socks: print('%s: sending "%s"' % (s.getsockname(), message) ) s.send(message) #读取2个socket上的response for s in socks: data = s.recv(1024) print( '%s: received "%s"' % (s.getsockname(), data) ) if not data: print('closing socket', s.getsockname() ) #运行输出 connecting to localhost port 10000 ('127.0.0.1', 56849): sending "b'This is the message. '" ('127.0.0.1', 56850): sending "b'This is the message. '" ('127.0.0.1', 56851): sending "b'This is the message. '" ('127.0.0.1', 56849): received "b'This is the message. '" ('127.0.0.1', 56850): received "b'This is the message. '" ('127.0.0.1', 56851): received "b'This is the message. '" ('127.0.0.1', 56849): sending "b'It will be sent '" ('127.0.0.1', 56850): sending "b'It will be sent '" ('127.0.0.1', 56851): sending "b'It will be sent '" ('127.0.0.1', 56849): received "b'It will be sent '" ('127.0.0.1', 56850): received "b'It will be sent '" ('127.0.0.1', 56851): received "b'It will be sent '" ('127.0.0.1', 56849): sending "b'in parts.'" ('127.0.0.1', 56850): sending "b'in parts.'" ('127.0.0.1', 56851): sending "b'in parts.'" ('127.0.0.1', 56849): received "b'in parts.'" ('127.0.0.1', 56850): received "b'in parts.'" ('127.0.0.1', 56851): received "b'in parts.'"