zoukankan      html  css  js  c++  java
  • IO模型

    IO模型

      模型就是解决某个问题的套路,IO模型主要是用来解决IO问题的

      IO问题:输入输出的问题

      例如我需要一个用户名来执行登录操作,问题是用户名需要输入,输入需要耗时,如果输入没有完成,

      后续的逻辑代码就无法继续执行,这种默认的处理方式就是阻塞IO模型

      网络IO中必经的两个阶段

        1、wait_data:等待数据从对方发到系统的缓存区

        2、copy_data:将数据从系统缓存区拷贝到应用的内存中

    阻塞IO

      阻塞IO模型指的是当遇到IO操作时,就将当前的线程阻塞住,cpu切换到其他线程执行,

      一直等到IO操作结束,拿到想要的结果时,再唤醒刚刚的线程,将状态调整为就绪态

      存在的问题

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

      解决方案

        多线程或者多进程:当客户端并发量非常大的时候,服务器可能无法开启新的线程或者

                 进程,如果不对数量加以限制,服务器就崩溃了(无效率可言)

        线程池或者进程池:对最大并发量加以了限制,保证服务器正常运行,但是问题时,

                 如果所有客户端都处于阻塞状态,那么这些线程也阻塞了,

                 后面再想进来访问也进不来

        协程:使用一个线程处理所有的客户端,当一个客户端处于阻塞状态时,

           可以切换至其他客户端任务

    非阻塞IO

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

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

      如何使用:

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

    import socket
    client = socket.socket()
    client.connect(("127.0.0.1",1688))
    
    while True:
        msg = input(">>:").strip()
        if not msg:continue
        if msg == 'q':break
        client.send(msg.encode('utf-8'))
        print(client.recv(1024).decode('utf-8'))
    客户端

    将阻塞设置为非阻塞之后需要注意的几个点

      1、accept  recv send 几个函数都有可能出现阻塞状态,所以都需要抓取异常

      2、需要不停的循环去问缓存区有没有数据,就需要把所有的客户端socket对象加到容器中,

         不断遍历recv,如果有值就接收,没值就不管

      3、回消息也应该把消息和对应的socket对象放到容器中,循环发送,如果发送成功,

         就从消息容器中删除,如果发送失败,说明现在缓存区是满的,下次循环再发

      4、客户端正常退出和异常退出都需要抓取异常,并且关闭socket对象,移除容器,

         这时候就需要注意发消息 的容器中还有没有这个socket对象的消息,如果有,

         一起删除,防止后续发出会抛异常

    import socket
    server = socket.socket()
    server.bind(("127.0.0.1",8080))
    server.listen()
    
    server.setblocking(False)   # 把阻塞改为非阻塞
    
    clients = []    # 创建容纳客户端socket对象的容器列表
    msgs = []       # 创建一个保存要发送信息和socket对象对应的容器
    
    while True:
        try:
            client,addr = server.accept()   # 如果没人连接过来,就会被捕获异常
            clients.append(client)
        except BlockingIOError:
            print("没人连接过来")
            # 接收消息,把消息保存起来
            for c in clients[:]:
                try:
                    data = c.recv(1024)     # 没接收到消息就会捕获异常,接着往下执行
                    if not data:
                        c.close()   # 如果客户端正常退出,在这里关闭客户端socket对象,并且移除列表
                        clients.remove(c)
                        continue        # 在这里关闭移除完之后要continue一下,不然还会把socket对象添加到保存消息列表中
                    msgs.append((c,data))
    
                except BlockingIOError:
                    print("没有任务需要接收")
                except ConnectionResetError:
                    c.close()
                    clients.remove(c)   # 其实在这关闭移除还需要注意一个点,那就是如果上面已经添加到保存列表去
                                        # 然后对面强行中断连接的话也会报异常
    
            # 发送消息
            for m in msgs[:]:
                try:
                    c,data = m
                    c.send(data.upper())    # 如果缓存区满了,就会捕获异常,接着执行
                    msgs.remove(m)  # 信息发送完之后把消息移除
                except BlockingIOError:
                    print("没有任务需要发送")
    服务器端

    多路复用IO

      在非阻塞IO模型中,我们要想实现并发,就必须一直询问操作系统缓存有没有数据或者能不能发送,

      这样是非常耗cpu的

      所以我们想了一个办法,就是让一个对象去统一检测,如果有socket对象可写或者可读,就把socket对象返出来

      然后进行处理,这里需要用到的模块就是select模块

      select模块中有一个select函数,这个函数可以实现帮我们检测列表中的socket对象是否可读或者可写

      select监听的socket对象是有上限的,默认为1024个

    使用方法

    import socket
    client = socket.socket()
    client.connect(("127.0.0.1",8080))
    
    while True:
        msg = input("msg:").strip()
        if not msg:continue
        if msg == 'q':break
        client.send(msg.encode('utf-8'))
        print(client.recv(1024).decode('utf-8'))
    客户端
    import socket
    import select
    server = socket.socket()
    server.bind(("127.0.0.1",8080))
    server.listen()
    
    rlist = [server,]  # 创建读的检测列表,server也是可读的socket对象,需要先加进来
    wlist = []  # 创建写的检测列表
    msgs = []
    
    """
    select模块中select函数中的几个参数
    rlist: 里面存储检测是否可读(recv、accept)的socket对象
    wlist: 里面存储检测是否可写(send)的socket对象
    xlist: 存储你需要关注的异常条件,忽略
    timeout:设置超时时间,如果超过设定时间还没有检测到可读或者可写,就返回空列表(一般不设置)
    """
    while True:
        readable_list,writeable_list,_ = select.select(rlist,wlist,[])
    
        # 循环可读的列表readable_list,有socket对象就读出来
        # 读取数据
        for soc in readable_list:   # type:socket.socket  # 这个可以让soc有socket对象的提示
            try:
                if soc == server: # 判断如果是等待连接的,就接收连接,并把客户端socket对象加到检测列表中
                    client,addr = server.accept()
                    rlist.append(client)
                else:  # 如果不是server,那就说明是客户端的socket对象可读了,读取数据
                    data = soc.recv(1024)
                    if not data:
                        soc.close()     # 检测到对面正常退出,那就把socket对象close并且移出检测列表
                        rlist.remove(soc)
                        continue
                    if soc not in wlist:  # 判断如果socket对象不在检测可写列表中就添加,在就不管
                        wlist.append(soc)
                        msgs.append((soc,data)) # 将socket对象和数据存到容器中
            except ConnectionResetError:
                soc.close()
                rlist.remove(soc)   # 抓捕到对面强退错误后,关闭socket对象,并且从rlist中移除
                if soc in wlist:        # 并且需要判断一下这个socket对象是否在可写的检测列表中,在就移除
                    wlist.remove(soc)
    
        # 循环可写的列表writeable_list,有socket对象就发送数据
        # 发送数据
        for soc in writeable_list: # type:socket.socket  # 这里面不可能有server,所以不需要判断
            for msg in msgs[:]:
                if soc == msg[0]: # 判断对象相同
                    soc.send(msg[1].upper())
                    msgs.remove(msg)  # 发送完就可以把信息从保存列表中移除
            wlist.remove(soc)
            # 如果这个socket对象需要发送的信息全部发送完之后,需要把他移出wlist检测
            # 因为select检测send就是如果缓存区没满,就会给你返回可写
            # 你如果不删除,会导致select会一直循环给你发可写信息
    服务器端

    服务器端需要注意的几个细节

    1、select.select()函数中几个参数的意思

      rlist: 里面存储检测是否可读(recv、accept)的socket对象

      wlist: 里面存储检测是否可写(send)的socket对象

      xlist: 存储你需要关注的异常条件,忽略

      timeout:设置超时时间,如果超过设定时间还没有检测到可读或者可写,就返回空列表(一般不设置)

    2、server也是一个socket对象,也需要检测accept,如果可读,就操作建立连接

    3、如果检测到客户端socket对象正常退出或者异常退出,都需要把socket对象移出检测可读列表,

       在这里需要注意的是,当你把socket对象移出检测可读列表时,一定要判断一下,检测可写列表

       里有没有该socket对象,如果有就一起移除,避免下面发送数据出错,没有就不用移除

    4、当检测出一个socket对象可写时,当把所有需要发送的数据发送完毕后,需要把该socket对象

       移出检测可写列表

       因为select检测可写,就是看缓存区,如果缓存区没满,他就返回给你可写,如果你不移除,

       会造成死循环(告诉你可写,你又没东西写)

  • 相关阅读:
    101. Symmetric Tree(js)
    100. Same Tree(js)
    99. Recover Binary Search Tree(js)
    98. Validate Binary Search Tree(js)
    97. Interleaving String(js)
    96. Unique Binary Search Trees(js)
    95. Unique Binary Search Trees II(js)
    94. Binary Tree Inorder Traversal(js)
    93. Restore IP Addresses(js)
    92. Reverse Linked List II(js)
  • 原文地址:https://www.cnblogs.com/hesujian/p/10998612.html
Copyright © 2011-2022 走看看