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会阻塞住,直到监听的描述符发生变化才继续往下执行) |
对于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)
#!/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()
I/O多路复用的应用场景
#(1)当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用I/O复用。 #(2)当一个客户同时处理多个套接口时,而这种情况是可能的,但很少出现。 #(3)如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。 #(4)如果一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用。 #(5)如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。 '''与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。'''