python实现一个小的socket I/O 多路复用
自己看了三天的I/O多路复用,总算是有点自己的思考了。看了很多大佬关于I/O多路复用的文章。
在这里我先推荐下:
https://blog.csdn.net/sehanlingfeng/article/details/78920423
https://www.cnblogs.com/zingp/p/6863170.html
这里还推荐一个是视频,讲解的没有那么学术化,更加偏向应用。
简单的梳理下自己对I/O多路复用的理解
我们都知道在c语言中,我们写个scanf()
, 后会阻塞这,并不会往下执行。
这是为什么呢?
那我们就要来说一下操作系统的知识了,程序在执行的过程中,有三种状态:
就绪状态
:进程获得了除了CPU之外的所有的必要资源,只要获得CPU就可以立即执行,此时的进程处于就绪态。运行状态
:进程已经获得CPU,正在运行,在多处理其系统中,会有多个进程同时处于运行状态。阻塞状态
:处于执行状态的进程由于发生某些事件而暂时无法继续执行,放弃处理机而处于暂停状态,此时进程就处于阻塞(执行受到阻塞)状态。
(如果没有学过操作系统的同学可以去简单的学一下,虽然有点难度。)
有了操作系统的知识,我们就可以解释为什么在运行scanf()
为什么会阻塞着了。因为没有得到相应的资源进程就会转为阻塞态。然后程序就不会往下面执行。
那我们怎样让程序,不被阻塞着然后继续往下执行呢?
我们就可以使用I/O多路复用来实现。
我们可以在后台写一个函数然后跑再后台监控这个进程的数据是否准备好了,后台的程序是自己运行的,和我自己写的主程序不影响。然而操作系统就给我们提供了这样一个函数。
Windows:select
Mac: select
Linux:select、poll、epoll
就拿select
函数作为例子,说明一下,select的功能是内部会有一个循环,但是这个循环是跑在操作系统内部的,功能是检测我们的I/O流是否发生了变化。如果变化了就会被检测出来。
然后我自己用python实现了一个简单的python socket 的 I/O多路复用:
server.py
import socket
import select
sk = socket.socket() # 创建一个套接字对象
ipaddr = ('127.0.0.1', 9998) # ip 和端口号
sk.bind(ipaddr) # 绑定
sk.listen(5) # 监听
inputs = [sk, ] # io 多路复用
outputs = [] # 存放要输出的I/O
massages = {} # 存放读入的消息
while True:
rlist, wlist, erro = select.select(inputs, outputs, [], 1) # 使用select 监听IO对象
for r in rlist:
if r == sk:
conn, addr = r.accept()
conn.sendall(b'hello boy!')
inputs.append(conn)
massages[conn] = []
else: # read
try:
data = r.recv(1024).decode(encoding='utf-8')
if not data:
raise Exception('disconnect')
except Exception as e: # 异常处理,防止客户端突然断开然后 程序保存崩溃
inputs.remove(r)
del massages[r]
continue
if data.strip() == 'q':
r.close()
inputs.remove(r)
del massages[r]
continue
print(data)
outputs.append(r)
massages[conn].append(data)
# 读写分离
for w in wlist: # write
msg = massages[w].pop()
w.sendall(('response:'+msg).encode(encoding='utf-8'))
outputs.remove(w)
client.py
import socket
s = socket.socket()
ipaddr = ('127.0.0.1', 9998)
s.connect(ipaddr)
data = s.recv(1024)
data = str(data, encoding='utf-8')
print(data)
while True:
input_data = input('>>>')
s.sendall(bytes(input_data, encoding='utf-8'))
if input_data.strip() == 'q':
break
elif input_data.strip() == '':
continue
recv_data = s.recv(1024)
print(str(recv_data, encoding='utf-8'))