day 39 I/O模型之阻塞I/O模型、非阻塞I/O模型、多路复用

今日内容:阻塞I/O模型、非阻塞I/O模型、多路复用

阻塞、非阻塞:程序的运行状态。

  阻塞:指遇到I/O操作;非阻塞:指运行态与就绪态

同步、异步:任务的提交方式。

  同步:指提交任务后在原在等待,等任务运行完毕后再执行其他代码;

  异步: 提交任务不用等,直接运行其他代码,通常与回调函数一起使用

I/O模型:模型就是一种解决问题的套路方法 ,I/O模型解决的是在单线程下的I/O问题       

 为了解决的是服务端的网络I/O,I/O行为:send / recv / connect / accept

收数据会经过两个I/O阶段:【waite_data】 等待收客户端的数据、【copy_data】从操作系统拿到数据后,应用程序再从缓存区读到应用程序内存中

发数据会经过的I/O阶段:应用程序将数据copy给系统缓存: copy_data --  本地I/O速度非常快,但数据量比较大操作系统缓存区被占满时,就会阻塞。

waite_data:相比copy_data耗时最长

copy_data:时间很短

一、阻塞 I/O模型:程序在遇到I/O阻塞时【waite_data、copy_data时】程序都需要在原地等待,什么事都干不了效率底下。默认情况下就是I/O阻塞,

  处理方式一:accept ,  recv、send时程序都需要在原地等待【waite_data、copy_data 阶段】,建立链接- 收发完成,循环处理同一时间只能服务一个客户端,没有并发效果。

  处理方式二:利用多进程accept交给一个进程,链接成功后将链接成功的客户端socket对象交给另一个进程处理【收发数据】实现了并发效果 --  因为进程开销大,通信不便利 --线程

        --  实现了并发

  处理方式三:线程不可能无限开,--  线程池,限制线程数量,保证机器正常运行,当并发量巨大处于I/O阻塞态的线程数大于池中限制数时,多出来的链接就无法链接上 -- 

         gevent模块:测检I/O并在I/O阻塞时切换到另一个任务上,单线程并发【协程】极大的提高并发:

二、非阻塞I/O模型Nonblocking model  :

    server.setblocking(Fasle) 设置为非阻塞【默认是阻塞的】, 不停的建链接,当收到错误信息或者说没有数据时就处理其他任务。有链接就将此链接保存到列表,

    再遍历列表尝试发送接收数据,可以发就发,不能收发就捕获异常【BlockingIOError:continue】【ConnectionResetError:conn.close(); r_list.remove(conn)】,

    不停的建链接-处理数据循环处理实现并发处理。

    问题:每次读取数据时  不一定有数据,为了能够及时数据,需要不停的忙轮询,忙轮询这种方式会在没有数据需要处理时也需要不停的循环,造成了CPU无功占用

import socket

import time

s = socket.socket()
s.bind(('127.0.0.1', 8080))
s.listen(5)

s.setblocking(False) # 默认为阻塞,设置为False表示非阻塞
clinents = [] # 用来存储客户端的列表
msgs = [] # 用来存储需要发送的数据和客户端的对象

while True: # 链接客户端的循环
try:
clinent, addr = s.accept() # 接受三次握手信息
print('来了一个客户端了... %s' % addr[1])
#有人链接成功了
clinents.append(clinent)
except BlockingIOError as e:
print('还没有人连过来。。。')
time.sleep(0.01) # 需要制造I/O来缓解CPU的高速运转
#收数据的操作
for c in clinents[:]:
try: #可能这个客户端还没有数据过来
#开始通讯任务
date = c.recv(1024)
if not date:
            c.close()
continue() #当为空时不用执行msgs.append((c, date)
                    #raise ConnectionResetError()
#c.send(date.upper()) 如果碰巧缓存区满了,这个数据就丢失了
#由于此处捕获了异常 所以应该单独来处理发送数据
msgs.append((c, date))
except BlockingIOError:
print('这个客户端不需要处理')
except ConnectionResetError:
#断开后删除这个客户端
c.close()
clinents.remove(c)

for i in msgs[:]: #发送数据的操作
try:
c, msg = i
c.send(msg.upper())
msgs.remove(i) #如果发送成功!删除这个数据
except BlockingIOError:
pass

三、多路复用:select.select()方法可以不停检查是否有数据,会返回需要处理的链接对象列表

    对比线程池:避免了开启线程的资源消耗

    缺点:同时检测socket数不能超过1024

    为什么是1024个原因如下:

        1.需要给每一个socket的等待队列中添加进程信息

        2.唤醒进程后,进行必须遍历所有socket 才能知道哪些socket有数据了

        3.还要遍历所有soket将进程从等待队列中删除

          上述操作,会大量消耗系统资源,所以必须限制同时处理的socket数据

import socket
import time
import select

s = socket.socket()
s.bind(('127.0.0.1', 8080))
s.listen(5)

"""
参数1: rlist里面存储需要被检测是否可读(是否可以执行recv)的socket对象
参数2: wlist里面存储需要被检测是否可写(是否可以执行send)的socket对象
参数3: xlist 存储你需要关注异常条件 -- 忽略即可
参数4: timeout 检测超时时间 一段时间后还是没有可以被处理的socket 那就返回空列表
返回值:三个列表
1.已经有数据到达的socket对象
2.可以发帝数据的socket对象 怎么可以发 缓冲区没有满
3.忽略
"""
read_list = [s] #将需要检测(是否可读== recv)的socket对象放到该列表中
#accept也是一个读数据操作,默认也会阻塞 也需要让select来检测
#注意select最多能检测1024个socket超出直接报错

write_list = [] #将需要检测(是否可写== send)的socket对象放到该列表中
#只要缓冲区不满都可以写

msg_list = [] #存储需要发送的数据和socket 等待select检测后再进行发送即write_list中的client

print('start')
while True:
rlist, wlist, xlist = select.select(read_list, write_list, [])
print('可读的有%s个,可写的有%s个' % (len(rlist),len(wlist)))
"""
readable_list 中存储的是已经可以读取数据的socket对象 可能是服务器 可能是客户端
"""
#处理可读的socket
for sec in rlist:
if sec == s:
#服务器的处理
clien, addr = sec.accept()
#将新链接的socket对象加入到待检测列表中
read_list.append(clien)
else:
try:
#收数据,客户端的处理
data = sec.recv(1024)
if not data:
# sec.close()
# rlist.remove(sec)
# continue
raise ConnectionResetError()
#不能直接发,因为此时缓冲区可能已经满了,导致send阻塞住,所以要发送数据前将这个socket交给select来检查
write_list.append(sec)
#将要发送的数据先存起来
msg_list.append((sec,data))
except ConnectionResetError :
print('非正常退出')
sec.close()
#对方下线后,应该从待检测列表中删除socket
read_list.remove(sec)
if sec in write_list:write_list.remove(sec)
#处理可写列表
for w in wlist:
#由于一个客户端可能有多个数据要发送,所以遍历所有客户端
for i in msg_list[:]:
if i[0] == w:
w.send(i[1])
#发送成功,将这个数据从列表中删除
msg_list.remove(i)
#数据已经都发给客户端 这个socket不需要再检测是否可写,必须要删除
write_list.remove(w) #否则 只要缓冲区不满 一直处于可写导致死循环
原文地址:https://www.cnblogs.com/qingqinxu/p/10999914.html