python开发基础篇:八:I/O模型

1:I/O模型参考文档  https://www.cnblogs.com/Eva-J/articles/8324837.html

IO模型介绍:网络I/O模型,一般都是网络当中的问题
  blocking IO             阻塞IO
  nonblocking IO         非阻塞IO
  IO multiplexing         IO多路复用
  signal driven IO        信号驱动IO
  asynchronous IO        异步IO

reqursts.get一个url,这里是有I/O操作的,这个I/O在等待结果,正常情况下一定阻塞,

  使用协程处理问题不会阻塞等待,会保留这个执行的状态,保留之后切换任务再去执行一个request请求
  假设同一时间执行10个request.get那么就节省了很多等待结果的时间,使用等待结果的时候开启执行别的任务,
  get的时候都有网络延时,但是这个网络延时所有的任务共享的,这个期间有I/O操作,但是没有阻塞,在等待I/O的同时去做了其他事情
  发了请求就干别的事了,不阻塞,等待多次时间拿到结果跟阻塞没有关系,是要拿结果的事情阻塞,请求网络这件事情没有阻塞,拿结果没有阻塞执行其他任务
  阻塞:有I/O就等着,等到有结果返回再去干其他事情

socketserver使用的就算IO多路复用来实现并发

网络当中经常遇见的阻塞:
  recv,recfrom,accept,
  send,connect,sendto  这些都是常见的网络I/O
  request.get()发送一个请求到对方服务器,发过去中之后就不管了,
    发送过去有网络延时,服务端接收端到请求有个处理时间,服务端发送数据过来有网络延时---
这三个过程就是数据准备阶段
    发到客户端是操作系统内核先接收到数据,进程还需要从操作系统copy数据出来还需要时间(这个时间比较短)---数据copy阶段
    经过网络数据都是先发送给操作系统内核的,进程再从操作系统内核拷贝数据出来,这一段时间的固定的,剩下的所有的时间都是等待数据准备时间
    网络I/O分为两个阶段:
      1:数据准备阶段
      2:数据copy阶段
  
  send发送数据也需要数据准备和数据copy,send(msg),需要有msg,生产msg的过程就是数据准备阶段
    假设msg接收一个input也需要等着,msg = 5开辟一个空间存储数据也需要时间----这就是数据准备阶段
    将要发送的msg发送给操作系统内核,操作系统帮忙发送数据----这就是send的数据copy阶段

  一般recv需要等而send不需要等:
    recv需要等别人,而send自己发起的,recv不知道什么时候别人发数据,所以只能傻等,
    send自己知道自己什么时候发数据,准备数据阶段和copy数据的阶段非常短,所以可以忽略不计

 2:阻塞IO(blocking IO)

阻塞:程序堵住停在那里    sleep,input,recv,accept这些都是阻塞
非阻塞:正常写的代码都是非阻塞,除了上面这些
socket里面可以设置recv是不阻塞的:setblocking,不拿到数据也过去

同步:干完一件事再干一件事,不可能同时处理两个任务
  sleep这里阻塞等着停1s,这里就同步了,如果停着1s的时候去干其他事情了这里还是有阻塞,只是没有同步了
  sleep(1)的时候去print打印数据,sleep的同时还能去做其他事情,这就说明并没有被阻塞住,我是异步了
  这个程序里面任然有个time.sleep,程序在这一步还是需要停1s,只是在这个地方这个程序虽然阻塞了停1s,
  但是其他的程序还能走就是异步如果这里阻塞了程序,其他程序也不能走就是同步
  同步:几个事,一个个顺序执行 异步:同时处理多个任务
  异步:同时执行几个事
socket recv数据步骤:
    1:等待发送方send数据,recv会阻塞在这里,recv等的过程也是I/O,没发数据之前等的过程也是I/O操作
    2:send数据之后数据先发送给发送端操作系统的内核态,发送端的内核态发送给接收方的操作系统内核缓存起来,
    这一段时间是网络的I/O,网络的延时,没有发送到接收方的内核上接收方感知不到发送数据 2就是准备数据阶段,这里发送端准备数据把数据send到内核需要一段时间,数据在网络上传输也耗费一段时间,这两段整体称位数据准备阶段 3:数据到达接收方的内核了,需要把数据拿到进程里,这是copy data复制数据阶段,复制数据也需要时间,这也是I/O,这个I/O相对2准备数据阶段很短
