• I/O模型


    目录:

      IO模型
        阻塞IO
        非阻塞IO
        IO多路复用
        异步IO

    总结:

    1.阻塞IO模型
        多线程 多进程 线程池 进程池  全是阻塞IO
    2.非阻塞IO
        协程是一种非阻塞IO
        1.setblocking(False) 将阻塞修改为非阻塞
        2.一旦是非阻塞 在执行accept recv send 就会立马尝试读写数据 一旦数据没准备好就抛异常
        3.捕获异常
        4.如果没有异常说明数据准备好了 直接处理
        5.捕获到异常 那就做别的事情
    
        可以实现单线程并发的效果,但会大量占用CPU资源
    
    3.多路复用
        将所有连接交给select来管理  管什么? 管哪个连接可以被处理
        作为处理任务的一方事情变少了  不需要重复不断的问操作系统拿数据  而是等待select返回需要处理的连接
        等待则意味着select是阻塞的
    
        一.创建连接 和管理连接
        1.创建服务器socket对象
        2.将服务器对象交给select来管理
        3.一旦有客户端发起连接 select将不在阻塞
        4.select将返回一个可读的socket对象(第一次只有服务器)
        5.服务器的可读代表有连接请求 需要执行accept  返回一个客户端连接conn  由于是非阻塞 不能立即去recv
        6.把客户端socket对象也交给select来管理  将conn加入两个被检测的列表中
    
        7.下一次检测到可读的socket 可能是服务器 也可能客户端 所以加上判断  服务器就accept 客户端就recv
        8.如果检测到有可写(可以send就是系统缓存可用)的socket对象 则说明可以向客户端发送数据了
        7 和 8 执行顺序不是固定的
    
        二.处理数据收发
        两个需要捕获异常的地方
        1.recv  执行第7步 表示可以读 为什么异常 只有一种可能客户端断开连接
            还需要加上if not 判断是否有数据  ;linux下 对方下线不会抛出异常 会收到空消息
        2.send  执行第8步 表示可以写 为什么异常 只有一种可能客户端断开连接
    
    4.异步IO
       不仅仅指网络IO,也包括本地IO 非阻塞IO 和 多路复用 解决都是网络IO的阻塞问题 本地IO 可以通过子线程 或子进程 来避免阻塞 但是对子线程或子进程而言 依然会阻塞 最终的解决方案就是协程 asyncio 该模快实现异步IO 内部使用协程实现
     

    IO模型:

    网络IO:明显阻塞发生在服务端的accept和recv,IO主要是在wait data 和copy data
        recv/accept:
            wait data:等待客户端产生数据——》客户端OS--》网络--》服务端操作系统缓存
            copy data:由本地操作系统缓存中的数据拷贝到应用程序的内存中
        send:
            copy data

    图 例:

     阻塞IO:

    线程池实现阻塞IO:阻塞,效率低
    
    服务端:
     1 from concurrent.futures import ThreadPoolExecutor
     2 import socket
     3 
     4 server = socket.socket()
     5 # 重用端口
     6 server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
     7 
     8 server.bind(("127.0.0.1",9999))
     9 
    10 server.listen(5)
    11 
    12 # 线程池
    13 pool = ThreadPoolExecutor(3)
    14 
    15 
    16 def data_handler(conn):
    17     print("一个新连接..")
    18     while True:
    19         data = conn.recv(1024)
    20         conn.send(data.upper())
    21 
    22 while True:
    23     conn,addr = server.accept()
    24     # 切到处理数据的任务去执行
    25     pool.submit(data_handler,conn)
    客户端:
     1 import socket
     2 
     3 c = socket.socket()
     4 
     5 c.connect(("127.0.0.1",9999))
     6 
     7 while True:
     8     msg = input(">>>:")
     9     if not msg:continue
    10     c.send(msg.encode("utf-8"))
    11     data = c.recv(1024)
    12     print(data.decode("utf-8"))
    View Code 

    非阻塞IO:

    通过setblocking(False)设置非阻塞:
      程序会一直循环向操作系统要数据,造成无效占用
      1、对cpu的无效占用率过高
      2、不能即时反馈客户端的信息
    服务端:
     1 from socket import *
     2 import time
     3 server = socket(AF_INET, SOCK_STREAM)
     4 server.bind(('127.0.0.1',8080))
     5 server.listen(5)
     6 # 设置阻塞为False
     7 server.setblocking(False)
     8 conn_l=[]#连接的客户端
     9 while True:
    10     try:#客户端异常断开
    11         print('总连接数[%s]' % len(conn_l))
    12         conn,addr=server.accept()#是在向操作系统发请求,要连接
    13         conn_l.append(conn)#连接上添加到列表
    14     except BlockingIOError:
    15         del_l=[]#存储断开连接的客户端
    16         for conn in conn_l:
    17             try:#收不到数据
    18                 data=conn.recv(1024)
    19                 if len(data) == 0:
    20                     # 如果没收到数据,删除连接
    21                     del_l.append(conn)
    22                     continue
    23                 conn.send(data.upper())
    24             except BlockingIOError:
    25                 pass
    26             except ConnectionResetError:
    27                del_l.append(conn)
    28         #循环删除异常的客户端
    29         for conn in del_l:
    30             conn_l.remove(conn)
    客户端:
     1 from socket import *
     2 import os
     3 
     4 client=socket(AF_INET,SOCK_STREAM)
     5 client.connect(('127.0.0.1',8080))
     6 
     7 while True:
     8     msg='%s say hello' %os.getpid()
     9     client.send(msg.encode('utf-8'))
    10     data=client.recv(1024)
    11     print(data.decode('utf-8'))
    View Code 

    IO多路复用:

    服务端:
     1 from concurrent.futures import ThreadPoolExecutor
     2 import socket
     3 import select
     4 # select 帮你从一堆连接中找出来需要被处理的连接
     5 
     6 server = socket.socket()
     7 # 重用端口
     8 server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
     9 
    10 server.bind(("192.168.11.210",9999))
    11 
    12 server.listen(5)
    13 
    14 # 设置是否为阻塞 默认阻塞
    15 server.setblocking(False)
    16 
    17 def data_handler(conn):
    18     print("一个新连接..")
    19     while True:
    20         data = conn.recv(1024)
    21         conn.send(data.upper())
    22 
    23 # 需要检测的 是否可读取的列表  (recv就是一个读取操作)
    24 rlist = [server,]
    25 # 需要检测的 是否写入的列表  (send就是写入操作)
    26 wlist = []
    27 
    28 # 需要发送的数据 目前是因为 我们要把接收的数据在发回去 所以搞了这个东西 正常没有这种需求
    29 # 目前客户端与服务器端 交互 是必须客户端发送数据 服务器端才能返回数据   正常没有这种需求
    30 dic = {}
    31 
    32 
    33 while True: # 用于检测需要处理的连接 需要不断检测 所以循环
    34     # rl目前可读的客户端列表  wl目前可写的客户端列表
    35     rl,wl,xl = select.select(rlist,wlist,[]) # select默认阻塞 阻塞到任意一个连接可以被处理
    36     print(len(rl))
    37     # 处理可读的socket
    38     for c in rl:
    39         # 无论是客户端还是服务器只要可读就会执行到这里
    40         if c == server:
    41             # 接收客户端的连接请求 (一个读操作)
    42             conn,addr = c.accept()
    43             # 将新连接也交给select来检测
    44             rlist.append(conn)
    45         else:# 不是服务器 就是客户端 客户端可读 可以执行recv
    46             try:
    47                 data = c.recv(1024)
    48                 if not data:
    49                     c.close()
    50                     rlist.remove(c)
    51                 print("%s 发送 %s" % (c,data.decode("utf-8")))
    52                 # 给客户端发送数据 前要保证目前可以发送 将客户端加入检测列表
    53                 wlist.append(c)  # 正常开发中 不可能必须客户端发送数据过来后 才能 给客户端发送
    54                                  # 所以这个添加到检测列表的操作 应该建立连接后立即执行
    55                 # 要发送的数据
    56                 dic[c] = data
    57             except ConnectionResetError:
    58                 # 客户端关闭连接
    59                 c.close()
    60                 rlist.remove(c)
    61     # 处理可写的socket
    62     for c in wl:
    63         print(c)
    64         try:
    65             c.send(dic[c].upper())
    66             # 删除数据
    67             dic.pop(c)
    68             # 从检测列表中删除已发送完成的客户端
    69             wlist.remove(c)
    70         except ConnectionResetError:
    71             c.close() # 关闭连接
    72             dic.pop(c) # 删除要发送的数据
    73             wlist.remove(c) # 从待检测的列表中删除
    74         except BlockingIOError:#可能缓存满了 发不了
    75             pass 
    客户端:
     1 import socket
     2 
     3 c = socket.socket()
     4 
     5 c.connect(("192.168.11.210",9999))
     6 
     7 while True:
     8     msg = input(">>>:")
     9     if not msg:continue
    10     c.send(msg.encode("utf-8"))
    11     data = c.recv(1024)
    12     print(data.decode("utf-8"))
    View Code 

    异步IO:

    模拟异步IO:
    import asyncio
    asyncio.coroutine()
    from concurrent.futures import  ThreadPoolExecutor
    
    def task():
        print("read start")
        with open(r"D:day40多路复用,降低CPU占用服务器.py",encoding="utf-8") as f:
            text = f.read()
            # f.write()
        print("read end")
        return text
    
    def fin(f):
        print("fin")
        print(f.result())
    
    pool = ThreadPoolExecutor(1)
    future = pool.submit(task)
    future.add_done_callback(fin)
    
    print("主 over")
    # 这种方式看起来像是异步IO 但是对于子线程而言不是
    # 在子线程中 执行read 是阻塞的 以为CPU必须切走 但是不能保证切到当前程序的其他线程
    # 想要的效果就是 在执行read 是不阻塞 还能干其他活   谁能实现  只有协程
    # asyncio 内部是使用的是协程
  • 相关阅读:
    python-异常处理总结
    python-笔记-内置函数
    python练习题--计算总分平均分操作excel
    python-笔记(操作excel)
    python -加密(MD5)
    jmeter-http信息头管理器
    python-判断一个字符串是不是小数
    [Python] [Django] Django将post请求变成get
    离线安装rabbitmq
    python第三方包的几种安装方式
  • 原文地址:https://www.cnblogs.com/xuechengeng/p/9957302.html
Copyright © 2020-2023  润新知