IO并发模型

IO 分类

IO分类:阻塞IO ,非阻塞IO,IO多路复用,异步IO等

阻塞IO

1.定义:在执行IO操作时如果执行条件不满足则阻塞。阻塞IO是IO的默认形态。

2.效率:阻塞IO是效率很低的一种IO。但是由于逻辑简单所以是默认IO行为。

3.阻塞情况:

  • 因为某种执行条件没有满足造成的函数阻塞
    e.g. accept input recv

  • 处理IO的时间较长产生的阻塞状态
    e.g. 网络传输,大文件读写

非阻塞IO

1.定义 :通过修改IO属性行为,使原本阻塞的IO变为非阻塞的状态。

设置套接字为非阻塞IO

sockfd.setblocking(bool)

  • 功能:设置套接字为非阻塞IO
  • 参数:默认为True,表示套接字IO阻塞;设置为False则套接字IO变为非阻塞

超时检测 :设置一个最长阻塞时间,超过该时间后则不再阻塞等待。

sockfd.settimeout(sec)

  • 功能:设置套接字的超时时间
  • 参数:设置的时间

 1 from socket import *
 2 from time import sleep,ctime
 3 
 4 # 日志文件
 5 f = open('log.txt','a+')
 6 
 7 # 创建套接字
 8 sockfd = socket()
 9 sockfd.bind(('127.0.0.1',9999))
