一、 简述:
1什么是多路 I/O 复用机制
- Python 中的多路 I/O 复用机制是一种高效的编程技巧,用于同时监视多个文件描述符是否有可读、可写或错误事件,并在这些描述符中任意一个或多个有事件发生时立即进行响应。
2 使用多路I/O 复用机制的目的
- 同时处理多个连接时提高网络I/O效率,从而提高程序的整体性能。
3 适用场景
- 各种 I/O 密集型应用程序的编程。
二、常用机制
Python,常用多路I/O复用机制可供选择,包括select
、poll
、epoll
和asyncio
等。这些机制都提供了一种方式来同时监视多个I/O事件,并且可以有效地处理并发的I/O操作。
-
select
:select
是最基本和最常见的多路I/O复用机制之一,适用于大多数Unix平台。它通过调用select.select()
函数来等待一组文件描述符上的事件,并返回已就绪的文件描述符列表。select
的一个缺点是它对于大量的文件描述符会有性能问题。 -
poll
:poll
是类似于select
的多路I/O复用机制,但在处理大量文件描述符时性能更好。它通过调用select.poll()
创建一个poll对象,并使用register()
方法注册文件描述符及其感兴趣的事件。然后,使用poll()
方法等待事件的发生,并返回就绪的文件描述符列表。 -
epoll
:epoll
是在Linux上使用的高性能多路I/O复用机制。它使用内核事件通知机制来实现高效的I/O事件监视,适用于大规模的并发连接。epoll
提供了select.epoll()
函数来创建一个epoll对象,并使用register()
方法注册文件描述符及其感兴趣的事件。然后,使用poll()
方法等待事件的发生,并返回就绪的文件描述符列表。 -
asyncio
:asyncio
是Python标准库中提供的异步I/O框架。它基于协程和事件循环的概念,提供了高层次的抽象来处理异步I/O操作。使用asyncio
,可以通过asyncio.select()
函数等待多个异步I/O操作,并在事件就绪时执行相应的回调函数。asyncio
对于构建高性能的异步网络应用非常有用。
二、示列:
#1 select示例: import select import socket # 创建套接字 server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.bind(('localhost', 8080)) server_socket.listen(5) inputs = [server_socket] while True: """ 参数说明: rlist(Readable List):包含需要监视可读事件的文件描述符的列表。当列表中的文件描述符准备好从其读取数据时,select 函数将返回这些文件描述符。 wlist(Writable List):包含需要监视可写事件的文件描述符的列表。当列表中的文件描述符准备好向其写入数据时,select 函数将返回这些文件描述符。 xlist(Exceptional List):包含需要监视异常事件的文件描述符的列表。当列表中的文件描述符发生异常情况时,如连接错误或带外数据,select 函数将返回这些文件描述符。 """
readable, writable, exceptional = select.select(inputs, [], []) for sock in readable: if sock is server_socket: # 新的连接 client_socket, client_address = server_socket.accept() inputs.append(client_socket) else: # 可读数据 data = sock.recv(1024) if data: # 处理数据 print(data.decode()) else: # 客户端关闭连接 inputs.remove(sock) sock.close()
# 2poll示例: import select import socket # 创建套接字 server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.bind(('localhost', 8080)) server_socket.listen(5) poller = select.poll() poller.register(server_socket, select.POLLIN) while True: events = poller.poll() for fd, event in events: if fd == server_socket.fileno(): # 新的连接 client_socket, client_address = server_socket.accept() # 参数为可迭代的对象 poller.register(client_socket, select.POLLIN) else: # 可读数据 sock = socket.fromfd(fd, socket.AF_INET, socket.SOCK_STREAM) data = sock.recv(1024) if data: # 处理数据 print(data.decode()) else: # 客户端关闭连接 poller.unregister(fd) sock.close()
#3 epoll示例: import select import socket # 创建套接字 server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.bind(('localhost', 8080)) server_socket.listen(5) epoller = select.epoll() epoller.register(server_socket.fileno(), select.EPOLLIN) while True: events = epoller.poll() for fd, event in events: if fd == server_socket.fileno(): # 新的连接 client_socket, client_address = server_socket.accept() client_socket.setblocking(0) ''' 常用的events标志有: EPOLLIN:表示可读事件 EPOLLOUT:表示可写事件 EPOLLRDHUP:表示对端连接关闭 EPOLLERR:表示错误事件 EPOLLHUP:表示挂起事件 ''' epoller.register(client_socket.fileno(), select.EPOLLIN) else: # 可读数据 sock = socket.fromfd(fd, socket.AF_INET, socket.SOCK_STREAM) data = sock.recv(1024) if data: # 处理数据 print(data.decode()) else: # 客户端关闭连接 epoller.unregister(fd) sock.close()
#4 asyncio示例: import asyncio async def handle_client(reader, writer): data = await reader.read(1024) message = data.decode() addr = writer.get_extra_info('peername') print(f"Received {message} from {addr}") # 响应客户端 response = "Hello, client!" writer.write(response.encode()) await writer.drain() writer.close() async def main(): server = await asyncio.start_server( handle_client, 'localhost', 8080) addr = server.sockets[0].getsockname() print(f'Serving on {addr}') async with server: await server.serve_forever() asyncio.run(main())
三、区别
主要对select、poll、epoll 进行比较
- 适用系统不同:
- select、poll:在多个操作系统上均可使用;
- epoll:仅能在 Linux 系统上使用。
- 监听数量不同:
- select:最多支持 1024 个文件描述符的监听;
- poll:支持数目比 select 多,但仍有限制;
- epoll:支持的数量没有明确的上限。
- 内部实现不同:
- select、poll 通过遍历监听的所有文件描述符来检测是否有 I/O 事件发生,效率较低;
- epoll 利用 epoll_ctl 系统调用来维护和更新事件列表,通过 epoll_wait 来通知进程哪些文件有读写操作的事件发生,效率更高。
- 事件注册方式不同:select、poll 的FD集合是一个静态的数据结构,因此每次注册事件都需要构建一个新的FD集合,效率较低;
- epoll 采用基于事件的就绪通知方式,采用 epoll_ctl() 函数向内核注册感兴趣的事件类别。
5.asyncio与其他三种的区别:
- 是Python标准库提供的异步I/O框架。
- 基于协程和事件循环的概念,提供了高层次的抽象来处理异步I/O操作。
- 使用
asyncio.select()
函数等待多个异步I/O操作,并在事件就绪时执行相应的回调函数。 - 强调基于事件驱动的编程模型,可以方便地编写异步代码。