首页 > 代码库 > Python开发基础-Day33 IO模型

Python开发基础-Day33 IO模型

IO模型分类

五种IO Model

    blocking IO      阻塞IO

    nonblocking IO     非阻塞IO

    IO multiplexing     IO多路复用

    signal driven IO    信号驱动IO

    asynchronous IO    异步IO

signal driven IO(信号驱动IO)在实际中并不常用,所以只剩下四种IO Model。

 

 

网络IO的两个过程

对于一个network IO ,会涉及到两个系统对象,一个是调用这个IO的process (or thread),另一个就是系统内核(kernel)。当一个read操作发生时,它会经历两个阶段:

  •  等待数据准备 (Waiting for the data to be ready):等待系统接收数据
  •  将数据从内核拷贝到进程中 (Copying the data from the kernel to the process):进程从系统缓存中拿到数据

同步IO:在这两个过程中有任意阶段出现阻塞状态。

  阻塞IO、非阻塞IO、IO多路复用都是同步IO

异步IO:全程无阻塞的IO

   异步IO属于异步IO(真的没毛病)

阻塞IO(Blocking IO)

技术分享

UDP包:当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据。对于network io来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),这个时候kernel就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来。

blocking IO的特点就是在IO执行的两个阶段都被block了。

示例:

 1 #服务端
 2 import socket
 3 sock=socket.socket()   #默认是TCP
 4 sock.bind(("127.0.0.1",8088))
 5 
 6 sock.listen(5)
 7 while True:
 8     conn,addr=sock.accept()   #默认是就是阻塞的方式,监听等待客户端连接(阶段一):等待中的阻塞
 9                               #客户端连接后接收数据(阶段二):socket对象和客户端地址,虽然接收数据的过程很快但是实际上也是阻塞
10     while True:
11         data=http://www.mamicode.com/conn.recv(1024)    #也是两个阶段的阻塞
12         print(data.decode(utf8))
13         if data.decode(utf8) ==q:
14             break
15         respnse=input(>>>>)
16         conn.send(respnse.encode(utf8))
17 
18 
19 #客户端
20 import socket
21 sock=socket.socket()
22 sock.connect(("127.0.0.1",8088))
23 
24 while True:
25     data=http://www.mamicode.com/input(>>>).strip()
26     sock.send(data.encode(utf8))
27     s_data = http://www.mamicode.com/sock.recv(1024)    #两个阶段的阻塞
28     print(s_data.decode(utf8))

 

非阻塞IO(Non-blocking IO)

 技术分享

当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。所以用户进程不需要等待,而是马上就得到了一个结果,用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。这个过程中,用户进程是需要不断的主动询问kernel数据好了没有。

非阻塞实际上是将大的整片时间的阻塞分成N多的小的阻塞,每次recvform系统调用之间,可以干点别的事情,然后再发起recvform系统调用,重复的过程通常被称之为轮询。轮询检查内核数据,直到数据准备好,再拷贝数据到进程,进行数据处理。需要注意,拷贝数据整个过程,进程仍然是属于阻塞的状态。

优点:能够在等待任务完成的时间里干其他活了(包括提交其他任务,也就是 “后台” 可以有多个任务在同时执行)。

缺点:任务完成的响应延迟增大了,因为每过一段时间才去轮询一次read操作,而任务可能在两次轮询之间的任意时间完成。这会导致整体数据吞吐量的降低。

 1 #服务端
 2 
 3 import socket
 4 import time
 5 sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)   #默认是TCP
 6 sock.bind(("127.0.0.1",8088))
 7 sock.listen(5)
 8 sock.setblocking(False)
 9 
10 while True:
11     try:
12         print(server waiting)
13         conn, addr = sock.accept()  # 默认是个阻塞的方式,等待客户端连接
14         while True:
15             data = http://www.mamicode.com/conn.recv(1024)  #这边也是阻塞的IO
16             print(data.decode(utf8))
17             if data.decode(utf8) == q:
18                 break
19             respnse = input(>>>>)
20             conn.send(respnse.encode(utf8))
21     except Exception as e:
22         print (e)
23         time.sleep(4)
24 
25 #客户端
26 import socket
27 sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)   #默认是TCP
28 
29 while True:
30     sock.connect(("127.0.0.1", 8088))   #因为服务端recv也是非阻塞,所以要不断重新连接
31     data=http://www.mamicode.com/input(>>>).strip()
32     sock.send(data.encode(utf8))
33     s_data = http://www.mamicode.com/sock.recv(1024)
34     print(s_data.decode(utf8))

 

 

IO多路复用IO multiplexing

IO多路复用,也叫做event driven IO,实现方式:select,poll或epoll

IO多路复用的好处就在于单个process就可以同时处理多个网络连接的IO

技术分享

