day10-epoll IO多路复用实现单线程支持上万并发代码实例
一、前言
之前我们就讲了select的这种方式,使用的是轮询方式去监测客户端的连接,效率比较低下,我们今天来聊聊epoll的方式,这种效率更高,但是这种方式在Windows下不支持,在Linux是支持的,那就不得不说下面的一个模块selectors。
二、selectors模块
2.1、英文解释
This module allows high-level and efficient I/O multiplexing, built upon the select
module primitives. Users are encouraged to use this module instead, unless they want precise control over the OS-level primitives used.
2.2、中文解释
selectors 默认是epoll,如果找不到epoll的话,比如说windows操作系统,就会找select
三、selectors模块使用
3.1、selectors实现非阻塞连接
说明:默认它是阻塞的,只要不阻塞了,就代表肯定有活动的数据,我就循环这个events。
import selectors,socket sel = selectors.DefaultSelector() def accept(sock,mask): "接收客户端信息实例" conn,addr = sock.accept() print("accepted",conn,'from',addr) conn.setblocking(False) sel.register(conn,selectors.EVENT_READ,read) #新连接注册read回调函数 def read(conn,mask): "接收客户端的数据" data = conn.recv(1024) if data: print("echoing",repr(data),'to',conn) conn.send(data) else: print("closing",conn) sel.unregister(conn) conn.close() server = socket.socket() server.bind(('localhost',9999)) server.listen(500) server.setblocking(False) sel.register(server,selectors.EVENT_READ,accept) #注册事件,只要来一个连接就调accept这个函数, #sel.register(server,selectors.EVENT_READ,accept) == inputs=[server,] while True: events = sel.select() #这个select,看起来是select,有可能调用的是epoll,看你操作系统是Windows的还是Linux的 #默认阻塞,有活动连接就返回活动连接列表 print("事件:",events) for key,mask in events: callback = key.data #相当于调accept了 callback(key.fileobj,mask) #key.fileobj=文件句柄
3.2、selectors用法
①定义一个对象
sel = selectors.DefaultSelector()
②注册一个事件
说明:注册事件,只要来一个连接就调accept这个函数,就相当于之前select的用法,sel.register(server,selectors.EVENT_READ,accept) == inputs=[server,],readable,writeable,exceptional = select.select(inputs,outputs,inputs)意思是一样的。
sel.register(server,selectors.EVENT_READ,accept)
③循环事件
说明,这边这个events输出的是什么呐?
while True: events = sel.select() #这个select,看起来是select,有可能调用的是epoll,看你操作系统是Windows的还是Linux的 print("事件:",events) for key,mask in events: callback = key.data #相当于调accept了 callback(key.fileobj,mask) #key.fileobj=文件句柄
打印events的结果:
事件: [(SelectorKey(fileobj=<socket.socket fd=276, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999)>, fd=276, events=1, data=<function accept at 0x000002722BB1D510>), 1)] accepted <socket.socket fd=352, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999), raddr=('127.0.0.1', 63829)> from ('127.0.0.1', 63829) 事件: [(SelectorKey(fileobj=<socket.socket fd=352, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999), raddr=('127.0.0.1', 63829)>, fd=352, events=1, data=<function read at 0x000002722BB1D840>), 1)] echoing b'ls' to <socket.socket fd=352, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999), raddr=('127.0.0.1', 63829)>
这样很容易明白了:
callback = key.data #第一次调用的是accept,第二次调用的是read callback(key.fileobj,mask) #key.fileobj=文件句柄
四、高并发的客户端代码
4.1、客户端高并发代码
说明:这个在Windows上试的时候是select,最好在Linux上试,它默认支持的是epoll
import socket,sys messages = [ b'This is the message. ', b'It will be sent ', b'in parts.', ] server_address = ('localhost', 9999) # Create a TCP/IP socket socks = [ socket.socket(socket.AF_INET, socket.SOCK_STREAM) for i in range(100)] # Connect the socket to the port where the server is listening print('connecting to %s port %s' % server_address) for s in socks: s.connect(server_address) for message in messages: # Send messages on both sockets for s in socks: print('%s: sending "%s"' % (s.getsockname(), message) ) s.send(message) # Read responses on both sockets for s in socks: data = s.recv(1024) print( '%s: received "%s"' % (s.getsockname(), data) ) if not data: print(sys.stderr, 'closing socket', s.getsockname() )
想了解更多关于epoll的东西:请猛击这里