• 网络编程之IO模型


    1.1 IO模型介绍

     IO多路复用概念

      IO发生时涉及的对象和步骤。对于一个网络IO,它会涉及到两个系统对象,一个是调用IO的进程或者线程,另一个就是系统内核。

    如当一个read操作发生时,会先等待数据准备,然后将数据从内核拷贝到进程中去

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

      实际上几乎所有的IO借口(包括socket端口)都是阻塞型的。如在调用recv(1024)的同时,线程将被阻塞,在此期间,线程将无法执行任何运算或响应任何的网络请求。

      解决方案:在服务端使用多线程(或者多进程),目的是让每个连接都拥有独立的线程(或者进程)。但是一旦客户端同一时间的连接请求过多的话,则会严重占用系统资源,降低系统对外界的响应效率。

      那么该如何解决呢?最好的办法就是使用‘线程池’或者‘连接池’。“线程池”旨在减少创建和销毁线程的频率,其维持一定合理数量的线程,并让空闲的线程重新承担新的执行任务。“连接池”维持连接的缓存池,尽量重用已有的连接、减少创建和关闭连接的频率。这两种办法可以很好地降低系统开销,适用于大型系统。   

      上述方案只是一定程度缓解了频繁调用IO接口带来的资源占用,‘池’终究是有上限的,当响应规模过大时,必须考虑调整池的大小。

    用解决该问题。可以使用非阻塞接口来解决

    1.2 非阻塞IO模型(nonblocking IO)

    多线程,多进程,进程池,线程池都可以实现并发,但是仍然没有解决IO问题,先看一下IO非阻塞模型(linux下)

      从图中可以看出,当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是用户就可以在本次到下次再发起read询问的时间间隔内做其他事情,或者直接再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存(这一阶段仍然是阻塞的),然后返回。

        也就是说非阻塞的recvform系统调用调用之后,进程并没有被阻塞,内核马上返回给进程,如果数据还没准备好,此时会返回一个error。进程在返回之后,可以干点别的事情,然后再发起recvform系统调用。重复上面的过程,循环往复的进行recvform系统调用。这个过程通常被称之为轮询。轮询检查内核数据,直到数据准备好,再拷贝数据到进程,进行数据处理。需要注意,拷贝数据整个过程,进程仍然是属于阻塞的状态。

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

    相关参数

    server.setblocking() # 默认是True  
    
    server.setblocking(False)  # False的话就成非阻塞了,这只是对于socket套接字来说的
     # 所以,在非阻塞式IO中,用户进程其实是需要不断的主动询问内核数据准备好了没有。
    
    wait data # 等数据,这个阶段是不阻塞的 
    
    copy data # 这个阶段还是要阻塞的

    server端

    import socket
    
    sk = socket.socket()
    sk.bind(('127.0.0.1',9000))
    sk.setblocking(False)
    sk.listen()
    conn_lst = []
    del_lst = []
    while True:
        try:
            conn,addr = sk.accept()   # 非阻塞的模型
            print(conn,addr)
            conn_lst.append(conn)
        except BlockingIOError as e:
            for conn in conn_lst:  #[conn1,conn2,conn3]
                try:
                    msg= conn.recv(1024)  # 非阻塞
                    if not msg:  # 判定是否为空
                        conn.close()
                        del_lst.append(conn)
                        continue
                    print(msg)
                    msg = msg.decode('utf-8').upper()
                    conn.send(msg.encode('utf-8'))
                except BlockingIOError:pass   # 阻塞IO报错,直接pass
            for conn in del_lst:
                conn_lst.remove(conn)
            del_lst.clear()  

     client端

    import time
    import socket
    
    sk = socket.socket()
    sk.connect(('127.0.0.1',9000))
    print(sk)
    for i in range(20):
        sk.send(b'hello')
        print(sk.recv(1024))
        time.sleep(1)
    sk.close()
    

    非阻塞IO模型优点

      能够在等待任务完成的时间里干其他活了(包括提交其他任务,也就是 “后台” 可以有多个任务在“”同时“”执行)。

    非阻塞IO模型缺点:

      1. 循环调用recv()将大幅度推高CPU占用率

      2. 任务完成的响应延迟增大了,因为每过一段时间才去轮询一次read操作,而任务可能在两次轮询之间的任意时间完成。这会导致整体数据吞吐量的降低。

    1.3 多路复用(IO multiplexing)  

      当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。

    操作系统中的IO多路复用的机制select:

      windows操作系统提供给你的一种监听接收数据IO的一个代理

    select模块:

      python使用操作系统select机制的功能

    server端:

    import socket
    import select
    sk = socket.socket()
    sk.bind(('127.0.0.1',9000))
    sk.setblocking(False)
    sk.listen()
    
    l = [sk]                         # sk.accept
    while True:                     
        r,w,x = select.select(l,[],[])   # 阻塞
        for obj in r:
            if obj is sk:
                conn,addr = obj.accept()
                l.append(conn)                   # conn.recv
            else:
                msg = obj.recv(1024)
                if not msg:
                    obj.close()
                    l.remove(obj)
                    continue
                print(msg)
                obj.send(b'bye')

      client端:

    import socket
    sk = socket.socket()
    sk.connect(('127.0.0.1',9000))
    while True:
        sk.send(b'hello')
        print(sk.recv(1024))
    sk.close()
    

     该模型优点:

      相比其他模型,使用select() 的事件驱动模型只用单线程(进程)执行,占用资源少,不消耗太多 CPU,同时能够为多客户端提供服务。如果试图建立一个简单的事件驱动的服务器程序,这个模型有一定的参考价值。

    该模型缺点:

      首先select()接口并不是实现“事件驱动”的最好选择。因为当需要探测的句柄值较大时,select()接口本身需要消耗大量时间去轮询各个句柄。

    对于IO多路复用(select)
    select检测的是哪个套接字准备好了(检测的时候等待了,变成阻塞了)

    select之所以比阻塞IO好,就是因为select可以检测多个套接字
    多个链接下select才能发挥它的优势
    但是你的套接字特别多,你怎么知道哪个好了呢,那么就得用循环去遍历一下
    那么如果特别多的时候,效率也就不咋高了

    epoll:只支持linux系统(就是为了解决select效率低的问题)
    epoll比pool,select效率高

    selectors 更好用,解决了上面select,epoll,pool的问题

    socketserver用这个模块IO问题也解决了,实现并发也解决了(见链接文章最底部实例

    1.4异步IO:

      参考链接:http://www.cnblogs.com/Eva-J/articles/8324837.html

      

     

  • 相关阅读:
    表单输入框黏贴自动过滤转义实例
    前端性能优化成神之路-浏览器缓存机制详解
    前端性能优化成神之路-HTTP压缩开启gzip
    前端性能优化成神之路—重绘与回流
    前端性能优化成神之路—图片相关的优化
    ES5-ES6-ES7_集合Set和Map
    ES5-ES6-ES7_解构赋值
    ES5-ES6-ES7_函数的扩展
    ES5-ES6-ES7_数值的扩展
    ES5-ES6-ES7_字符串与JOSN格式的数据相互转换以及深度克隆新对象
  • 原文地址:https://www.cnblogs.com/LearningOnline/p/9054397.html
Copyright © 2020-2023  润新知