阻塞I/O模型:
recv想从网络上获取数据,这个recv请求发送给操作系统,操作系统接受到请求,操作系统本来没有数据,

  操作系统就让你等待,等到发送端数据准备阶段完成,准备好了数据发送给接收端,接收端操作系统接收到数据了,
  recv还是陷入等待,等待数据从操作系统copy到进程里,copy结束,这就结束一次recv,程序就拿到数据了,结束阻塞继续执行其他代码
  ----这个过程就是一个阻塞过程,
这就叫阻塞I/O模型,平时都是使用这个,发起一次recv等着的过程都是阻塞I/O模型
  等着的,停在这里阻塞着
阻塞I/O:
  recv,recfrom,accept,request.get()

  一个完整的recv拿到数据的阻塞I/O图:中间相关操作很多

阻塞的效果就是 程序在这里停住了
阻塞I/O大大降低了cpu的利用率,
socket服务端:
  主进程阻塞问题,使用多进程,多线程规避了阻塞问题,分离了阻塞,
  让多个阻塞分别到每个线程或者每个进程去阻塞了,没有真的降低I/O
  但是多线程多进程没有真的解决上面这些阻塞,
多线程和多进程:
  来一个人请求就得开几个线程,线程进程不无限开辟,
  使用线程进程池概念来避免无限开启线程进程的情况,但是池是有限的,池只有4个进程只能接收4个人的聊天链接进来聊天
开超过2个进程多使用进程池,控制资源,但是单纯的进程池不能满足用户的需求,只适合小并发的问题

真正需要解决的实际上还是I/O,多线程多进程本质上没有解决I/O问题,只是分离了
  把recv,recvfrom和accept时间利用起来,提供程序效率

3:非阻塞IO(non-blocking IO)

recv发送请求给操作系统,告诉他我想recv请求数据了,操作系统没有数据,就不在这里阻塞着,直接告诉你没有数据---这一个recv就结束了,代码不会阻塞在这里,就可以去干别的事情
做的其他事情和要接收的数据没有关系,就可以去做其他事情(获取网页返回结果时候拿到结果和请求下一个网页没有关系,可以在等待网络延时给数据或者socket send数据的同时可以再去干别的事情)
过了一会再去问操作系统有数据没有,又执行一个recv,假设操作系统还是没有数据,又返回回来一个没有数据,又去干别的事情,干完自己的事情后又问操作系统,这时候假设有数据了
把数据返回给我,copy data出来,等待数据的阶段不用等待了,等待准备数据,没准备好不等就回来,准备好了数据之后还需要将数据copy到进程,这个阶段copy 数据还是需要等待,
只不过这时候等待的时间很短很短,这样一个recv操作才执行完
阻塞I/O一直recv一直等的情况下,假设数据等待了10s才接收到一个数据,现在使用这10s做了多次其他事情
微观下实际上切换十分迅速,过一会就问一下cpu数据来了没

非阻塞I/O:
工作效率高,带来了CPU的负担,需要反复查询数据给计算机增加了负担,一般情况下不使用,掌握不好cpu负担问题,cpu占用率太高对程序不利,卡
  但是sleep来降低cpu的使用率,cpu下来了,但是操作延时就变大了,比如别人给你发消息需要1s才接收到,消耗cpu才换来及时性
  最好的方式是异步I/O,但是python没有机制去实现,操作系统干的,所以暂时使用I/O多路复用来减少cpu使用率,提高工作效率,程序异步起来
阻塞I/O:工作效率底
I/O多路复用:在有多个对象需要I/O阻塞的时候,能够有效的减少阻塞带来的时间损耗,且能够在一定程度上减少cpu的负担,
异步I/O:既cpu工作效率高,cpu负担也低,但是自己实现不了,
    asyncio这个爬虫模块就是异步I/O模块,自己封装了很多操作系统级别的命令
    节省了等待数据传来阶段的时间,还节省了数据从操作系统到进程的时间

