原文:https://blog.csdn.net/historyasamirror/article/details/5778378
非常优秀的一片博文
对于一个 网络IO (以read为例),他会涉及到两个系统对象,一个是调用这个IO的process(or thread),另一个就是系统内核(kernel)。当一个read操作发生时,他必须要经历过两个阶段: 1、等待数据准备(waiting for the data to be ready)===>将数据从内核拷贝到进程中(copying the data from the kernel to the process)
blocking IO(阻塞IO)
在linux中,默认情况下所有的socket都是blocking,一个典型的读操作大概就是这样的:
进程调用recvfrom命令 ===>调用系统指令查询====>是否有数据报准备=====>数据报准备好 ====>复制数据报====>复制完成=====>进程接收数据报(结束阻塞)。blocking IO 的特点就是在IO执行的两个阶段都被阻塞了(block,即此时cpu处于等待状态,会切走处理其他线程、进程任务)
non-blocking IO (非阻塞IO)
我么可以通过设置setblocking的参数为False为,默认或者不写都默认为阻塞同步;来改变其工作状态为非阻塞状态,但依旧是非阻塞同步;在这种情况下,我们执行读操作,流程大致如下:
从上图中可以看出,在应用下的某一个进程或者线程发出read操作时,该进程便会立马操作系统索要数据报,若数据报还未准备好,此时kernel并不会阻塞用户进程,而是向用户返回一个error(在python里面被称作是BlockingIOError)。但是从用户角度来说,发起一个read操作后,并需要等待数据准备好,而是需要立马返回数据结果,当用户进程判断返回时error时,将会再次向操作系统索要数据报,直到kerenel中的数据已经准备好了,并且再次接受到了system call ,那么他就会拷贝数据报到用户内存,然后结束读操作。在这个过程,用户进程需要不停存文操作系统数据是否已准备好,会占用大量CPU
#coding=utf-8
import socket
sever = socket.socket()
sever.bind(("127.0.0.1",52334))
#设置socket为非阻塞模式,默认参数为True,或者不写,皆默认为阻塞连接
sever.setblocking(False)
sever.listen(5)
sock_list = []
info_list = []
while True:
try:
sock, add = sever.accept() #若此时sever不可读(即客户端无连接),系统将会报错为BlockingIOError
sock_list.append(sock)
print("11")
except BlockingIOError:
#当没有客户端输入处理下面的写入和输出操作
for conn in sock_list[:]:
try:
# 若此时kernel无数据报,操作系统将会报错为BlockingIOError,若客户端主动退出将会报ConnectionResetError
data = conn.recv(1024)
print(data.decode("utf-8"))
info_list.append((conn,data))
except BlockingIOError:
pass
except ConnectionResetError:
#若客户端主动退出,则关闭其连接,并且从socket对象中删除它
conn.close()
sock_list.remove(conn)
for single_info in info_list[:]:
try:
print(single_info)
#若此时客户端主动退出将会报ConnectionResetError,若kernel此时无法写入数据,操作系统将会报错为BlockingIOError
single_info[0].send(single_info[1].upper())
info_list.remove(single_info)
except BlockingIOError:
pass
except ConnectionResetError:
#若客户端主动退出,则关闭其连接,并且从socket对象中删除它
single_info[0].close()
info_list.remove(single_info)
服务端
客户端
#coding=utf-8
import socket
def client():
while True:
client = socket.socket()
client.connect(("127.0.0.1",52334))
msg = input(">>>>").strip()
# msg = "一☞??"
if not msg:
client.close()
break
client.send(msg.encode("utf-8"))
print(client.recv(1024).decode("utf-8"))
if __name__ == '__main__':
client()
IO multiplxeing(多路复用)
模块使用selcet。它的好处在于一个线程可以同时处理多个多个网络连接的IO。它的基本原理就是不断的询问所负责的所有的socket,是否可读、是否可写,如果是,便返回给应用进程执行相关操作,否则就会一直阻塞,直到有可读可写操作出现。流程如下
当用户进程导入了select模块后,并且调用了select函数后,那么此时整个用户进程便会被阻塞,同时,kernel会“监视”所有的select负责的socket,当其中任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作将kernel拷贝到用户进程。
这个图和blocking IO的图其实并没有太大的不同,事实上,还更差一些。因为这里需要使用两个system call (select 和 recvfrom),而blocking IO只调用了一个system call (recvfrom)。但是,用select的优势在于它可以同时处理多个connection。(多说一句。所以,如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。)
在IO multiplexing Model中,实际中,对于每一个socket,一般都设置成为non-blocking,但是,如上图所示,整个用户的process其实是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。
服务端:
#coding=utf-8
import select, socket
sever = socket.socket()
sever.bind(("127.0.0.1",52334))
#设置socket为非阻塞模式,默认参数为True,或者不写,皆默认为阻塞连接
sever.setblocking(False)
sever.listen(5)
sever.setblocking(False)
read_list = [sever]
write_list = []
info = {}
while True:
readable,writable,_ = select.select(read_list,write_list,[])
"""select(rlist, wlist, xlist, timeout=None)
该操作支持多个文件操作符的异步I/O 注意:在windows上,仅仅支持socket模块;而在Unix上,支持所有文件操作符.
select(rlist, wlist, xlist[, timeout]) ->返回 (rlist, wlist, xlist)
直到一种或多种文件操作符处于某种I/O操作前,保持wait状态(阻塞状态),前三个参数是将要被阻塞的文件操作标识符的序列;
rlist :在读操作准备好之前处于堵塞状态 wrlist:在写操作准备好之前处于堵塞状态 xlist:等待一个异常状况
如果只有一种状况需要处理,其他的参数可以写[],或者位pass。一个文件操作符可以是socket,也可以是一个文件对象。
第四个参数是一可选参数特定处理以秒为单位的的时间等待,如果不写或者位None,那则一直等待
该函数返回一个元组,包含三个列表 可读列表 ,可写列表 异常列表
"""
for conn in readable:
if conn == sever:
try:
#可能会没有客户端来连接,会报错BlockingIOError
sock, add = sever.accept()
read_list.append(sock)
except BlockingIOError:
pass
else:
try:
# 可能会没有数据报,会报错BlockingIOError,又或者客户端主动断开连接
data = conn.recv(1024)
print(data.decode("utf-8"))
write_list.append(conn)
info[conn] = data
print(info)
except BlockingIOError:
pass
except ConnectionResetError:
conn.close()
read_list.remove(conn)
for conn in writable:
try:
# 可能会没有数据报,会报错BlockingIOError,又或者客户端主动断开连接
conn.send(info[conn].upper())
info.pop(conn)
write_list.remove(conn)
except BlockingIOError:
pass
except ConnectionResetError:
conn.close()
write_list.remove(conn)
info.pop(conn)
客户端:
#coding=utf-8
import socket
def client():
while True:
client = socket.socket()
client.connect(("127.0.0.1",52334))
msg = input(">>>>").strip()
# msg = "一☞??"
if not msg:
client.close()
break
client.send(msg.encode("utf-8"))
print(client.recv(1024).decode("utf-8"))
if __name__ == '__main__':
client()
Asynchronous IO 异步IO
用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。
四种模式比较
An asynchronous I/O operation does not cause the requesting process to be blocked;
两者的区别就在于synchronous IO做”IO operation”的时候会将process阻塞。按照这个定义,之前所述的blocking IO,non-blocking IO,IO multiplexing都属于synchronous IO。有人可能会说,non-blocking IO并没有被block啊。这里有个非常“狡猾”的地方,定义中所指的”IO operation”是指真实的IO操作,就是例子中的recvfrom这个system call。non-blocking IO在执行recvfrom这个system call的时候,如果kernel的数据没有准备好,这时候不会block进程。但是,当kernel中数据准备好的时候,recvfrom会将数据从kernel拷贝到用户内存中,这个时候进程是被block了,在这段时间内,进程是被block的。而asynchronous IO则不一样,当进程发起IO 操作之后,就直接返回再也不理睬了,直到kernel发送一个信号,告诉进程说IO完成。在这整个过程中,进程完全没有被block。