阻塞io(blocking IO)
当我们用套接字通讯时套接字对象调用revform方法 系统内核就开始了network io 的第一个阶段 wair for data 这是系统内核就等待足够的数据到来
而用户整个进程就会被阻塞 当系统内核等到了数据 它就会将数据从缓存中拷贝到 用户进程的内存中 然后系统返回结果给recv 用户进程接受到数据
解除阻塞状态 进入就绪态 重新运行起来
所以在 wait for data 和copy data 用户进程都属于阻塞的状态
简单的解决办法就是让服务器端使用多线程或者多进程,目的是为了让每一个接口都有用一个独立的线程或进程 这样任何一个连接有网络io都
不会影响其他线程或进程的运行 它阻塞它的 我运行我的
但是 开启多线程或多进程 在遇到同时要想相应巨大连接请求的时候 都会严重占据系统资源 降低服务器对外界的响应速度 所以开启多进程或
多线程并不能完全解决网络阻塞io这个问题
当然 我们会想到你无线开线程和进程系统会扛不住 我们可以限制线程和进程的同步开启 可以用线程池或进程池 来维持一定合理数量的线程或
进程 并让空闲的线程重新承担新的执行任务 规定只能同时开线程池里面规定数量的线程 减少创建和关闭连接的频率 降低系统的开销
但是这样也并非完美 线程池和进程池 能一定程度上缓解 系统的开销 但是如果用户数量大大超出 线程池始终有自己的上线 “池”构成的系统对外界的响应并不比没有池的时候效果好多少。所以使用“池”必须考虑其面临的响应规模,并根据响应规模调整“池”的大小。
非阻塞io(nonblocking IO)
从图中可以看出,当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是用户就可以在本次到下次再发起read询问的时间间隔内做其他事情,或者直接再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存(这一阶段仍然是阻塞的),然后返回。
也就是说非阻塞的recvform系统调用调用之后,进程并没有被阻塞,内核马上返回给进程,如果数据还没准备好,此时会返回一个error。进程在返回之后,可以干点别的事情,然后再发起recvform系统调用。重复上面的过程,循环往复的进行recvform系统调用。这个过程通常被称之为轮询。轮询检查内核数据,直到数据准备好,再拷贝数据到进程,进行数据处理。需要注意,拷贝数据整个过程,进程仍然是属于阻塞的状
但是非阻塞IO模型绝不被推荐。
我们不能否则其优点:能够在等待任务完成的时间里干其他活了(包括提交其他任务,也就是 “后台” 可以有多个任务在“”同时“”执行)。
但是也难掩其缺点:
#1. 循环调用recv()将大幅度推高CPU占用率;这也是我们在代码中留一句time.sleep(2)的原因,否则在低配主机下极容易出现卡机情况 #2. 任务完成的响应延迟增大了,因为每过一段时间才去轮询一次read操作,而任务可能在两次轮询之间的任意时间完成。这会导致整体数据吞吐量的降低。
此外,在这个方案中recv()更多的是起到检测“操作是否完成”的作用,实际操作系统提供了更为高效的检测“操作是否完成“作用的接口,例如select()多路复用模式,可以一次检测多个连接是否活跃。
from socket import * server=socket() server.bind(('127.0.0.1',8080)) server.listen() server.setblocking(False) r_list=[] w_list=[] while True: try: conn,addr=server.accept() r_list.append(conn) except BlockingIOError: print(len(r_list),'干活') for c in r_list[:]: try: data=c.recv(1024) if not data:continue w_list.append((c,data)) except BlockingIOError: continue except ConnectionResetError: r_list.remove(c) c.close() for tup in w_list[:]: try: conn,data=tup conn.send(data) w_list.remove(tup) except BlockingIOError: continue
多路复用(IO multiplexing)
from socket import * import select server=socket() server.bind(('127.0.0.1',8080)) server.listen() r_list=[server,] w_list=[] data_list=[] while True: rlist,wlist,_=select.select(r_list,w_list,[]) print('rl:%s'%len(rlist),'wl:%s'%len(wlist)) for c in rlist: if c==server: conn,addr=c.accept() r_list.append(conn) else: try: data=c.recv(2048) if not data: c.close() r_list.remove(c) continue if c not in w_list: w_list.append(c) data_list.append((c,data)) except ConnectionResetError: c.close() r_list.remove(c) if c in w_list:w_list.remove(c) for c in w_list: for i in data_list: if c == i[0]: c.send(i[1]) data_list.remove(i) w_list.remove(c)
在非阻塞IO模型中,我们要想实现并发,就必须一直询问操作系统缓存有没有数据或者能不能发送,这样是非常耗cpu的
所以我们想了一个办法,就是让一个对象去统一检测,如果有socket对象可写或者可读,就把socket对象返出来
然后进行处理,这里需要用到的模块就是select模块
select模块中有一个select函数,这个函数可以实现帮我们检测列表中的socket对象是否可读或者可写
使用方法
1、select.select()函数中几个参数的意思
来源:https://www.cnblogs.com/huikejie/p/11000662.html