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多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。'''
原文地址:https://www.cnblogs.com/yxy-linux/p/5663922.html