在前一篇文章中,我们实现了通过非阻塞套接字的并发服务,但是这种实现方式是有很多问题的。
一、CPU资源的极大浪费;
二、如果没有连接那么,accept()和recv()都在做无用功;
三、对BlockIOError的处理也是在做无用功。
针对以上问题,现在我们使用IO多路复用的技术来实现并发的服务。
什么是IO多路复用
IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程。IO多路复用适用如下场合:
1.当客户需要处理多个描述符时,必须使用I/O复用。 2.当一个客户同时处理多个套接口时,而这种情况是可能的,但很少出现。 3.如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口。 4.如果一个服务器即要处理TCP,又要处理UDP。 5.如果一个服务器要处理多个服务或多个协议。
select,pselect,poll,epoll
,但select,pselect,poll,epoll本质上都是同步I/O
。其中epoll时Linux系统独有的。也是要主要介绍的。select
Python的select()方法直接调用操作系统的IO接口,它监控sockets,open files, and pipes(所有带fileno()方法的文件句柄)何时变成readable 和writeable, 或者通信错误,select()使得同时监控多个连接变的简单,并且这比写一个长循环来等待和监控多客户端连接要高效,因为select直接通过操作系统提供的C的网络接口进行操作,而不是通过Python的解释器。
select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点。select的一 个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,可以通过修改宏定义甚至重新编译内核的方式提升这一限制,但是这样会造成效率的降低
select(rlist, wlist, xlist, timeout=None)
select()方法接收并监控3个通信列表, 第一个rlist监控所有要进来的输入数据,第二个wlist是监控所有要发出去的输出数据,第三个监控异常错误数据,第四个设置指定等待时间,如果想立即返回,设为null即可,最后需要创建2个列表来包含输入和输出信息来传给select(),让select方法通过内核去监控,然后生成三个实例。zaipython中直接导入select使用。
epoll
epoll的方式,这种效率更高,但是这种方式在Windows下不支持,在Linux是支持的,selectors模块就是默认使用就是epoll,但是如果在windows系统上使用selectors模块,就会找不到epoll,从而使用select。
服务端代码:
import socket import selectors server=socket.socket() server.bind(('0.0.0.0',8899)) server.listen() epoll=selectors.DefaultSelector() def recv_func(conn): data=conn.recv(1024) if data==b'': epoll.unregister(conn) conn.close() else: print("接收到数据:>>>",data) conn.send(data) def server_func(ser): conn,addr=ser.accept() print("连接处理!") epoll.register(conn,selectors.EVENT_READ,recv_func) epoll.register(server,selectors.EVENT_READ,server_func) while True: events=epoll.select() for key,value in events: callback=key.data sock=key.fileobj callback(sock)
客户端代码
import socket client=socket.socket() client.connect(('127.0.0.1',8899)) while True: data=input("请输入传送的数据:>>>>") client.send(data.encode()) print("接收到的数据:>>>",client.recv(1024).decode())