• socketserver 进阶之I/O多路复用


    I/O多路复用

    I/O多路复用指:通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。

    I/O多路复用的基本原理就是select /epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。就通知用户进程。

    Python中有一个select模块,其中提供了:select、poll、epoll三个方法,分别调用系统的 select,poll,epoll 从而实现IO多路复用。
    根据系统不同:他支持的也不同
     
    Windows Python:
        提供: select
    Mac Python:
        提供: select
    Linux Python:
        提供: select、poll、epoll

    注意:网络操作、文件操作、终端操作等均属于IO操作,对于windows只支持Socket操作,其他系统支持其他IO操作,但是无法检测 普通文件操作 自动上次读取是否已经变化。

    普通文件操作所有系统都是完成不了的,普通文件是属于I/O操作!但是对于python来说文件变更python是监控不了的,所以我们能用的只有是“终端的输入输出,Socket的输入输出”

    对于Select:

    句柄列表11, 句柄列表22, 句柄列表33 = select.select(句柄序列1, 句柄序列2, 句柄序列3, 超时时间)
     
    参数: 可接受四个参数(前三个必须)
    返回值:三个列表
     
    select方法用来监视文件句柄,如果句柄发生变化,则获取该句柄。
    1、当 参数1 序列中的句柄发生可读时(accetp和read),则获取发生变化的句柄并添加到 返回值1 序列中
    2、当 参数2 序列中含有句柄时,则将该序列中所有的句柄添加到 返回值2 序列中
    3、当 参数3 序列中的句柄发生错误时,则将该发生错误的句柄添加到 返回值3 序列中
    4、当 超时时间 未设置,则select会一直阻塞,直到监听的句柄发生变化
       当 超时时间 = 1时,那么如果监听的句柄均无任何变化,则select会阻塞 1 秒,之后返回三个空列表,如果监听的句柄有变化,则直接执行。
    

    利用select监听终端操作实例

    #!/bin/bin/env python
    # -*-coding:utf-8 -*-
    # Author : rain
    
    import socket
    import select
    
    # 创建socket对象
    sk = socket.socket()
    # 设置监听的IP与端口
    sk.bind(('127.0.0.1', 9000))
    # 设置client最大等待连接数
    sk.listen(5)
    
    inputs = [sk, ]
    # 将sk这个对象加入到列表中,并且赋值给inputs
    # 原因:看上例conn是客户端对象,客户是一直连接着呢,连接的时候状态变了,连接上之后,连接上之后,还是服务端的socket 有关吗?
    # 是不是的把他改为动态的?
    
    while True:
        rlist, w, e = select.select(inputs, [], [], 1)
        print(len(inputs), len(rlist))      # 打印inputs列表,查看执行变化
        # 监听sk(服务器)对象,如果sk对象发生变化,表示有客户端来连接了,此时rlist的值为[sk]
        # 监听conn对象,如果conn发生变化,表示客户端有新消息发送过来了,此时的rlist的值为[客户端sk]
        for r in rlist:
            # 这里判断,如果是客户端连接过来的话他不是sk,如果是服务端的socket连接过来的话是sk
            if r == sk:
                conn, addr = sk.accept()
                # 追加一个conn
                inputs.append(conn)
                conn.send(bytes('helle server,this is client', encoding='utf8'))
            else:
                # 如果是客户端,接受和返回数据
                try:
                    data = r.recv(2048)
                    r.sendall(data)
                except Exception :
                    # 如果没有收到客户端端数据,则移除客户端句柄 因为,不管是正常关闭还是异常关闭,client端的系统底层都会发送一个消息
                    inputs.remove(r)
    # client 端
    import socket
    
    ck = socket.socket()
    ck.connect(('127.0.0.1', 9000))
    data = ck.recv(2048)
    print(data.decode())
    while True:
        inp = input(">>>>>>")
        ck.sendall(bytes(inp, encoding='utf8'))
        s_data = ck.recv(2048)
        print(s_data.decode())
    ck.close()

    通过I/O多路复用让socket实现了处理多个客户端的方法,参数注解:

    # 第一个参数,监听的句柄序列,当有变动的时候就能捕获到把值赋值给readable_list
    # 如果第二参数有参数,即只要不是空列表,select就能感知,然后writeabled_list就能获取值
    # 第三个参数监听描述符,select内部,检测列表里面的描述符在底层操作的时候有没有异常,如果异常了他也当成一个变化,把这个赋值给error_list 一般第三个参数和第一个参数相同
    # 第四个参数,阻塞时间,如 1秒(这个如果不写,select会阻塞住,直到监听的描述符发生变化才继续往下执行)

    rlist, wlist, e = select.select(inputs, [], [], 1)

    对于I/O多路复用,咱们上面的例子就可以了,但是为了遵循select规范需要把读和写进行分离:

    # rlist -- wait until ready for reading      #等待直到有读的操作
    # wlist -- wait until ready for writing      #等待直到有写的操作
    # xlist -- wait for an ``exceptional condition'' #等待一个错误的情况
    #!/bin/bin/env python
    # -*-coding:utf-8 -*-
    # Author : rain
    
    import socket
    import select
    
    # 创建socket对象
    sk = socket.socket()
    # 设置监听的IP与端口
    sk.bind(('127.0.0.1', 9000))
    # 设置client最大等待连接数
    sk.listen(5)
    
    inputs = [sk, ]
    # 将sk这个对象加入到列表中,并且赋值给inputs
    # 原因:看上例conn是客户端对象,客户是一直连接着呢,连接的时候状态变了,连接上之后,连接上之后,还是服务端的socket 有关吗?
    # 是不是的把他改为动态的?
    output = []
    
    while True:
        rlist, wlist, e = select.select(inputs, output, [], 1)
        print(len(inputs), len(rlist), len(wlist))      # 打印inputs列表,查看执行变化
        # 监听sk(服务器)对象,如果sk对象发生变化,表示有客户端来连接了,此时rlist的值为[sk]
        # 监听conn对象,如果conn发生变化,表示客户端有新消息发送过来了,此时的rlist的值为[客户端sk]
        for r in rlist:
            # 这里判断,如果是客户端连接过来的话他不是sk,如果是服务端的socket连接过来的话是sk
            if r == sk:
                conn, addr = sk.accept()
                # 追加一个conn
                inputs.append(conn)
                conn.send(bytes('helle server,this is client', encoding='utf8'))
            else:
                # 如果是客户端,接受和返回数据
                try:
                    data = r.recv(2048)
                    # r.sendall(data)
                    if not data:
                        raise Exception('断开连接')
                    else:
                        output.append(r)
                except Exception :
                    # 如果没有收到客户端端数据,则移除客户端句柄 因为,不管是正常关闭还是异常关闭,client端的系统底层都会发送一个消息
                    inputs.remove(r)
        # 实现读写分离(wlist为conn)
        for w in wlist:
            w.sendall(bytes('response', encoding='utf8'))
            output.remove(w)
    读写分离server
    #!/bin/bin/env python
    # -*-coding:utf-8 -*-
    # Author : rain
    
    import socket
    
    ck = socket.socket()
    ck.connect(('127.0.0.1', 9000))
    data = ck.recv(2048)
    print(data.decode())
    while True:
        inp = input(">>>>>>")
        ck.sendall(bytes(inp, encoding='utf8'))
        s_data = ck.recv(2048)
        print(s_data.decode())
    ck.close()
    读写分离client

    I/O多路复用的应用场景 

    #(1)当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用I/O复用。
    #(2)当一个客户同时处理多个套接口时,而这种情况是可能的,但很少出现。
    #(3)如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。
    #(4)如果一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用。
    #(5)如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。
    '''与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。'''
  • 相关阅读:
    NoClassDefFoundError问题
    Spring-Batch处理MySQL数据后存到CSV文件
    jQuery EasyUI + struts2.3 + mongoDB 列表查询翻页JAVA样例
    mongodb exception in initAndListen: 12596 old lock file, terminating 解决方法
    硬盘安装RedHat Enterprise Linux 6(转载)
    jQuery zxxbox弹出框插件(v3.0)
    在html页面中利用ftp访问协议格式载入服务器图片
    eclipse中 com.sun.image.codec.jpeg.JPEGCodec 无法编译通过问题
    java 去掉字符串右侧空格
    去掉eclipse js 错误提示
  • 原文地址:https://www.cnblogs.com/yxy-linux/p/5663922.html
Copyright © 2020-2023  润新知