python使用select和epoll实现IO多路复用实现并发服务器
在select模块中, 有三种方法实现IO多路复用并发服务器
- select
- poll
- epoll
select的原理: 在多路复用的模型中,比较常用的有select模型和epoll模型。这两个都是系统接口,由操作系统提供。当然,Python的select模块进行了更高级的封装。
网络通信被Unix系统抽象为文件的读写,通常是一个设备,由设备驱动程序提供,驱动可以知道自身的数据是否可用。支持阻塞操作的设备驱动通常会实现一组自身的等待队列,如读/写等待队列用于支持上层(用户层)所需的block或non-block操作。设备的文件的资源如果可用(可读或者可写)则会通知进程,反之则会让进程睡眠,等到数据到来可用的时候,再唤醒进程。
这些设备的文件描述符被放在一个数组中,然后select调用的时候遍历这个数组,如果对于的文件描述符可读则会返回改文件描述符。当遍历结束之后,如果仍然没有一个可用设备文件描述符,select让用户进程则会睡眠,直到等待资源可用的时候在唤醒,遍历之前那个监视的数组。每次遍历都是依次进行判断的。
例如使用select实现echo(回显)服务器
import select import socket import sys server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(('', 7788)) server.listen(5) inputs = [server, sys.stdin] running = True while True: # 调用 select 函数,阻塞等待 readable, writeable, exceptional = select.select(inputs, [], []) # 数据抵达,循环 for sock in readable: # 监听到有新的连接 if sock == server: conn, addr = server.accept() # select 监听的socket inputs.append(conn) # 监听到键盘有输入 elif sock == sys.stdin: cmd = sys.stdin.readline() running = False break # 有数据到达 else: # 读取客户端连接发送的数据 data = sock.recv(1024) if data: sock.send(data) else: # 移除select监听的socket inputs.remove(sock) sock.close() # 如果检测到用户输入敲击键盘,那么就退出 if not running: break server.close()
但是在底层原理中, select和epoll 都是使用轮询原理来实现的. epoll是触发通知机制
epoll的优点:
- 没有最大并发连接的限制,能打开的FD(指的是文件描述符,通俗的理解就是套接字对应的数字编号)的上限远大于1024
- 效率提升,不是轮询的方式,不会随着FD数目的增加效率下降。只有活跃可用的FD才会调用callback函数;即epoll最大的优点就在于它只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,epoll的效率就会远远高于select和poll。
import socket import select # 创建套接字 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 可重复绑定 s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 绑定本机地址端口 s.bind(("", 7788)) # 变为被动服务器 s.listen(1024) # 创建一个epoll对象 epoll = select.epoll() # 在epoll中注册s套接字(注意此处不是直接用是s, 而是使用s的fileno) epoll.register(s.fileno(), select.EPOLLIN|select.EPOLLET) # 创建两个字典, 来保存fileno和与其对应的套接字和地址 connections = {} addresses = {} # 开始等待客户端发送来的信息 while True: # 对epoll中的套接字进行扫描 epollList = epoll.poll() # 对扫描到的事件进行判断 for fd,events in epollList: # 如果判断是s套接字 if fd == s.fileno(): conn,addr = s.accept() print("有新的客户端到来...%s"%str(addr)) connections[conn.fileno()] = conn addresses[conn.fileno()] = addr epoll.register(conn.fileno(), selecte.EPOLLIN|select.EPOLLET) # 如果是接收到了数据 elif events == select.EPOLLIN: recvData = connections[fd].recv(1024) if len(recvData) > 0: print("recvData: %s"%recvData) else: epoll.unregister(fd) connections[fd].close() print("%s....offline....."%str(addresses[fd]))
引:https://www.jianshu.com/p/cdfddb026db0