用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。这个过程中有两次system call(系统调用) select阻塞时候 和 recvfrom阻塞时候。用多路复用的的优势在于它可以同时处理大批量的connection,不适用单个或少量,少量还不如multi-threading + blocking IO。

select示例:

 1 #服务端
 2 import socket
 3 import select
 4 sock=socket.socket()
 5 sock.bind(("127.0.0.1",8088))
 6 sock.listen(5)
 7 
 8 inp=[sock,] #定义监听的套接字对象列表,列表列表里可以有多个对象
 9 
10 while True:
11     #字段顺序:input list 、output list、error list、date(可以不写)
12     r=select.select(inp,[],[],None) #对比的是sock.accept(),这一步只做了监听的事情,监听哪个socket对象活动,当没有客户端连接时候会阻塞
13     # 当监听到有活动的socket对象时候,将返回值给r
14     print(r,r)
15     print(r,r[0])
16     #r接收的返回是一个元组,r[0]是活动的对象列表
17 
18     for obj in r[0]:
19         if obj == sock: #如果活动的对象是sock,那么将客户端对象加入监听列表,客户端再发数据时候,触发客户端的对象活动
20             conn,addr=obj.accept()  #accept只做第二个阶段的事情,取回数据:client的socket对象和地址
21             print(conn,addr)
22             inp.append(conn)
23         else:
24             data=http://www.mamicode.com/obj.recv(1024)
25             print(data.decode(utf8))
26             resp=input(>>>)
27             obj.send(resp.encode(utf8))
28 
29 #客户端
30 import socket
31 sock=socket.socket()  
32 sock.connect(("127.0.0.1", 8088))
33 while True:
34     data=http://www.mamicode.com/input(>>>).strip()
35     sock.send(data.encode(utf8))
36     s_data = http://www.mamicode.com/sock.recv(1024)
37     print(s_data.decode(utf8))

 

因为使用的是for循环,当多个客户端发消息给服务端,只能一个个顺序处理。

在windows下只能用select实现多路复用

在Linux可以使用select、poll、epoll实现,推荐使用epoll,对比:

  select和poll的监听方式为轮询方式,即每次都要循环一遍监听列表,效率低,另外select有连接数限制,poll无限

  epoll连接数无限,区别在于监听方式不同,每个socket对象绑定一个回调函数,当socket对象活动了就触发回调函数,把自己写到活动列表中,epoll直接调用活动列表

 

信号驱动IO(signal driven IO)

不常用,不做说明

 

异步IO(Asynchronous I/O)

技术分享

 

用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。

 

IO模型区别

 技术分享

 selectors模块

 该模块能够按照系统平台,自动选择多路复用的方式。

 1 #服务端
 2 import selectors
 3 import socket
 4 
 5 sel=selectors.DefaultSelector()
 6 
 7 def accept(sock,mask):
 8     conn,addr=sock.accept()     #4、获取客户端的conn对象和地址
 9     print(accetped,conn,from,addr)
10     conn.setblocking(False)
11     sel.register(conn,selectors.EVENT_READ,read)    #5、注册conn对象,将conn对象和函数read绑定
12 
13 def read(conn,mask):
14     data=http://www.mamicode.com/conn.recv(1024)    #9、服务端通过conn对象接收消息,进行下面的逻辑处理
15     if data:
16         print(echoing,repr(data),to,conn)
17         conn.send(data)
18     else:
19         print(closing,conn)
20         sel.unregister(conn)
21         conn.close()
22 
23 sock=socket.socket()
24 sock.bind((127.0.0.1,8088))
25 sock.listen(100)
26 sock.setblocking(False)
27 sel.register(sock,selectors.EVENT_READ,accept)  #sock对象注册绑定accept函数
28 
29 while True:
30     #不管是哪个方式,都是使用select方法监听活动的socket对象
31     events=sel.select()     #1、执行sel阻塞监听,当有客户端连接,激活sock对象,返回一个存放活动sock对象相关信息的列表
32                             #6、客户端通过conn对象发送消息,激活sel监听列表中的的conn对象,返回一个存放活动conn对象相关信息的列表
33     print(events,type(events))
34     for key,mask in events:
35         print(mask)
36         print(key.data)   #socket对象注册绑定的accept函数
37         print(key.fileobj)
38         callback=key.data   #2、取得返回的sock绑定的函数
39                             #7、取得返回conn绑定的函数
40         callback(key.fileobj,mask)  #3、key.fileobj是sock对象,执行函数
41                                     #8、执行函数read,并传入conn对象
42 
43 
44 #客户端
45 import socket
46 sock=socket.socket()
47 sock.connect(("127.0.0.1", 8088))
48 while True:
49     data=http://www.mamicode.com/input(>>>).strip()
50     sock.send(data.encode(utf8))
51     s_data = http://www.mamicode.com/sock.recv(1024)
52     print(s_data.decode(utf8))

 

Python开发基础-Day33 IO模型