原理:I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪,能够通知程序进行相应的操作。
select执行流程
- select需要提供要监控的数组,然后由用户态拷贝到内核态
- 内核态线性循环监控数组,每次都需要遍历整个数组
- 内核发现文件描述符状态符合操作结果,将其返回
- 所以对于我们监控的socket都要设置为非阻塞的,只有这样才能保证不会被阻塞
优点
- 基本各个平台都支持
缺点
-
每次调用select,都需要把fd集合由用户态拷贝到内核态,在fd多的时候开销会很大
-
单个进程能够监控的fd数量存在最大限制,因为其使用的数据结构是
数组
。 -
每次select都是线性遍历整个数组,当fd很大的时候,遍历的开销也很大
-
所以对于我们监控的socket都要设置为非阻塞的,只有这样才能保证不会被阻塞
服务端
import select
import socket
socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_server.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
socket_server.bind(('', 10002))
socket_server.listen(5)
# 接收连接队列
rlist = [socket_server]
# 应答队列
wlist = []
# 错处理的IO事件
xlist = []
while True:
# 监控IO发生
rs, ws, xs = select.select(rlist, wlist, xlist)
# 遍历三个返回值列表,判断哪个IO发生
for r in rs:
# 如果是套接字准备就绪,处理连接
if r == socket_server:
c, addr = r.accept()
print("Connect from: ", addr)
print("当前连接套接字的文件描述符:", c.fileno())
# 将客户端套接字加入新的关注IO
rlist.append(c)
# 用于沟通的新客户端套接字
else:
data = r.recv(1024)
if not data:
# 客户端断开连接,将客户端套接字关闭,并且从监控列表中移除
rlist.remove(r)
r.close()
continue
print(data.decode("utf-8"))
# 主动处理这个IO,作应答
wlist.append(r)
for w in ws:
w.send(b'OK')
wlist.remove(w)
for x in xs:
pass
客户端
import socket
flag = 1
s = socket.socket()
s.connect(('127.0.0.1', 10002))
while flag:
input_msg = input('input>>>')
if input_msg == '0':
break
s.sendall(input_msg.encode())
msg = s.recv(1024)
print(msg.decode())
s.close()