阻塞I/O等到就行,不需要反反复复问内核数据准备好了没,数据1s没有来,那么需要反反复复在1s内切换很多次查询,假设没有任务要执行的时候,
  没有要执行的任务就一直查询数据,时间耗费大

  一个完整的recv拿到数据的非阻塞I/O图

  非阻塞IO(non-blocking IO)代码实例

server端tcp服务设置非阻塞,接收多个链接进来和多个链接聊天
server.py
import socket sk = socket.socket() sk.bind(("127.0.0.1", 9999)) sk.listen() sk.setblocking(False) # 设置程序非阻塞,默认为True,异步的程序 conn_list = [] del_lst = [] while 1: # 为了实时监听accept和recv两个行为 try: conn, addr = sk.accept() # 危险,可能报错 非阻塞 # 有链接来才能执行下面的 for c in conn_list:里面的代码 # 如果一次性发起20个链接后就再也没有链接来了, # 那么就conn, addr = sk.accept() 这里报错,try这里conn, addr = sk.accept() 后面的代码都无法执行 # 就执行不到for循环里面的代码了,就无法和别人聊天了 # 所以for c in conn_list:聊天的代码需要放到except里 # 但是for c in conn_list:聊天的代码放在except里有链接进来的一次无法走except里面的代码,这一次循环无法进行聊天 # 但是没有必要解决这个问题 conn_list.append(conn) except BlockingIOError as f: for c in conn_list: # server端运行报错,现在不阻塞了,一瞬间就报错了,所以需要加异常处理 try: msg = c.recv(1024).decode("utf8") # 设置sk.setblocking(False) 之后recv也不会阻塞了,全都不阻塞了 if not msg: c.close() del_lst.append(c) # 如果收到空消息,表示对方链接已经关闭,那么close关闭这个链接后把这个链接放入到删除列表里 # 然后在for循环外面从conn_list列表里把关闭的del_lst已经存储的已经关闭的链接删除了 # continue # 如果对方链接已经关闭了那么 continue 跳过本次循环, # # 因为后面的 c.send(msg.upper().encode("utf8"))会报错,链接都没了还发个鸡巴消息 else: c.send(msg.upper().encode("utf8")) # 上面是if是关链接,下面else是发数据 except BlockingIOError as f: pass # 接收不到消息也不处理了 if del_lst: for del_item in del_lst: conn_list.remove(del_item) del_lst = [] 第一步:conn, addr = sk.accept() 接收一个链接,有链接来不执行except里面的代码, 第二步:conn_list.append(conn)把链接放到列表里一步第二步因为没有阻塞所以执行速度非常快 上面就是循环一次,马上又循环一个一次,又执行一个try 第二次循环执行try,假设没有链接来,conn, addr = sk.accept() 报错执行except里面的语句 马上执行for c in conn_list:循环所有的链接列表,同时跟多个人进行聊天,100个人都给我发数据 然后recv这里都接收到,然后挨个问100个人有没有数据发给我,还可能100个人只有4个人跟我说话 剩下的96个人不跟我说话那么msg = c.recv(1024) 这里也不会阻塞,会报错 所有聊天的代码msg = c.recv(1024)这里还需要加try 不能在 for c in conn_list:外面加try 因为try之后报错后面的代码都不执行了,for外面加try,任何一个地方报错整个for循环都不执行了,后面的链接聊天就聊不了 for循环内部加try,内部出了问题不影响整个for循环 尝试接收数据,发送打印数据 对方的链接断开后我这边conn还是一直在接收,conn.recv接收到的一直是空数据 使用if处理一下,如果没有msg,把conn关了,把conn从conn_list里删掉,删除列表不能在for循环里面删除 sk.setblocking(False):设置了非阻塞套接字 sk.accept()这里就不阻塞等待链接进来了,能接收到消息就拿到conn和addr,接收不到消息就报BlockingIOError错误 这里while循环能接收到好多个conn,反复的接收, 现在能多个连接进来,但是conn在while循环里一直变化,被新进来的链接覆盖了 所以需要把conn存起来,存储在列表里, 然后遍历conn_list,看有没有人发消息过来,recv接收msg消息 现在就能和所有人聊天了 while循环, 每一次进入循环监听一波有没有链接进来, 每一次进入循环都问一下每个conn有没有收发消息
client.py

