python入门第三十五天--事件驱动模型(补)练习理解

阻塞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)\python36\python.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)\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

 

多路复用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'))
server
 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
client

 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"))

 

 







posted @ 2018-09-03 11:12  巨兽~墨菲特  阅读(666)  评论(0编辑  收藏  举报