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检测可写,就是看缓存区,如果缓存区没满,他就返回给你可写,如果你不移除,

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

  • 相关阅读:
    linux常见的编码转换
    linux sort的用法
    转--11个失败之后
    shell入门
    迟到
    必须要回答的问题
    【转载】个人开发者要掌握的时间规划建议
    Unity 碰撞检测 OnTriggerEnter 入门
    浅谈BUFF设计
    随机掉宝,对玩家来讲真的随机吗?
  • 原文地址:https://www.cnblogs.com/hesujian/p/10998612.html
Copyright © 2011-2022 走看看