阻塞IO
阻塞IO(blocking IO)的特点:就是在IO执行的两个阶段(等待数据和拷贝数据两个阶段)都被block了。
什么是阻塞呢?想象这种情形,比如你等快递,但快递一直没来,你会怎么做?有两种方式:
- 快递没来,我可以先去睡觉,然后快递来了给我打电话叫我去取就行了。
- 快递没来,我就不停的给快递打电话说:擦,怎么还没来,给老子快点,直到快递来。
很显然,你无法忍受第二种方式,不仅耽搁自己的时间,也会让快递很想打你。
而在计算机世界,这两种情形就对应阻塞和非阻塞忙轮询。
- 非阻塞忙轮询:数据没来,进程就不停的去检测数据,直到数据来。
- 阻塞:数据没来,啥都不做,直到数据来了,才进行下一步的处理
server
1 #!/usr/bin/env python3 2 #-*- coding:utf-8 -*- 3 ''' 4 Administrator 5 2018/9/3 6 ''' 7 import socket 8 9 sk=socket.socket() 10 sk.bind(('127.0.0.1',8080)) 11 sk.listen(3) 12 13 while 1: 14 conn,addr=sk.accept() 15 while 1: 16 data=conn.recv(1024) 17 print(data.decode('utf8')) 18 conn.sendall(data)
client
#!/usr/bin/env python3 #-*- coding:utf-8 -*- ''' Administrator 2018/9/3 ''' import socket sk=socket.socket() sk.connect(('127.0.0.1',8080)) while 1: inp=input(">>>") sk.sendall(inp.encode('utf8')) data=sk.recv(1024) print(data.decode('utf8'))
运行结果:
"D:Program Files (x86)python36python.exe" F:/python从入门到放弃/9.3/client.py >>>hello hello >>>nihao nihao >>>中国 中国 >>>亚运会 亚运会 >>>中国金牌数 中国金牌数 >>>
非阻塞型IO模型
非阻塞式IO中,用户进程其实是需要不断的主动询问kernel数据准备好了没有
非阻塞如何利用
- 吃满 CPU !
- 宁可用 while True ,也不要阻塞发呆!
- 只要资源没到,就先做别的事!
server:
1 #!/usr/bin/env python3 2 #-*- coding:utf-8 -*- 3 ''' 4 Administrator 5 2018/9/3 6 ''' 7 import socket,time 8 #非阻塞型IO模型 9 sk=socket.socket() 10 sk.bind(('127.0.0.1',8080)) 11 sk.listen(3) 12 13 sk.setblocking(False)#设置非阻塞。 IO模型发生了变化 14 print("start listen") 15 conn_list=[]#连接列表 16 while 1: 17 try: 18 conn, addr = sk.accept() 19 print("connet by",addr) 20 conn_list.append(conn) 21 conn.setblocking(False)#设置非阻塞。 IO模型发生了变化 22 # while 1: 23 # data=conn.recv(1024) 24 # print(data.decode('utf8')) 25 # conn.sendall(data) 26 except Exception as e: 27 print("没有接收到数据。警告:%s"%e) 28 time.sleep(3) 29 30 for conn in conn_list: 31 try: 32 data=conn.recv(1024) 33 if data: 34 print(data.decode('utf8')) 35 conn.sendall(data) 36 else: 37 print('close conn',conn) 38 conn.close() 39 conn_list.remove(conn) 40 print("还有在线的客户端个数:%s"%len(conn_list)) 41 except IOError: 42 pass
client:
1 #!/usr/bin/env python3 2 #-*- coding:utf-8 -*- 3 ''' 4 Administrator 5 2018/9/3 6 ''' 7 import socket 8 9 sk=socket.socket() 10 11 sk.connect(('127.0.0.1', 8080)) 12 while 1: 13 inp=input(">>>") 14 if inp != 'q': 15 sk.sendall(inp.encode('utf8')) 16 data=sk.recv(1024) 17 print("返回的数据%s"%data.decode('utf8')) 18 else: 19 sk.close() 20 print("关闭客户端") 21 break#跳出循环
运行结果:
"D:Program Files (x86)python36python.exe" F:/python从入门到放弃/9.3/server.py start listen 没有接收到数据。警告:[WinError 10035] 无法立即完成一个非阻止性套接字操作。 connet by ('127.0.0.1', 54241) 没有接收到数据。警告:[WinError 10035] 无法立即完成一个非阻止性套接字操作。 connet by ('127.0.0.1', 54242) 没有接收到数据。警告:[WinError 10035] 无法立即完成一个非阻止性套接字操作。 connet by ('127.0.0.1', 54243) 没有接收到数据。警告:[WinError 10035] 无法立即完成一个非阻止性套接字操作。 没有接收到数据。警告:[WinError 10035] 无法立即完成一个非阻止性套接字操作。 没有接收到数据。警告:[WinError 10035] 无法立即完成一个非阻止性套接字操作。 这是1个 没有接收到数据。警告:[WinError 10035] 无法立即完成一个非阻止性套接字操作。 没有接收到数据。警告:[WinError 10035] 无法立即完成一个非阻止性套接字操作。 这是2个 没有接收到数据。警告:[WinError 10035] 无法立即完成一个非阻止性套接字操作。 没有接收到数据。警告:[WinError 10035] 无法立即完成一个非阻止性套接字操作。 这是3个 没有接收到数据。警告:[WinError 10035] 无法立即完成一个非阻止性套接字操作。 没有接收到数据。警告:[WinError 10035] 无法立即完成一个非阻止性套接字操作。 没有接收到数据。警告:[WinError 10035] 无法立即完成一个非阻止性套接字操作。 close conn <socket.socket fd=216, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 54241)> 还有在线的客户端个数:2 没有接收到数据。警告:[WinError 10035] 无法立即完成一个非阻止性套接字操作。 close conn <socket.socket fd=232, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 54242)> 还有在线的客户端个数:1 没有接收到数据。警告:[WinError 10035] 无法立即完成一个非阻止性套接字操作。 close conn <socket.socket fd=248, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 54243)> 还有在线的客户端个数:0 没有接收到数据。警告:[WinError 10035] 无法立即完成一个非阻止性套接字操作。 Process finished with exit code 1
多路复用IO
把socket交给操作系统去监控,相当于找个代理人(select), 去收快递。快递到了,就通知用户,用户自己去取。
阻塞I/O只能阻塞一个I/O操作,而I/O复用模型能够阻塞多个I/O操作,所以才叫做多路复用
使用select函数进行IO请求和同步阻塞模型没有太大的区别,甚至还多了添加监视socket,以及调用select函数的额外操作,感觉效率更差。
但是,使用select以后最大的优势是用户可以在一个线程内同时处理多个socket的IO请求。用户可以注册多个socket,然后不断地调用select读取被激活的socket,
即可达到在同一个线程内同时处理多个IO请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。
epoll是目前Linux上效率最高的IO多路复用技术。
epoll是惰性的事件回调,惰性事件回调是由用户进程自己调用的,操作系统只起到通知的作用。
epoll实现并发服务器,处理多个客户端
1 import socket 2 import select 3 4 sk1=socket.socket() 5 sk1.bind(('127.0.0.1',8080)) 6 sk1.listen(3) 7 8 sk2=socket.socket() 9 sk2.bind(('127.0.0.1',8081)) 10 sk2.listen(3) 11 while 1: 12 r,w,e=select.select([sk1,sk2],[],[])#监听--------读 ,写 ,错误 13 for obj in r: 14 conn,address=obj.accept() 15 # obj.recv(1024) 16 conn.send("i am server...".encode('utf8'))
1 import socket 2 import time 3 4 sk=socket.socket() 5 6 7 while 1: 8 sk.connect(('127.0.0.1', 6667)) 9 print("ok") 10 sk.sendall(bytes("hello","utf8")) 11 time.sleep(2) 12 break
1 import socket 2 import select 3 sk=socket.socket() 4 sk.bind(("127.0.0.1",8800)) 5 sk.listen(5) 6 7 sk1=socket.socket() 8 sk1.bind(("127.0.0.1",6667)) 9 sk1.listen(5) 10 11 while True: 12 r,w,e=select.select([sk,sk1],[],[],5) 13 for i in r:#r 接受到的是绑定的两个socket 对象之一。是服务端的对象 conn 是这两个r对象连接的客户端的对象 14 # conn,add=i.accept() 15 # print(conn) 16 print("hello") 17 print('>>>>>>',r)
1 import socket 2 import time 3 4 sk=socket.socket() 5 6 7 while 1: 8 sk.connect(('127.0.0.1', 6667)) 9 print("ok") 10 sk.sendall(bytes("hello","utf8")) 11 time.sleep(2) 12 break
运行结果:
...........重复打印......... hello >>>>>> [<socket.socket fd=216, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 6667)>] hello >>>>>> [<socket.socket fd=216, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 6667)>] hello >>>>>> [<socket.socket fd=216, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 6667)>] ...................
select属于水平触发
触发方式:水平触发,边缘触发。
水平状态,有 就触发,没有就不触发 1触发 0不触发
边缘触发,有变化就触发,没有变化就不触发。0->1触发 1->0触发
1 #!/usr/bin/env python3 2 #-*- coding:utf-8 -*- 3 ''' 4 Administrator 5 2018/9/3 6 ''' 7 import socket 8 import select 9 10 sk=socket.socket() 11 sk.bind(('127.0.0.1',8800)) 12 sk.listen(5) 13 14 15 inp=[sk,] 16 while 1: 17 inputs,outputs,errors=select.select(inp,[],[],5) 18 for obj in inputs: 19 if obj==sk: 20 conn,addr=obj.accept() 21 print(conn) 22 inp.append(conn) 23 # data=conn.recv(1024) 24 # print(data.decode('utf8')) 25 # conn.sendall(data) 26 else: 27 data = obj.recv(1024) 28 print(data.decode('utf8')) 29 inps=input("回答%s>>>"%inp.index(obj)) 30 obj.sendall(inps.encode("utf8"))
客户端:
1 #!/usr/bin/env python3 2 #-*- coding:utf-8 -*- 3 ''' 4 Administrator 5 2018/9/3 6 ''' 7 import socket 8 import time 9 10 sk=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 11 12 sk.connect(('127.0.0.1', 8800)) 13 while 1: 14 inp=input(">>>").strip() 15 sk.sendall(bytes(inp,"utf8")) 16 data=sk.recv(1024) 17 print(str(data,"utf8"))
改进::: 当有用户退出时,不影响服务端的聊天通信::
1 #!/usr/bin/env python3 2 #-*- coding:utf-8 -*- 3 ''' 4 Administrator 5 2018/9/3 6 ''' 7 import socket 8 import select 9 10 sk=socket.socket() 11 sk.bind(('127.0.0.1',8800)) 12 sk.listen(5) 13 14 15 inp=[sk,] 16 while 1: 17 inputs,outputs,errors=select.select(inp,[],[],5) 18 for obj in inputs: 19 if obj==sk: 20 conn,addr=obj.accept() 21 print(conn) 22 inp.append(conn) 23 # data=conn.recv(1024) 24 # print(data.decode('utf8')) 25 # conn.sendall(data) 26 else: 27 try: 28 data = obj.recv(1024)#如果断开联系,则把该对象从inp列表中删除 29 except Exception: 30 inp.remove(obj) 31 if obj in inp:#筛选一下 32 print(data.decode('utf8')) 33 inps=input("回答%s>>>"%inp.index(obj)) 34 obj.sendall(inps.encode("utf8"))