import socket
import time
import threading
def func():
    sk = socket.socket()
    sk.connect(("127.0.0.1", 9999))
    time.sleep(1)
    sk.send(b"hi")
    print(sk.recv(1024))
    sk.close()
for i in range(10):
    threading.Thread(target=func).start()

4:多路复用IO(IO multiplexing)  select模型监听多个对象处理多个任务的操作  I/O多路复用,事件驱动

select是操作系统I/O多路复用模型提供的一个机制,select是操作系统提供的,可以使用python模块来操作,也会阻塞等待,这个模型可以监听一个socket对象,
  sk.accept,conn.recv,告诉需要一个数据,他来连接我或者给我一个数据,原本是accept或者recv阻塞,指令发送给操作系统
  现在把select多路复用对象,操作select来监听一个socket相关的对象,然后告诉操作系统要发数据了,操作系统来监听数据这些对象sk或者conn
  这个时候每当有一个数据发送过来操作系统去主动告诉你有数据来了,请你接收数据,程序得到通知之后再去进行recv操作,然后再去回信告诉操作系统可以copy data了
  将数据从操作系统copy到进程,程序获得数据,执行其他任务了
I/O多路复用需要借助操作系统进行I/O多路复用的机制,select(阻塞)发送消息告诉操作系统需要数据,操作系统准备数据

多路复用IO:只监听一个对象还不如阻塞I/O快,需要回信什么,但是可以同时监听多个对象,监听sk,监听conn,20个甚至更多个用户连接监听conn2.....conn20
    有人连接进来得到通知sk有消息了,sk有消息了就执行sk.accept,conn来消息了得到通知执行conn.recv

I/O多路复用:wait时间共享了,有信息就处理,但是copy data的时间还是需要等待,数据到了操作系统必须等待,等到程序拿到,这个时间很短但是也很耗时
      效率最高的是异步I/O

  I/O多路复用流程图

select
poll
epoll
操作系统提供了I/O多路复用的机制。不是python程序提供的,多路复用机制一共3种,select,poll,epoll
  windows里面只有select,Linux上兼顾上面三种I/O多路复用的模式

以前接收数据是一个recv请求
传递给操作系统
现在是一个select把想监听的文件描述符放在这里面,现在表面上是select在阻塞,select是I/O多路复用的机制
  操作系统的操作使用python模块来调用
  select模块就是操作操作系统进行I/O多路复用的机制
  原本程序是recv就阻塞,现在把套接字交给操作系统的select模块让他去帮我阻塞---select阻塞,因为想要接收数据
  请求发送给操作系统,操作系统暂时没有数据的适合,select模型任然在这里阻塞,阻塞cpu的利用率不高没有利用起来,好处是不会给cpu加负担
  select这里也需要等着,等着有数据,当有数据的时候,操作系统通知进程数据来了,程序接收到通知,接收到通知之后知道recv的数据来了
  程序再执行一次recv,之前的阻塞因为select在阻塞,当有数据的时候告诉我recv需要接收数据了,程序recv,告诉操作系统开始接收数据了
  copy data阶段,数据就赋值到进程

I/o多路复用,假设一个套接字,既经历wait data,又经历copy data,两次阻塞都阻塞程序,比I/O阻塞还多了个通知进程数据来了让他开始接收数据的环节
  一个套接字的话I/O多路复用更慢

多个任务来说,监听多个文件描述符,放了很多请求的时候,某个套接字来消息了就通知进程,接受到通知之后进程知道到底是哪个套接字来请求了
  假设conn3有请求了,那么conn3.recv就可以了。这就复用起来了,
  同时监听多个conn,sk这些socket链接,能同时接收到消息,接收消息经历copy data之后在底下去处理数据
  这个过程循环交替进行,就能又让cpu闲置,又能顺利接收通知和消息
  多个任务来说可以让没有消息的时候让cpu闲置下来,有消息的时候程序才去处理

  I/O多路复用实例1

