IO模型
这里比较难理解:参考http://www.cnblogs.com/Eva-J/articles/8324837.html
同步:提交一个任务之后,要等待这个任务执行完毕
异步:只管提交任务,不等待这个任务执行完毕就可以做其他事情
阻塞:recv,recvfrom,accept,程序停了。一个程序从运行状态——阻塞状态——就绪后还需要重新排队,等待操作系统的调用
非阻塞:
再说一下IO发生时涉及的对象和步骤。对于一个network IO (这里我们以read举例),它会涉及到两个系统对象,一个是调用这个IO的process (or thread),另一个就是系统内核(kernel)。当一个read操作发生时,该操作会经历两个阶段:
#1)等待数据准备 (Waiting for the data to be ready) #2)将数据从内核拷贝到进程中(Copying the data from the kernel to the process)
当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据。对于network io来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),这个时候kernel就要等待足够的数据到来。
而在用户进程这边,整个进程会被阻塞。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来。
可以这样理解:陷入阻塞就相当于献出了CPU的运行权力。
非阻塞模型
sever端
import socket sk=socket.socket() sk.setblocking(False)#这里默认是Ture,也就是说允许存在阻塞,那么设置为fasle就表示不存在阻塞 sk.bind(('127.0.0.1',8080)) sk.listen() while 1: conn,addr=sk.accept()
clent端
import socket sk=socket.socket() sk.connect(('127.0.0.1',8080)) sk.send(b'hello') sk.close()
但是此时sever端会报错,因为sever端此时不允许阻塞,但是sever端连接不上client端只能阻塞,所以就会报错
Traceback (most recent call last): File "F:/python/python学习/人工智能/第一阶段day2/sever.py", line 8, in <module> conn,addr=sk.accept() File "D:\anoconda\lib\socket.py", line 212, in accept fd, addr = self._accept() BlockingIOError: [WinError 10035] 无法立即完成一个非阻止性套接字操作。 Process finished with exit code 1
那么就需要采用try方法
sever
import socket sk=socket.socket() sk.setblocking(False)#这里默认是Ture,也就是说允许存在阻塞,那么设置为fasle就表示不存在阻塞 sk.bind(('127.0.0.1',8080)) sk.listen() while 1: try: conn,addr=sk.accept() print('建立连接') print(conn.recv(1024)) except BlockingIOError: pass
client
import socket import time sk=socket.socket() sk.connect(('127.0.0.1',8080)) sk.send(b'hello') time.sleep(1) sk.send(b'bye') sk.close()
此时结果是
建立连接 b'hello' Process finished with exit code -1
只能接收到第一个send这是因为当接收第一个send后,client睡眠了1秒,此时这1秒内,sever端接收不到消息,就判断为阻塞,但是由于阻塞为false,那么就执行except条件,然后等到执行到第二个send的时候,sever端无法和client端再建立连接,所以无法接收到第二个send的信息。那么:
最终结果
sever
import socket sk=socket.socket() sk.setblocking(False)#这里默认是Ture,也就是说允许存在阻塞,那么设置为fasle就表示不存在阻塞 sk.bind(('127.0.0.1',8080)) sk.listen() l1=[]#来保存来所有来请求sever端的conn连接 del_l=[]#来保存与sever端断开的conn连接 while 1: try: conn,addr=sk.accept() print('建立连接:',addr) l1.append(conn)#利用列表将接收到没有执行的conn都先收集起来, except BlockingIOError: for con in l1:#放到下面这里执行 try: msg = con.recv(1024) if msg==b'': del_l.append(con) continue else: print(msg) con.send(b'bye') except BlockingIOError:pass for con in del_l: con.close() l1.remove(con) del_l.clear()
client
import socket import time from threading import Thread def talk(): sk = socket.socket() sk.connect(('127.0.0.1', 8080)) sk.send(b'hello') time.sleep(1) print(sk.recv(1024)) sk.close() for i in range(20): Thread(target=talk).start()
注意上面的代码以后会很常用,因为在未来在使用异步的过程中是不可能出现阻塞的,所以需要多练习。
但是注意上面的代码中使用了while循环,但是while循环非常占用内存资源,所以看下面的方法。
多路复用IO(IO multiplexing)
原理:
第一步
sever
import socket import select sk=socket.socket() sk.bind(('127.0.0.1',8080)) sk.setblocking(False) sk.listen() #这里一定要树立一个观念就是,sk监听的是连接,而conn监听的是收发信息 read_list=[sk] r_list,w_list,x_list=select.select(read_list,[],[]) for i in r_list: conn,addr=i.accept() print(conn) print(sk)
client
import socket import time from threading import Thread def talk(): sk = socket.socket() sk.connect(('127.0.0.1', 8080)) sk.send(b'hello') time.sleep(1) print(sk.recv(1024)) sk.close() for i in range(20): Thread(target=talk).start()
sever端结果
<socket.socket fd=452, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 64695)>#这就是conn <socket.socket fd=464, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080)>
进阶
sever
import socket import select sk=socket.socket() sk.bind(('127.0.0.1',8080)) sk.setblocking(False) sk.listen() #这里一定要树立一个观念就是,sk监听的是连接,而conn监听的是收发信息 read_list=[sk] while 1: r_list,w_list,x_list=select.select(read_list,[],[]) for i in r_list: if i is sk: conn,addr=i.accept() read_list.append(conn) else: msg=i.recv(1024) if msg==b'':#自己在运行过程没有出现b'',但是没有这行代码的话,i.send(b'bye')无法正常运行 i.close() read_list.remove(i) continue print(msg) i.send(b'bye')
client
import socket import time from threading import Thread def talk(): sk = socket.socket() sk.connect(('127.0.0.1', 8080)) sk.send(b'hello') time.sleep(1) print(sk.recv(1024)) sk.close() for i in range(20): Thread(target=talk).start()
IO多路复用,这是windows内部的select机制完成的。select实际上是在操作系统的帮助下循环这个列表,查看每一项是否有可读事件。
注意:select机制在widows系统和linux系统上都有;但是linux系统上还有pool和epool(mac系统)
select:
pool:可以监听的对象比select机制下要多
以上两种方式,都是操作系统轮询(类似于for循环)每一个被监听的项,看是否有读操作;但是随着监听项的增加,导致效率降低
epool:相当于在每一个监听的项上绑定了一个回调函数,那么一旦该项接收到信息,回调函数会立即反馈给用户,但是这个功能windows上没有