zoukankan      html  css  js  c++  java
  • IO模型,非阻塞IO模型,select实现多路复用

    1. IO阻塞模型

    IO问题:

    输入输出

    我要一个用户名用来执行登陆操作,问题用户名需要用户输入,输入需要耗时, 如果输入没有完成,后续逻辑无法继续,所以默认的处理方式就是 等

    将当前进程阻塞住,切换至其他进程执行,等到按下回车键,拿到了一个用户名,再唤醒刚才的进程,将状态调整为就绪态

     

    以上处理方案 就称之为阻塞IO模型

     

    存在的问题:

    当执行到recv时,如果对象并没有发送数据,程序阻塞了,无法执行其他任务

    解决方案:

    1.1 多线程或多进程

    当客户端并发量非常大的时候,服务器可能就无法开启新的线程或进程,如果不对数量加以限制 服务器就崩溃了

    1.2 线程池或进程池

    首先限制了数量 保证服务器正常运行,但是问题是,如果客户端都处于阻塞状态,这些线程也阻塞了

    1.3 协程

    使用一个线程处理所有客户端,当一个客户端处于阻塞状态时可以切换至其他客户端任务

     

    2.非阻塞IO模型

    阻塞IO模型在执行recv 和 accept 时 都需要经历wait_data

    非阻塞IO即 在执行recv 和accept时 不会阻塞 可以继续往下执行

     

    如何使用:

    将server的blocking设置为False 即设置非阻塞

     

    存在的问题 :

    这样一来 你的进程 效率 非常高 没有任何的阻塞

    很多情况下 并没有数据需要处理,但是我们的进程也需要不停的询问操作系统 会导致CPU占用过高

    而且是无意义的占用

    案例:使用server

    import socket
    import time
    
    server = socket.socket()
    server.bind(('192.168.13.29', 8080))
    server.listen()
    server.setblocking(False)  # 默认为阻塞,设置为False 表示非阻塞
    
    # 用来存储客户端的列表
    clients = []
    # 用来存储需要发送的数据和客户端对象
    msgs = []
    
    # 链接客户端的循环
    while True:
        try:
            client, addr = server.accept()  # 接收三次握手信息
            # 如果有人链接成功,那么就把客户端对象添加到列表
            clients.append(client)
        except BlockingIOError as e:
            # 实验效果,设置等待时间
            time.sleep(0.01)
    
            # 收取数据的操作,遍历客户端列表,此处因为如果客户端
            # 将链接断开,那么就不能存在列表中了,所以需要删除,
            # 而并不能在for 循环中不能操作原列表,所以切片,会得到一个新的列表
            for c in clients[:]:
                try:  # 可能这个客户端还没没有数据过来
                    # 开始通讯任务
                    data = c.recv(2048)
                    if not data:
                        c.close()
                        # 如果客户端断开,会一直收空。所以直接删除该客户端
                        clients.remove(c)
                    # c.send(data.upper()) # 此处如果这样写。如果缓存区满了,这个数据就丢了
                    # 由于此处捕获了异常,所以因该单独来处理发送数据
                    msgs.append((c, data))  # 将客户端对象与需要发送的信息以元祖的格式存放至字典中
                except BlockingIOError as e:
                    pass
                    # print('这个客户端还不需要处理。。。。')
                except ConnectionResetError:
                    # 断开后删除这个客户端
                    c.close()
                    clients.remove(c)
    
                # 制作发送数据的操作
                # 遍历存放客户端对象与需要发送信息的列表,然后将信息发送给客户端
            for i in msgs[:]:
                try:
                    c, msg = i  # 因为i存储的两个数据,是元祖,所以使用解压赋值
                    c.send(msg.upper())
                    msgs.remove(i)  # 如果发送成功! 删除这个客户端对象与数据
                except BlockingIOError:
                    pass
    Vode

    3.多路复用

    案例: 使用select 模块

    import socket
    import time
    import select
    
    # 使用select模块来实现
    server = socket.socket()
    server.bind(('192.168.13.29', 8080))
    server.listen()
    
    '''
    select需要三个参数
    参数1:rlist 里面存储需要被检测是否可读(是否可以执行recv)的socket对象
    参数2:wlist 里面存储需要被检测是否可写(是否可以执行send)的socket对象
    参数3:xlist存储你需要关注异常条件,忽略即可
    参数4:timeout 检测超时时间,一段时间后还是没有可以被处理的socket 那就返回空列表
    返回值: 三个列表
    1. 已经有数据到达的socket对象
    2. 可以发送数据的socket对象
    3.忽略。。。。。
    '''
    # 此处之所以添加server 是把server添加到select中检测是否有用户连接
    rlist = [server, ]
    
    # 存储要发送的数据和socket
    wlist = []
    
    # 要发送的数据和客户端的socket
    msgs = []
    
    while True:
        # 传入select所需的三个参数,返回的是有数据到达,或者可以发送数据的socket对象
        ras, was, _ = select.select(rlist, wlist, [])  # 阻塞直到socket可读或是可写
        # 处理可读的socket
        for s in ras:
            # 判断列表中的s等于服务器的对象,那么就进行三次握手
            if s == server:
                client, addr = server.accept()
                # 然后将客户端的套接字对象添加到rlist列表中
                rlist.append(client)
            else:
                # 如果是客户端套接字对象
                try:
                    # 那么就收数据
                    data = s.recv(2048)
                    if not data:
                        raise ConnectionResetError()
                    # 将客户端套接字对象存入待检测的wlist列表中
                    wlist.append(s)
                    # 将客户端套接字的对象与要发送的数据添加到msgs列表中
                    msgs.append((s, data))
                except ConnectionResetError:
                    s.close()
                    rlist.remove(s)
                    if s in wlist: wlist.remove(s)
        # 处理可写的socket,遍历检测后的was,
        for s in was:
            # 再遍历要发送的数据列表
            for msg in msgs[:]:
                # 如果需要发送的客户端等于需要发送数据的客户端
                if msg[0] == s:
                    # 就将数据发送给这个客户端
                    s.send(msg[1].upper())
                    # 发送成功之后,删除已经无用的数据,并且需要将socket从wlist列表中删除
                    # 不删除会造成死循环,因为socket一直处于可写状态
                    msgs.remove(msg)
                    wlist.remove(s)
    View Code
  • 相关阅读:
    eclips断点调试
    单位换算
    信息论与编码复习
    嵌入式学习笔记
    DAVINCI项目日志
    英语考试方法
    虚拟机安装Ubuntu的上网设置(有线网络和无线网络)
    重装系统必须备份的几种数据
    linux笔记
    word应用技巧
  • 原文地址:https://www.cnblogs.com/liguodeboke/p/11000510.html
Copyright © 2011-2022 走看看