# server端
import
select import socket sk = socket.socket() sk.bind(("127.0.0.1", 9999)) sk.listen() read_list = [sk]

1:往select里面写了一个read_list告诉操作系统需要监听这些对象,当他有消息可读的时候通知我
2:程序在select这里一直阻塞,阻塞到监听的对象某一个有消息过来了就解除阻塞,拿到返回值,返回的对象ret就是监听的对象里面有读写动作的部分
  只要有返回就说明sk一定有数据了
3:判断如果返回的是sk。就sk.accept接收链接,接收链接一定成功,因为是有链接才来通知的,这里是不阻塞的,直接就拿到conn,
  拿到conn之后不能recv,recv就会阻塞程序,没有数据就一直阻塞等数据,conn的recv也是读的操作,把conn添加到read_list里
4:现在监听的读的对象有[sk, conn]程序又在这里阻塞了,等待两件事:1:sk有数据可读 2:conn有数据可读
  conn有数据可读说明客户端通过链接发送数据过来了,sk有数据可读说明有客户端connect进来了
  现在返回的r_list返回的对象不一定是sk或者conn,谁有数据过来就让谁返回
  假设connect链接进来就是sk得到响应,这时候拿到sk继续accept,往read_list里面添加一个conn2另外一条链接,又多监听了一个对象
  如果有消息进来了,就算conn得到响应了,走else语句,拿着conn recv数据,打印数据。
  如果conn接收到的数据为空,别人发空消息,conn仍然能感知到,conn任然能够接收,接受到的是空数据
  如果收到的数据为空说明对方关闭了链接,走if条件判断,知道conn被别人断开了,连着没有意义,我这段也close断开连接
  close了之后需要把监听的read_list列表里删除已经被关闭的conn,删了之后select监听的链接就不会监听删了的了,等于链接关闭了
  有时候别人发了数据就把sk.close了,服务端这里还没有close,服务端这里仍然能接收到别人给我发数据,
5:现在写的比较简单,假设一次性监听到了多个文件对象有读操作,需要for循环遍历每个文件对象去进行读操作
  判断是sk就进行accept,判断是conn就recv
while 1: r_list, w_list, x_list = select.select(read_list, [], []) # select阻塞 print(r_list) if r_list[0] == sk: conn, addr = r_list[0].accept() print(conn, addr) read_list.append(conn) else: ret = r_list[0].recv(1024) print(ret) if not ret: # 客户端把socket链接close关闭后,客户端会发送个空信号,这边还会触发select里面的r_list, # 这里recv读取一个空信息打印,然后下面把断开的链接remove再也不接收了 # 客户端链接关闭后这时候conn这个套接字会一直接收空消息, # 所以需要判断一下,把对方关了的套接字从监听列表里删除 r_list[0].close() read_list.remove(r_list[0]) # 监听多个文件对象,任何一个对象有数据就返回文件描述符,while 1监听多次, # 运行select这行就阻塞代码,当有客户端连接的时候,select就取消阻塞,select能瞬间感知到通知 # select感知到之后就把来了消息的文件对象socket放到返回结果ret里面 # 所有就能拿到列表里感知到有人连接进来的socket对象, # 如果想让socket感知多次有客户端连接进来,需要while循环 # for r in r_list: # conn, addr = r.accept() # 把sk交给操作系统让他去检测有没有人来连接.当有人连接的时候能接收到通知, # 把sk放到返回值ret的r_list里面,告诉程序这个r_list里面的对象都可以读取数据的 # 这里监听的sk只有accept读取数据 # 从原来的accept这里去等待, # 变成了现在的监听对象能不能读,能读了之后再去做accept操作,这个过程不是阻塞的, # 现在只要走到accept一定是有数据进来给他读取的,有人连接进来且请求到了操作系统了, # 操作系统通知程序有人连接进来再去接收链接拿到conn # wait data阶段是select执行的 # copy data阶段是accept执行的 # conn当前链接,可能被用到当前链接聊天,聊天不需要conn.recv,因为这样用会阻塞 # 再有别的人连接进来阻塞的时候感知不到, # read_list.append(conn) 参数1:rlist -- wait until ready for reading,读数据 sk一直accept读取数据 程序一开始运行起来在 select.select这里阻塞着, 当有读的信息,写的信息过来了不阻塞了 ret是一个元组,元组里返回3个列表 列表1:r_list可读的 列表2:w_list可写的 列表3:x_list可改的 假设监听了10个对象,只有可读的(被连接上的,recv有数据过来的)才会在ret列表里
server
import select
import socket

