Python :TCP服务器非阻塞+IO多路复用
一、非阻塞状态
在socket中服务器在等待客户端连接以及接收客户端的消息的时候会发生阻塞状态。
我们要想达到非阻塞状态就得先设置非阻塞
设置非阻塞:socket对象.setblocking(False)
服务端代码示例:
import socket
ADDR = ("127.0.0.1", 8000)
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 设置非阻塞
server.setblocking(False)
server.bind(ADDR)
server.listen(5)
二、达到非阻塞效果
如果服务端直接设置非阻塞会报错,那么这里引入异常处理( try-except) 来帮助我们达到非阻塞的效果。
代码逻辑:尝试连接,如果连接不到直接执行excep的内容。建立连接到的和断开连接的列表可以达到再服务(一个服务端连接多个客户端)的效果。
服务端:
import socket
ADDR = ("127.0.0.1", 5555)
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 设置非阻塞
server.setblocking(False)
server.bind(ADDR)
server.listen(5)
# 保存客户端连接对象的列表
conn_list = []
# 要断开的连接对象的列表
del_list = []
while True:
# 捕获异常
try:
conn, addr = server.accept() # 非阻塞
conn_list.append(conn)
except BlockingIOError:
# 检查列表中的连接对象是否发来了消息
for conn in conn_list:
try:
data = conn.recv(1024) # 非阻塞
if not data:
conn.close()
del_list.append(conn)
continue
print("收到:", data.decode('utf-8'))
conn.sendall("已接收".encode"utf-8")
except BlockingIOError:
continue
except ConnectionAbortedError:
#客户端退出
conn.close()
del_list.append(conn)
for conn in del_list:
print(conn, "断开连接")
conn_list.remove(conn)
# 清空列表
del_list.clear()
server.close()
客户端:
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(("127.0.0.1", 5555))
print("连接成功")
while True:
message = input(">>")
if not message:
continue
if message == "quit":
print("客户端退出")
break
client.send(message.encode("utf-8"))
data = client.recv(1024)
print(data.decode("utf-8"))
client.close()
三、IO多路复用
IO多路复用是用于提升效率,可以同时监听多个网络连接IO(一个服务端可以服务多个客户端)
可以监视多个文件描述符,一旦描述符就绪,能够通知到程序进行相应的读写操作。
这里引入select方法
import select
r_list, w_list, x_list=select(rlist, wlist, xlist[, timeout])
rlist 列表 读IO列表,添加等待发生的或者可读的IO事件
wlist 列表 写IO列表,存放要可以主动处理的或者可写的IO事件
xlist 列表 异常IO列表,存放出现异常要处理的IO事件
timeout 超时时间
返回值:
r_list 列表 rlist中准备就绪的IO
w_list 列表 wlist中准备就绪的IO
x_list 列表 xlist中准备就绪的IO
服务端:
import socket
import select
ADDR = ("127.0.0.1",4444)
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 设置非阻塞
server.setblocking(False)
server.bind(ADDR)
server.listen(5)
# 第一个被监听的对象是server
# 后面被添加的是conn
read_list = [server]
while True:
r_list, w_list, x_list = select.select(read_list, [], [])
# 准备就绪的对象
for read in r_list:
if read == server:
# 建立新连接
conn, addr = server.accept()
print(f"与{addr}连接成功")
# 把conn添加到监听列表中
read_list.append(conn)
else:
data = read.recv(1024)
if data:
print("客户端:", data.decode("utf-8"))
read.send("已收到".encode"utf-8")
else:
print(read, "断开连接")
read.close()
read_list.remove(read)
server.close()
客户端:
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(("127.0.0.1", 4444))
print("连接成功")
while True:
message = input(">>")
if not message:
continue
if message == "quit":
print("客户端退出")
break
client.send(message.encode("utf-8"))
data = client.recv(1024)
print(data.decode("utf-8"))
client.close()
注意:
1.设置非阻塞要在绑定 ip 之前
2.wlist,xlist 不需要使用到,直接传一个空列表即可
3.客户端在编辑处设置允许多实例即可实现再服务









