day42---几种network IO模型

I/O模型的简介

前置知识

"""
任务的提交方式
同步:就是发生功能调用时,需要等待任务的返回结果,期间不做任何操作。
异步:无需等待任务的返回结果,继续执行程序,任务的返回结果由异步回调机制返回。
任务的提交结果
阻塞:发生功能调用时(如I/O操作),会将当前线程挂起,直至得到返回结果,再将线程激活。
非阻塞:与阻塞的概念是相反的
"""

这里研究的I/O模型都是针对网络的I/O

5种I/O模型

"""
blocking I/O         阻塞I/O
nonblocking I/O      非阻塞I/O
I/O multiplexing     I/O多路复用
signal driven I/O    信号驱动I/O(不需要掌握)
asynchronous I/O     异步I/O
"""

对于一个Network I/O (以read为例),它会涉及到两个系统对象。一是调用这个I/O的process(or thread),另一个是系统内核(kernel)。当一个read操作发生时,该操作会经历两个阶段。

(1)等待数据准备(waitting for data to ready)
(2)将数据从内核拷贝到进程中(copying the data from thekernel to the process)

阻塞I/O模型

在linux中,默认情况下所有的socket都是blocking。

blocking IO的特点就是在IO执行的两个阶段(等待数据和拷贝数据两个阶段)都被block了

import socket

IP_ADDRESS = ('0.0.0.0', 9090)

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(IP_ADDRESS)
sock.listen(5)

while True:
    conn, addr = sock.accept()
    while True:
        try:
            msg = conn.recv(1024)
            if not msg: break
            print(msg)
            conn.send(msg.upper())
        except ConnectionResetError as e:
            break
    conn.close()

在服务端开设多进程或者多线程 进程池线程池 其实还是没有解决IO问题。该等的地方还是得等 ,没有规避
只不过多个人等待的彼此互不干扰。

非阻塞I/O模型

在Linux下,可以设置socket使其变为nonblocking。

在非阻塞式IO中,用户进程其实是需要不断的主动询问kernel数据准备好了没有。

"""
服务端
"""
import socket
import time

IP_ADDRESS = ('0.0.0.0', 9090)

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(IP_ADDRESS)
sock.listen(5)

# 将network IO由阻塞变为非阻塞
sock.setblocking(False)
read_list = []
del_list = []

while True:
    try:
        conn, addr = sock.accept()
        read_list.append(conn)
    except BlockingIOError as e:
        """
        非阻塞模式下,没有阻塞状态,若没有连接进来会报错
        """
        # time.sleep(0.01)
        for conn in read_list:
            try:
                recv_data = conn.recv(1024)
                if not recv_data:
                    conn.close()
                    del_list.append(conn)
                    continue
                print(recv_data.decode('utf-8'))
                conn.send(recv_data.upper())
            except BlockingIOError as e:
                continue
            except ConnectionResetError as e:
                conn.close()
                del_list.append(conn)
        """
        去掉无用的连接
        """
        for conn in del_list:
            read_list.remove(conn)
        read_list.clear()
"""
客户端
"""
import socket

IP_ADDRESS = ('182.92.59.34', 9090)

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect_ex(IP_ADDRESS)

while True:
    # msg = input('please enter the message>>>:').strip()
    # if not msg: continue
    client.send(b'hello world!')
    back_msg = client.recv(1024)
    print(back_msg.decode('utf-8'))

但是非阻塞IO模型绝不被推荐。

因为长时间占用着CPU并且不干活 让CPU不停的空转,我们实际应用中也不会考虑使用非阻塞IO模型。

I/O多路复用

"""
当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。 这个图和blocking IO的图其实并没有太大的不同,事实上还更差一些。因为这里需要使用两个系统调用(select和recvfrom),而blocking IO只调用了一个系统调用(recvfrom)。但是,用select的优势在于它可以同时处理多个connection。
"""
强调:
1. 如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。
2. 在多路复用模型中,对于每一个socket,一般都设置成为non-blocking,但是,如上图所示,整个用户的process其实是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。

结论

select的优势在于可以处理多个连接,不适用于单个连接

"""
服务端
"""
import socket
import select

IP_ADDRESS = ('0.0.0.0', 9090)

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(IP_ADDRESS)
sock.listen(5)

sock.setblocking(False)
read_list = [sock, ]
write_list = []
wdata = {}

while True:
    r_list, w_list, x_list = select.select(read_list, write_list, [])
    for item in r_list:
        """
        针对不同的对象,做不同的处理,r_list里面可能是socket对象,也可能是conn对象
        """
        if item is sock:
            conn, addr = item.accept()
            """将conn加入到监管队列中去"""
            read_list.append(conn)
        else:
            try:
                msg = item.recv(1024)
                if not msg:
                    item.close()
                    read_list.remove(item)
                    continue
                print(msg.decode('utf-8'))
                write_list.append(item)
                wdata[item] = msg.upper()
            except Exception:
                item.close()
                read_list.remove(item)

    for item in w_list:
        item.send(wdata[item])
        write_list.remove(item)
        wdata.pop(item)
        
"""
客户端
"""
import socket

IP_ADDRESS = ('182.92.59.34', 9090)

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect_ex(IP_ADDRESS)

while True:
    # msg = input('please enter the message>>>:').strip()
    # if not msg: continue
    client.send(b'hello world!')
    back_msg = client.recv(1024)
    print(back_msg.decode('utf-8'))

总结:

"""
监管机制其实有很多
select机制  windows linux都有

poll机制    只在linux有   poll和select都可以监管多个对象 但是poll监管的数量更多

上述select和poll机制其实都不是很完美 当监管的对象特别多的时候
可能会出现 极其大的延时响应

epoll机制   只在linux有
	它给每一个监管对象都绑定一个回调机制
	一旦有响应 回调机制立刻发起提醒

针对不同的操作系统还需要考虑不同检测机制 书写代码太多繁琐
有一个人能够根据你跑的平台的不同自动帮你选择对应的监管机制
selectors模块
"""

异步I/O

"""
异步IO模型是所有模型中效率最高的 也是使用最广泛的
相关的模块和框架
	模块:asyncio模块
	异步框架:sanic tronado twisted
		速度快!!!
"""
import threading
import time
import asyncio


async def hello():
    print(f'hello world {threading.current_thread().getName()}')
    """使用sleep来模拟IO操作"""
    time.sleep(1)
    print(f'hello world {threading.current_thread().getName()}')


loop = asyncio.get_event_loop()
task = [hello(), hello()]
loop.run_until_complete(asyncio.wait(task))
loop.close()

注意:

"@coroutine" decorator is deprecated since Python 3.8, use "async def" instead

I/O模型的比较分析

"""
经过上面的介绍,会发现non-blocking IO和asynchronous IO的区别还是很明显的。在non-blocking IO中,虽然进程大部分时间都不会被block,但是它仍然要求进程去主动的check,并且当数据准备完成以后,也需要进程主动的再次调用recvfrom来将数据拷贝到用户内存。而asynchronous IO则完全不同。它就像是用户进程将整个IO操作交给了他人(kernel)完成,然后他人做完后发信号通知。在此期间,用户进程不需要去检查IO操作的状态,也不需要主动的去拷贝数据。
"""

selectors模块

select,poll,epoll

猜你喜欢

转载自www.cnblogs.com/surpass123/p/12795055.html