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.'"

浙公网安备 33010602011771号