sk = socket.socket()
sk.bind(("127.0.0.1", 9999))
sk.listen()
read_list = [sk]

while 1:
    r_list, w_list, x_list = select.select(read_list, [], []) 
    print(r_list)
    for item in r_list:
        if item == sk:
            conn, addr = item.accept()
            print(conn, addr)
            read_list.append(conn)
        else:
            ret = item.recv(1024)
            print(ret)
            if not ret:
                item.close()
                read_list.remove(item)
client
import socket
import time

sk = socket.socket()
sk.connect(("127.0.0.1", 9999))
time.sleep(20)
sk.send(b"hello")
time.sleep()
sk.close()

  I/O多路复用实例2

server
import select
import socket
sk = socket.socket()
sk.bind(("127.0.0.1", 9999))
sk.listen()
read_list = [sk]
while 1:
    r_list, w_list, x_list = select.select(read_list, [], [])
    for item in r_list:
        if item == sk:
            conn, addr = r_list[0].accept()
            print(conn, addr)
            read_list.append(conn)
        else:
            ret = item .recv(1024).decode("utf8")
            if not ret:
                item .close()
                read_list.remove(item)
            else:
                print(ret)
                item.send(f"received {ret}".encode("utf8"))
# 一个线程select就实现并发了,这就是I/O多路复用
client
import socket
import time
import threading
def client_async(args):
    sk = socket.socket()
    sk.connect(("127.0.0.1", 9999))
    for i in range(10):
        time.sleep(2)
        sk.send(f"{args}:{i}:hello".encode("utf8"))
        print(sk.recv(1024).decode("utf8"))
    sk.close()
for i in range(20):
    threading.Thread(target=client_async, args=("*"*i,)).start()

 

select处理机制:表面看上去是并发一起做处理,处理完了之后通知我
  操作系统拿着read_list,read_list存储各种文件描述符,操作系统也会一个个的去问每个都准备好了没,挨个问
  如果100个文件描述符就问100次,如果问第一个的时候第100个就已经好了就会有延时----这就是问题
  select,poll都是轮询的方式去问文件描述符数据好没好,随着要检测的数据增加,效率会下降,问的人越多效率越低
  但是select和poll存储数据用的数据结构不同,看上去都是列表,底层还有另外的数据结构的存储,不同的数据存储方式
  对于一些数据结构是有固定长度的,
  select有数目的限制,大小限制
  poll数据结构和select有所不同,能处理的对象更多
select和poll都不适合处理大并发

epool:能处理多对象,不是使用轮询方式检测文件描述符,每一次当往里面放一个对象的时候,它给这个对象添加一个回调函数
  100个对都在操作系统里,操作系统不挨个问就在这,当一个数据找到对象的时候,这一个对象马上去调用回调函数,调用回调函数通知你,一下就拿到通知了
  这样是及时响应的,这就是epoll的优势,epoll只能在linux里使用
  select模块只能使用select模型,如果想使用epoll也行,需要操作系统上有epoll

 

 

selectors
#服务端
from socket import *
import selectors

sel=selectors.DefaultSelector()  # 创建一个默认的多路复用模型,这个帮忙选择一个最好的I/O复用的模型,有epoll就使用epoll,没有就使用poll或者select,帮我们选择模型
def accept(server_fileobj,mask):
    conn,addr=server_fileobj.accept()
    sel.register(conn,selectors.EVENT_READ,read)

def read(conn,mask):
    try:
        data=conn.recv(1024)
        if not data:  
