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

    在socket的通信中,recv,accept,recvfrom(UDP协议接收信息)这些阶段由于需要收到信息,才能继续下面的代码,所以这些阶段叫做阻塞,类似于
    我们python变成中的input函数,time.sleep方法,在socket通信中,这些阻塞会使进程进入到阻塞状态,下次再进入运行状态时要消耗内存,所以解决
    阻塞可以提高我们代码的执行效率和节省内存空间。下面以访问文件为例,看几种典型的IO模式

    一、阻塞IO
    经历了两个阻塞阶段
    1.发送方:发出去的请求之后等待回应
    2.接收方:收到请求,整理数据,从内核拷贝到进程里
    这是最原始的IOmodel,记住这两个阻塞阶段,后面的IO模型都是基于这两点做该进的。

    当然,我们可以开起多线程,多进程的方式,在阻塞的时间里处理其他的事情,但是当线程、进程开到很多的时候会占用系统资源,降低系统响应速率。
    当规模比较大时,这种方法就不适合选择了。

    二、非阻塞IO

    import socket
    sk = socket.socket()
    sk.bind(('127.0.0.1',9000))
    sk.setblocking(False)
    sk.listen()
    conn_l = []   # 已连接的客户端列表
    del_conn = []
    while True:
        try:
            conn,addr = sk.accept()  #不阻塞,但是没人连我会报错
            print('建立连接了:',addr)
            conn_l.append(conn)    #将连接过的加入列表,便于提取下次客户端第二次发送信息
        except BlockingIOError:
            for con in conn_l:
                try:
                    msg = con.recv(1024)  # 非阻塞,如果没有数据就报错
                    if msg == b'':
                        del_conn.append(con)
                        continue
                    print(msg)
                    con.send(b'byebye')
                except BlockingIOError:pass
            for con in del_conn:
                con.close()
                conn_l.remove(con)
            del_conn.clear()
    server端
    import time
    import socket
    from threading import Thread
    
    def func():
        sk = socket.socket()
        sk.connect(('127.0.0.1',8080))
        time.sleep(0.1)  # 模拟阻塞
        sk.send(b'hello')
        time.sleep(0.5)  # 模拟接收消息过程
        msg = sk.recv(1024)
        print(msg)
        sk.close()
    
    for i in range(10):
        t = Thread(target=func)
        t.start()
    client端

    依次启动服务端,客户端 服务端
    收到客户端连接并加入conn_l列表,由于客户端设置了一个发消息之前设置了一个sleep,并且服务端set.blocking=False,
    服务端while循环时未取到accept会进入注释为‘1’的异常处理,查询conn_1后,接收到客户端完成sleep发来的信息。但此时会接收到一系列的
    b'',(同时未取到信息会报错,捕捉并且不作处理)因为客户端已经发送完成,没有消息了,服务端recv都是空,应该判断msg是否为b'',
    并将这个连接加入到删除列表,循环完成后删除。

    但是用while轮询时,非常占用内存,导致系统处理变慢,响应速率降低。

    三、IO多路复用

    IO多路复用模型是socket服务端借助了操作系统来完成的,操作系统会监听访问者,一旦收到连接请求,操作系统来给socket服务端提供信号,让其进入accept阶段,向下继续执行,使其变为非阻塞状态,同时也会将文件拷贝至进程,让其recv。这种用于socket端比较多的情况,因为比起阻塞IO,多路复用增加了一个系统与socket之间的相互通信。但是如果监听的socket较多的话,效率还是很快的。

    import select   # select模块是操作系统用来监听的模块
    import socket
    
    sk = socket.socket()
    sk.bind(('127.0.0.1',8000))
    sk.setblocking(False)
    sk.listen()
    
    read_lst = [sk] # 将需要监听的sk加入列表
    while True:   # [sk,conn]
        r_lst,w_lst,x_lst = select.select(read_lst,[],[]) # 返回的是三个值,分别为是哪个socket可读,可写,可改
        # 一旦有文件可以读取,就会返回该socket的地址 
        for i in r_lst:
            if i is sk:
                conn,addr = i.accept()
                read_lst.append(conn)   # socket可连接的客户端链接地址
            else:
                ret = i.recv(1024)
                if ret == b'':
                    i.close()
                    read_lst.remove(i)
                    continue
                print(ret)
                i.send(b'goodbye!')
    服务端server

    相较于非阻塞IO,可以监听多个socket,并且不用多次轮询,优化了内存使用

    补充一点,代码中的select是windows系统用的,还有Linux的poll模块,监听的数量要比select多,以及Linux的epolled模块,不仅起到监听socket对象的作用,而且还会给每个对象加上一个回调函数。windows中的selector模块和其作用相似。

    在监听的对象成百上千时,由于数据类型是列表,数量越多查询速度越慢,但是使用epoll一旦准备好可以读取,可以调用它的回调函数直接来发送信号给socket,速率更高。

    四、异步IO

    异步IO原理是 用户端发送读取请求,不进入阻塞,可以做不相干的业务逻辑,将请求发给操作系统执行,操作系统完成文件的recv和提取到进程的过程,用户端直接读取。

    但由于python端没有可以直接操作系统的接口,所以目前大部分异步IO都是由C语言完成。

  • 相关阅读:
    进程-线程-消息队列
    用Ogre实现《天龙八部》场景中水面(TerrainLiquid)详解
    TCP协议三次握手过程分析【图解,简单清晰】
    excel批量删除sql语句
    批量删除指定盘指定格式文件
    Linux命令速查手册(第2版)学习
    List、Map、Set 三个接口,存取元素时,各有什么特点
    HashMap 什么时候进行扩容呢
    两个对象值相同 (x.equals(y) == true),但却可有不同的 hash code,这句话对不对?
    ArrayList,Vector, LinkedList 的存储性能和特性
  • 原文地址:https://www.cnblogs.com/jimmyhe/p/10503912.html
Copyright © 2011-2022 走看看