阻塞IO(blocking IO)的特色:就是在IO执行的两个阶段(等待数据和拷贝数据两个阶段)都被block了。python
什么是阻塞呢?想象这种情形,好比你等快递,但快递一直没来,你会怎么作?有两种方式:服务器
很显然,你没法忍受第二种方式,不只耽搁本身的时间,也会让快递很想打你。
而在计算机世界,这两种情形就对应阻塞和非阻塞忙轮询。多线程
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)
clientapp
#!/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'))
运行结果:socket
"D:\Program Files (x86)\python36\python.exe" F:/python从入门到放弃/9.3/client.py >>>hello hello >>>nihao nihao >>>中国 中国 >>>亚运会 亚运会 >>>中国金牌数 中国金牌数 >>>
非阻塞式IO中,用户进程实际上是须要不断的主动询问kernel数据准备好了没有ide
非阻塞如何利用函数
server:spa
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)\python36\python.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
把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"))