1.在程序中一般有5中IO model
1.blocking IO 阻塞 IO
2.nonbiocking IO 非阻塞IO
3.IO multiplexing IO多路复用
4.signal driven IO 信号驱动IO(基本上不用)
5.asynchronous 异步IO
2.在进行IO操作时涉及到的对象和步骤,对于一个networkIO(这里我们以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)
3.同步、异步、堵塞、非堵塞的概念:
3.1同步是指提交一个任务后只能等待这个任务完成后才能执行下面的语句
3.2异步只管提交任务不等待这个任务执行完毕后就可以执行其他的事情
3.3 阻塞就是在这个指令下进行等待操作,无法进行下面的操作(recv、accept)
3.4非阻塞就是让·程序可以没有阻碍地执行下去
4.在进程或者线程执行过程中,如果遇到堵塞程序地执行过程会变成这样:
5.使用什么方法可以规避IO堵塞地(方法一就是设置setblocking=False)
6.非阻塞IO模型:
7.非阻塞模式地程序:以tcp服务为例:
如果设置非阻塞状态:accept即使没有接受到数据也会执行下一步操作:
import socket sk=socket.socket() sk.setblocking(False) sk.bind(('127.0.0.1',8080)) sk.listen() while True: conn,addr=sk.accept()
结果为
从结果可以看出如果accep没有接收到请求而进行下面地操作会报错。可以使用try 来进行解决 程序如下:
server端程序:
import socket sk=socket.socket() sk.setblocking(False) sk.bind(('127.0.0.1',8080)) sk.listen() while True: try: conn,addr=sk.accept() print('建立连接') msg=conn.recv(1024) print(msg) except BlockingIOError: pass
client端程序:
import socket import time sk=socket.socket() sk.connect(('127.0.0.1',8080)) while True: sk.send(b'hello') time.sleep(1) sk.send(b'python'
结果为
从结果我们可以看出他只打印第一次发送地结果。(虽然tcp协议知道要进行三次数据地发送,但是每一次都要从新进行数据地连接,导致三次都要从新开始,数据都是第一次地)
8.解决办法加一个列表:
server端
import socket sk=socket.socket() sk.setblocking(False) sk.bind(('127.0.0.1',8080)) sk.listen() conn_l=[] while True: try: conn,addr=sk.accept() print('建立连接') conn_l.append(conn) except BlockingIOError: for con in conn_l: msg=con.recv(1024) print(msg)
结果为
9.更改后地程序:
server端
import socket sk=socket.socket() sk.setblocking(False) sk.bind(('127.0.0.1',8080)) sk.listen() conn_l=[] del_l=[] while True: try: conn,addr=sk.accept() print('建立连接') conn_l.append(conn) except BlockingIOError: for con in conn_l: try: msg=con.recv(1024) if msg==b'': del_l.append(con) print(msg) except BlockingIOError:pass for con in del_l: conn_l.remove(con)
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'python') time.sleep(1) sk.send(b'hfhhf') sk.close()
10.终极程序:
server端程序:
import socket sk=socket.socket() sk.setblocking(False) #设置让堵塞状态变成非堵塞状态 sk.bind(('127.0.0.1',8080)) sk.listen() conn_l=[] #如果建立连接就把连接句柄放到列表中 del_l=[] #如果通讯结束就把句柄放到这个列表中 while True: try: conn,addr=sk.accept()#在非阻塞状态下如果没有人连我也会报错 print('建立连接',addr)#1.如果建立连接 conn_l.append(conn)#2.没有建立连接 except BlockingIOError: for con in conn_l:#2看有连接地地址,3没有连接地地址 try: msg=con.recv(1024)#在非阻塞状态下如果没有数据就会报错,2.1有信息,2.2没有信息 if msg==b'':#如果接受数据为这个则代表client结束通讯2.1前面 del_l.append(con)#2.1 continue#2.1 print(msg)#2.1 con.send(b'byebye')#2.1 except BlockingIOError:pass#2.2 for con in del_l:#2 con.close() conn_l.remove(con) del_l.clear()
client端程序:
import socket import time from threading import Thread def func(): sk=socket.socket() sk.connect(('127.0.0.1',8080)) sk.send(b'hello') msg=sk.recv(1024) print(msg) sk.close() for i in range(20): Thread(target=func).start()
11。非阻塞模型其实在编程过程中我们并不推荐:
非阻塞模式地优点就是可以节约堵塞地事件进行下面程序地执行,但是其缺点也是很大:循环调用recv蒋提高cpu的利用率,这也是我们在代码中使用时间睡眠的原因,否则在低配电脑上会出现卡机的情况。
在任务额完成的时候延迟反应大,因为没经过一段时间才去循环一次read操作,而任务可能在两次轮询之间的某一时间就完成了。这样会导致整数据的吞吐量降低。
12.解决方式使用多路复用(即select)方法:
13.在python程序中怎么显示:
import select import socket sk=socket.socket() sk.bind(('127.0.0.1',8080)) sk.setblocking(False) sk.listen() read_lst=[sk] #把监听到连接的数据放到列表中 while True: r_l,w_l,x_l=select.select(read_lst,[],[]) print(r_l)#查看返回的内容 print(sk)#发现和ret的第零个数据一样 for i in r_l:#遍地得到的数据 if i==sk:#如果数据是sk conn,addr=i.accept() print(conn) read_lst.append(conn) else: #如果数据是conn msg=i.recv(1024) if msg==b'': #当通信结束以后 i.close()#先把通信进行关闭 read_lst.remove(i)#然后再把地址删掉 continue print(msg) i.send(b'python')
14.IO多路复用:
windows上使用select ,操作系统会轮询每一个监听的项,看是否有读操作
但是linux上一般使用poll它可以监听的对象要比select多,但随着监听数量的增多,效率也会有所下降
和epoll 也是使用于linux,他又返回函数机制。
15.linux 系统上运行的程序:
from socket import * import selectors sel=selectors.DefaultSelector() def accept(server_fileobj,mask): conn,addr=server_fileobj.accept() sel.register(conn,selectors.EVENT_READ,read) def read(conn,mask): try: data=conn.recv(1024) if not data: print('closing ',conn) sel.unregister(conn) conn.close() return conn.send(data.upper()+b'ss') except Exception: print('closing',conn) sel.register(conn) conn.close() sk=socket() sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) sk.bind(('127.0.0.1',8080)) sk.setblocking(False)#把状态设置成非阻塞状态 sel.register(sk,selectors.EVENT_READ,accept)#相当于往select中都列表append了一个文件 while True:#句柄server_fileobj,并且绑定了一个回调函数accept events=sel.select()#用于检测所有的fileobj,时候完成wait和data的 for sel_obj,mask in events: callback=sel.obj.data callback(sel_obj.fileobj,mask)