网络IO阻塞

原文: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。
 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值