# linux和winwods客户端,不同操作系统对方关闭套接字后recv效果不一样,报错是linux操作系统,not data是window系统,接收空数据,
# 不同的操作系统client断开连接都需要处理,这里就是做的两个处理
# 如果拿到空数据说明对方关闭了套接字就close,sel取消注册的套接字
print('closing',conn) sel.unregister(conn) conn.close() return conn.send(data.upper()+b'_SB') except Exception:  # 报错是linux操作系统 print('closing', conn) sel.unregister(conn) conn.close()
# 不同的系统client断开链接都需要处理 server_fileobj
=socket(AF_INET,SOCK_STREAM) server_fileobj.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) server_fileobj.bind(('127.0.0.1',8088)) server_fileobj.listen(5) server_fileobj.setblocking(False) #设置socket的接口为非阻塞 sel.register(server_fileobj,selectors.EVENT_READ,accept) #相当于往select的读列表里append了一个文件句柄server_fileobj,并且绑定了一个回调函数accept # 使用已经创建好的模型去注册想要监听的对象,现在想监听sk对象,server_fileobj就是sk,
# selectors.EVENT_READ指定要监听对象的读操作,只管accept读取数据,给文件描述符读操作注册一个accept方法,一旦socket出现需要读数据就调用自己定义的accept方法
# 这行代码:注册一个socket,监听它,当他有数据可读的时候就调用accept方法
while True: events=sel.select() #监听,检测所有的fileobj,是否有完成wait data的,阻塞这里,来一个人连接进来就解除阻塞 for sel_obj,mask in events: callback=sel_obj.data #callback=accpet,sel_obj.data拿到当初注册的accept方法,后面执行注册方法,传递两个参数,sel_obj.fileobj就是监听到的socket对象 callback(sel_obj.fileobj,mask) #accpet(server_fileobj,1)
# sel_obj拿到监听有活动的套接字对象,
# mask:有点像address,但是不同,里面还包括客户端发过来的链接已经本地链接的各种信息都在mask里面
# sel_bij.data找到之前注册的方法,有人连我就调用注册的方法,调用方法把sel_obj.fileobj传进去,
# sel_obj.fileobj就是一个conn或者sk的socket套接字对象
# 有人触发了在sel当中注册的对象,比如有人触动sk才走到for循环这里,触发后得到两个selector模块帮忙封装的对象
#
sel_obj.data拿到当初注册的时候写的accept方法,后面调用这个accept方法执行,传两个参数:套接字对象和mask
# mask传递进去其实没有使用到,可以不传递,必须传
sel_obj.fileobj,这个就是监听的socket对象
# 调用accept方法传递socket套接字参数,sk.accept拿到链接conn,又把conn注册到sel对象里,
# 一开始sel对象注册了sk,又注册一个conn进来
# 又陷入while 死循环监听所有注册的文件描述符,现在就监听[sk, conn]两个文件描述符了
# 后面又阻塞在sel.select等待被触发,再触发的时候又看拿到的callback是什么,是accept还是注册的read方法
# conn被触发就获取注册的read方法,sk被触发就获取注册的accept方法
# 如果触发read方法那么就使用read方法recv数据就行了,

#客户端
from socket import *
c=socket(AF_INET,SOCK_STREAM)
c.connect(('127.0.0.1',8088))

while True:
    msg=input('>>: ')
    if not msg:continue
    c.send(msg.encode('utf-8'))
    data=c.recv(1024)
    print(data.decode('utf-8'))

 

5:异步IO(Asynchronous I/O)

异步I/O:发起一个任务没有数据就回来了,回来之后这段时间可以做其他事情,直到操作系统拿到数据之后再把数据copy到进程之后直接发一个信息告诉我进程数据已经来了
  这时候再去响应处理数据,超级快
I/O多路复用可以使用python的select模块实现,异步I/O是操作系统做的,很好,但是python实现不了,
  只能借助操作系统的一些机制或者他提供的接口来做,目前python还没有提供一个完全写出异步I/O操作的接口
  python也是实现了一些异步的模型,有应用限制
I/O多路复用wait data和copy data阶段都需要经历
完全异步I/O整个wait data和copy data这个阶段都不需要我程序来承受,我在等待的过程当中可以做任何事情,只需要等操作系统调用告诉我数据好了就行了
  异步框架来说完全不阻塞的

 



 

posted @ 2022-03-08 17:42  至高无上10086  阅读(184)  评论(0编辑  收藏  举报