I/O模型
I/O模型
有五种I/O模型:blocking I/O、nonblocking I/O、IO multiplexing、signal driven、asynchronous IO
1.blocking I/O(阻塞I/O模型)

(1)遇到I/O会有两个阶段:等待数据准备阶段、将数据从内核拷贝到进程中;
(2)遇到I/O之后,要执行进程的下一条指令,必须等等待数据准备阶段和将数据从内核拷贝到进程中两个阶段执行完;
(3)特点:IO执行的两个阶段(等待数据准备阶段和将数据从内核拷贝到进程中)被block;
(4)除非特别指定,几乎所有的IO接口都是阻塞型的;
(5)多线程模型可以方便高效的解决小规模的服务请求,但面对大规模的服务请求,就不适用了,可以用非阻塞接口来尝试解决这个问题。

2.nonblocking I/O(非阻塞I/O)

- 非阻塞I/O:就是在I/O等待数据准备阶段,先给kernel发信号询问数据是否准备好,若没有准备好,程序会先执行另外的进程或线程,执行完后会循环询问kernel中是否有数据,若没有再去执行其它的进程或线程,若内存中有数据了,再执行I/O操作,这时该进程下一条程序执行需等待的时间就只是将内存的数据拷入进程中的时间。
- 在非阻塞I/O中,用户进程其实是需要不断的主动询问kernel数据准备好了没有
- 该模型不被推荐,因为CPU占用率高,任务完成的响应延迟增大了
- 优点:能够在等待任务完成的时间里去执行其它程序(包括提交其它任务,也就是‘后台’可以有多个任务在‘同时’执行)
- 在这个方案中recv()更多的是起到检测‘操作是否完成’的作用
server: from socket import * server=socket(AF_INET,SOCK_STREAM) server.bind(('127.0.0.1',8080)) server.listen(5) server.setblocking(False) conn_l=[] del_l=[] while True: try: conn,addr=server.accept() print('%s:%s'%(addr[0],addr[1])) conn_l.append(conn) except BlockingIOError: print('没有数据') print(len(conn_l)) # 基于建立好的连接收发信息 for conn in conn_l: try: data = conn.recv(1024) if not data: del_l.append(conn) continue conn.send(data.upper()) except BlockingIOError:pass except ConnectionResetError: del_l.append(conn) for conn in del_l: conn_l.remove(conn) conn.close() del_l client: from socket import * from threading import Thread def receive(): s=socket() s.connect(('127.0.0.1',8080)) while True: msg=input('>>:') if not msg:break s.send(msg.strip().encode('utf-8')) data=s.recv(1024) print(data.decode('utf-8')) if __name__ == '__main__': p=Thread(target=receive) p.start()
3.IO multiplexing(多路复用IO)

- 当用户进程调用了select,那么整个进程会被block,而同时,kernel会‘监视’所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回,这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。
- 如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用muti-threading+blocking I/O的web server性能更好,可能延迟还更大。所以select/epoll应用于处理更多的连接。
- 多路复用模型中,对于每一个socket,一般都设置成non-blocking,process是被select这个函数block,而不是被socket I/O block。
结论:select的优势在于可以处理多个连接,不适用于单个连接
server: from socket import * import select server = socket(AF_INET, SOCK_STREAM) server.bind(('127.0.0.1', 8080)) server.listen(5) server.setblocking(False) # 将阻塞改为非阻塞 data = [server, ] # 刚开始可以‘监视‘一个套接字 while True: print('检测的套接字数%s' % len(data)) r_l, _, _ = select.select(data, [], []) # 只有r_l有值,调用select方法实现检测i/o 并自动切换的功能 for obj in r_l: if obj == server: conn, addr = obj.accept() # 与客户端产生连接 data.append(conn) # conn可以触发I/O,将conn加入‘监视‘列表 print('%s:%s' % (addr[0], addr[1])) else: try: data_l = conn.recv(1024) if not data_l: obj.close() data.remove(obj) continue conn.send(data_l.upper()) except ConnectionResetError: obj.close() data.remove(obj)
from socket import * c=socket(AF_INET,SOCK_STREAM) c.connect(('127.0.0.1',8085)) while True: msg=input('>>: ').strip() if not msg:continue c.send(msg.encode('utf-8')) data=c.recv(1024) print(data.decode('utf-8'))

浙公网安备 33010602011771号