10 sockfd.listen(3)
11 
12 # 设置套接字为非阻塞
13 sockfd.setblocking(False)
14 
15 # 设置超时检测时间
16 sockfd.settimeout(3)
17 
18 while True:
19   print("Waiting for connect...")
20   try:
21     connfd,addr = sockfd.accept()
22   except (BlockingIOError,timeout) as e:
23     # 如果没有客户端连接,每隔3秒写一个日志
24     f.write("%s : %s
"%(ctime(),e))
25     f.flush()
26     sleep(3)
27   else:
28     print("Connect from",addr)
29     data = connfd.recv(1024).decode()
30     print(data)
31 
32 # Waiting for connect...
33 # Waiting for connect...
34 # Waiting for connect...
35 #log.txt文件逐渐写入
36 # Thu Jun 27 00:29:29 2019 : timed out
37 # Thu Jun 27 00:29:35 2019 : timed out
38 # Thu Jun 27 00:29:41 2019 : timed out
非阻塞IO示例

IO多路复用

1.定义:同时监控多个IO事件,当哪个IO事件准备就绪就执行哪个IO事件。以此形成可以同时处理多个IO的行为,避免一个IO阻塞造成其他IO均无法执行,提高了IO执行效率。

2.具体方案:

  • select方法 : windows linux unix
  • poll方法: linux unix
  • epoll方法: linux

select 方法

rs, ws, xs=select(rlist, wlist, xlist[, timeout])

  • 功能: 监控IO事件,阻塞等待IO发生
  • 参数:
    • rlist 列表 存放关注的等待发生的IO事件
    • wlist 列表 存放关注的要主动处理的IO事件
    • xlist 列表 存放关注的出现异常要处理的IO
    • timeout 超时时间
  • 返回值:
    • rs 列表 rlist中准备就绪的IO
    • ws 列表 wlist中准备就绪的IO
    • xs 列表 xlist中准备就绪的IO

select 实现tcp服务

1.将关注的IO放入对应的监控类别列表

2.通过select函数进行监控

3.遍历select返回值列表,确定就绪IO事件

4.处理发生的IO事件

注意:

wlist中如果存在IO事件,则select立即返回给ws

处理IO过程中不要出现死循环占有服务端的情况

IO多路复用消耗资源较少,效率较高

 1 """
 2 重点代码
 3 
 4 思路分析:
 5 1.将关注的IO放入对应的监控类别列表
 6 2.通过select函数进行监控
 7 3.遍历select返回值列表,确定就绪IO事件
 8 4.处理发生的IO事件
 9 """
10 
11 from socket import *
12 from select import select
13 
14 # 创建一个监听套接字作为关注的IO
15 s = socket()
16 s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
17 s.bind(('0.0.0.0',8888))
18 s.listen(3)
19 
20 # 设置关注列表
21 rlist = [s]
22 wlist = []
23 xlist = [s]
24 
25 # 循环监控IO
26 while True:
27   rs,ws,xs = select(rlist,wlist,xlist)
28   # 遍历三个返回列表,处理IO
29   for r in rs:
30     # 根据遍历到IO的不同使用if分情况处理
31     if r is s:
32       c,addr = r.accept()
33       print("Connect from",addr)
34       rlist.append(c) # 增加新的IO事件
35     # else为客户端套接字就绪情况
36     else:
37       data = r.recv(1024)
38       # 客户端退出
39       if not data:
40         rlist.remove(r) # 从关注列表移除
41         r.close()
42         continue # 继续处理其他就绪IO
43       print("Receive:",data.decode())
44       # r.send(b'OK')
45       # 我们希望主动处理这个IO对象
46       wlist.append(r)
47 
48   for w in ws:
49     w.send(b'OK')
50     wlist.remove(w) # 使用后移除
51 
52   for x in xs:
53     pass
select tcp服务模型

位运算

定义 : 将整数转换为二进制,按二进制位进行运算

运算符号:

  • & 按位与
  • | 按位或
  • ^ 按位异或
  • << 左移
  • >> 右移
  • 14 --> 01110
  • 19 --> 10011
  • 14 & 19 = 00010 = 2 一0则0
  • 14 | 19 = 11111 = 31 一1则1
  • 14 ^ 19 = 11101 = 29 相同为0不同为1
  • 14 << 2 = 111000 = 56 向左移动低位补0
  • 14 >> 2 = 11 = 3 向右移动去掉低位

poll方法

p = select.poll()

  • 功能 : 创建poll对象
  • 返回值: poll对象

p.register(fd,event)   

  • 功能: 注册关注的IO事件
  • 参数:fd 要关注的IO

event 要关注的IO事件类型

常用类型:

  • POLLIN 读IO事件(rlist)
  • POLLOUT 写IO事件 (wlist)
  • POLLERR 异常IO (xlist)
  • POLLHUP 断开连接 

  p.register(sockfd,POLLIN|POLLERR)

p.unregister(fd)

  • 功能:取消对IO的关注
  • 参数:IO对象或者IO对象的fileno

events = p.poll()

  • 功能: 阻塞等待监控的IO事件发生
  • 返回值: 返回发生的IO

events格式 [(fileno,event),()....]

每个元组为一个就绪IO,元组第一项是该IO的fileno,第二项为该IO就绪的事件类型

poll_server 步骤:

  1. 创建套接字
  2. 将套接字register
  3. 创建查找字典,并维护
  4. 循环监控IO发生
  5. 处理发生的IO
 1 """
 2 尽量掌握
 3 
 4 思路分析:
 5 1. 创建套接字作为监控IO
 6 2. 将套接字register
 7 3. 创建查找字典,并维护(要时刻与注册IO保持一致)
 8 4. 循环监控IO发生
 9 5. 处理发生的IO
10 """
11 
12 from socket import *
13 from select import *
14 
15 # 创建套接字
16 s = socket()
17 s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
18 s.bind(('0.0.0.0',8888))
19 s.listen(3)
20 
21 # 创建poll对象关注s
22 p = poll()
23 
24 # 建立查找字典,用于通过fileno查找IO对象
25 fdmap = {s.fileno():s}
26 
27 # 关注s
28 p.register(s,POLLIN|POLLERR)
29 
30 # 循环监控
31 while True:
32   events = p.poll()
33   # 循环遍历发生的事件 fd-->fileno
34   for fd,event in events:
35     # 区分事件进行处理
36     if fd == s.fileno():
37       c,addr = fdmap[fd].accept()
38       print("Connect from",addr)
39       # 添加新的关注IO
40       p.register(c,POLLIN|POLLERR)
41       fdmap[c.fileno()] = c # 维护字典
42     # 按位与判定是POLLIN就绪
43     elif event & POLLIN:
44       data = fdmap[fd].recv(1024)
45       if not data:
46         p.unregister(fd) # 取消关注
47         fdmap[fd].close()
48         del fdmap[fd]  # 从字典中删除
49         continue
50       print("Receive:",data.decode())
51       fdmap[fd].send(b'OK')
poll 服务端程序

epoll方法

1.使用方法 : 基本与poll相同

  • 生成对象改为 epoll()
  • 将所有事件类型改为EPOLL类型

2.epoll特点:

  • epoll 效率比select poll要高
  • epoll 监控IO数量比select要多
  • epoll 的触发方式比poll要多 (EPOLLET边缘触发)
 1 """
 2 尽量掌握
 3 """
 4 
 5 from socket import *
 6 from select import *
 7 
 8 # 创建套接字
 9 s = socket()
10 s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
11 s.bind(('0.0.0.0',8888))
12 s.listen(3)
13 
14 # 创建epoll对象关注s
15 ep = epoll()
16 
17 # 建立查找字典,用于通过fileno查找IO对象
18 fdmap = {s.fileno():s}
19 
20 # 关注s
21 ep.register(s,EPOLLIN|EPOLLERR)
22 
23 # 循环监控
24 while True:
25   events = ep.poll()
26   # 循环遍历发生的事件 fd-->fileno
27   for fd,event in events:
28     print("亲,你有IO需要处理哦")
29     # 区分事件进行处理
30     if fd == s.fileno():
31       c,addr = fdmap[fd].accept()
32       print("Connect from",addr)
33       # 添加新的关注IO
34       # 将触发方式变为边缘触发
35       ep.register(c,EPOLLIN|EPOLLERR|EPOLLET)
36       fdmap[c.fileno()] = c # 维护字典
37     # 按位与判定是EPOLLIN就绪
38     # elif event & EPOLLIN:
39     #   data = fdmap[fd].recv(1024)
40     #   if not data:
41     #     ep.unregister(fd) # 取消关注
42     #     fdmap[fd].close()
43     #     del fdmap[fd]  # 从字典中删除
44     #     continue
45     #   print("Receive:",data.decode())
46     #   fdmap[fd].send(b'OK')
epoll 服务端程序
原文地址:https://www.cnblogs.com/maplethefox/p/11055941.html