I/O多路复用 # 同步:提交一个任务之后要等待这个任务执行完毕 # # 异步:只管提交任务,不等待这个任务执行完毕就可以去做其他的事情 # # 阻塞:recv、recvfrom、accept,线程阶段 运行状态-->阻塞状态-->就绪 # # 非阻塞:没有阻塞状态 # 对于服务端在recv和send,accetpt的时候都会处于阻塞的状态,这导致了线程服务执行或者是 # 计算任何的网络请求 # low版解决方案: # 在服务端使用多线程或者是多进程的方式,让每个连接都有自己的线程或者是进程 # 存在问题: 如果线路过多的时候,会严重占用系统资源,减低系统对外界的响应效率,且他们本生也存在假死的状态 # # 改进方案》 # “线程池”旨在减少创建和销毁线程的频率,其维持一定合理数量的线程,并让空闲的线程重新承担新的执行任务。“连接池”维持连接的缓存池 # ,尽量重用已有的连接、减少创建和关闭连接的频率。 # 这两种技术都可以很好的降低系统开销,都被广泛应用很多大型系统,如websphere、tomcat和各种数据库等 # 改进方案后存在的问题》》 # 面临的可能同时出现的上千甚至上万次的客户端请求,“线程池”或“连接池”或许可以缓解部分压力,但是不能解决所有问题。 # 总之,多线程模型可以方便高效的解决小规模的服务请求, # 但面对大规模的服务请求,多线程模型也会遇到瓶颈,可以用非阻塞接口来尝试解决这个问题(不推荐使用)
1.2非阻塞(不推荐)
import socket import time server = socket.socket() ip = ('127.0.0.1',8080) server.bind(ip) server.listen() server.setblocking(False) connlist = [] while 1: while 1: try: con,add = server.accept()#设置为非阻塞的时候,如果没有人连接的直接报错, connlist.append(con) break except Exception: print('没有人连接我') time.sleep(0.1) #减少CPU的消耗 for con in connlist: while 1: try: msg = con.recv(1024).decode('utf-8') print(msg) hh = 'qingshur '.encode('utf-8') con.send(hh) break except Exception: print('么有消息')
import socket client = socket.socket() client.connect(('127.0.0.1',8080)) while 1: to_ser = input('我想说>>>>>').encode('utf-8') client.send(to_ser) msg = client.recv(1024).decode('utf-8') print(msg)
使用的场景
1. 如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。 2. 在多路复用模型中,对于每一个socket,一般都设置成为non-blocking,但是,如上图所示, 整个用户的process其实是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。
I/O 多路复用
1.3select的基本用法
# import select # # fd_r_list, fd_w_list, fd_e_list = select.select(rlist, wlist, xlist, [timeout]) # # 参数: 可接受四个参数(前三个必须) # rlist: wait until ready for reading #等待读的对象,你需要监听的需要获取数据的对象列表 # wlist: wait until ready for writing #等待写的对象,你需要写一些内容的时候,input等等,也就是说我会循环他看看是否有需要发送的消息,如果有我取出这个对象的消息并发送出去,一般用不到,这里我们也给一个[]。 # xlist: wait for an “exceptional condition” #等待异常的对象,一些额外的情况,一般用不到,但是必须传,那么我们就给他一个[]。 # timeout: 超时时间 # 当超时时间 = n(正整数)时,那么如果监听的句柄均无任何变化,则select会阻塞n秒,之后返回三个空列表,如果监听的句柄有变化,则直接执行。 # 返回值:三个列表与上面的三个参数列表是对应的 # select方法用来监视文件描述符(当文件描述符条件不满足时,select会阻塞),当某个文件描述符状态改变后,会返回三个列表 # 1、当参数1 序列中的fd满足“可读”条件时,则获取发生变化的fd并添加到fd_r_list中 # 2、当参数2 序列中含有fd时,则将该序列中所有的fd添加到 fd_w_list中 # 3、当参数3 序列中的fd发生错误时,则将该发生错误的fd添加到 fd_e_list中 # 4、当超时时间为空,则select会一直阻塞,直到监听的句柄发生变化 # # # select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。 # # 它的基本原理就是select/epoll这个function会不断的轮询所负责的所有socket, # # 当某个socket有数据到达了,就通知用户进程
1.4简单版的多路复用
1.4.1,服务端
mport select import socket server = socket.socket() server_ip = ('127.0.0.1',8080) server.bind(server_ip) rlist = [server,]#要监听的对象 server.listen() while 1: print('*****') r1,w1,e1 = select.select(rlist,[],[]) print(r1) for sock in r1: print(r1) if sock == server: conn,addr = sock.accept() rlist.append(conn) else: from_client_msg = sock.recv(1024) print(from_client_msg.decode('utf-8'))
mport socket client = socket.socket() client.connect(('127.0.0.1',8080)) while 1: to_ser = input('我想说>>>>>').encode('utf-8') client.send(to_ser) msg = client.recv(1024).decode('utf-8') print(msg)