下面就是详细的 Python 网络编程使用 select 实现 socket 全双工异步通信功能的攻略。
1、什么是 select
select 是一种 I/O 多路复用机制,它可以监控多个文件描述符,等待输入或输出操作就绪,从而实现启用一个线程或一个进程就能同时管理多个连接通道。
2、select 的优劣
- 优点:select 可以同时监听多个连接,无需通过多线程、多进程实现,节省了系统资源,提高了程序效率。
- 缺点:select 在高并发条件下,处理效率会随着连接增多而下降,且开发难度较大。
3、使用 select 实现 socket 全双工异步通信
(1)创建套接字和绑定端口:
import socket
server = socket.socket()
server.bind(('localhost', 8888))
server.listen(5)
(2)准备 I/O 数据:
msg1 = b'Hello, from client1!'
msg2 = b'Hello, from client2!'
(3)创建 select 对象和加入列表:
import select
inputs = [server]
outputs = []
(4)循环接收和处理数据:
while True:
# select 机制实现异步 I/O
# 监听 inputs 中可读事件和写事件,超时时间设置为 5 秒
rlist, wlist, elist = select.select(inputs, outputs, inputs, 5)
# 遍历可读事件列表,接收客户端连接请求
for s in rlist:
if s is server:
conn, addr = server.accept()
print('新客户端连接:', addr)
# 将新连接加入 inputs 中,以便监控其输入事件
inputs.append(conn)
else:
# 从已连接的客户端接收数据并输出
data = s.recv(1024)
if data:
print(data)
# 将输出目标加入 outputs 列表
outputs.append(s)
else:
# 如果客户端连接关闭,将其从 inputs 中移除
inputs.remove(s)
s.close()
# 遍历可写事件列表,发送数据给客户端
for s in wlist:
if s in outputs:
# 向连接的客户端发送数据
s.sendall(msg2)
# 将该连接从输出列表中删除
outputs.remove(s)
# 遍历异常事件列表,关闭连接并移除
for s in elist:
inputs.remove(s)
if s in outputs:
outputs.remove(s)
s.close()
上述示例中,创建了一个服务器套接字server 和一个监听列表 inputs,将 server 添加到 inputs 列表中,实现异步监听连接请求。接下来,使用 select.select() 方法,将 inputs 中的 socket 对象分组,轮流检查每个可读或可写的套接字,并响应相应的数据,并在需要时添加或删除socket对象。这种管理方式提供了更高的并发处理能力,增加了服务器的稳定性和可用性。
示例中还可以通过数据包长度(recv(1024))或消息结束符(\n等)来控制数据的粘连度,这显然需要在数据发送方进行控制。
4、示例说明
示例1:使用 select 实现简单聊天室
# 聊天室服务端示例
import socket
import select
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(('localhost', 10086))
server.listen(5)
inputs = [server]
outputs = []
while True:
rlist, wlist, elist = select.select(inputs, outputs, inputs, 5)
for s in rlist:
if s is server:
conn, addr = s.accept()
print('New connection from', addr)
inputs.append(conn)
else:
try:
data = s.recv(1024)
except:
data = b''
if data:
print(data)
for other in inputs:
if other != s and other != server:
try:
other.sendall(data)
except:
pass
else:
inputs.remove(s)
s.close()
for s in wlist:
pass
for s in elist:
inputs.remove(s)
if s in outputs:
outputs.remove(s)
s.close()
server.close()
以上代码中,服务端创建了一个socket实例,循环接收客户端请求,并实现了基于select式的异步输入输出数据。每当一个客户端 socket 实例有数据的时候,服务端就会将这个消息广播到所有连接到的 socket 实例中。
示例2:使用 select 实现批量邮件发送
# 邮件发送客户端实例
import socket
import select
def sendmail(sender, recipient, subject, content):
# 连接邮件服务器
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.connect(('smtp.example.com', 25))
inputs = [server]
outputs = []
# 接收服务器返回信息并解码为字符串
def recv_all(sock):
data = b""
while True:
try:
more = sock.recv(1024)
if not more:
break
data += more
except Exception as e:
print("对方网络不稳定,连接中断!")
data = ""
break
return data.decode('utf-8')
# 发送邮件
rlist, wlist, elist = select.select(inputs, outputs, inputs, 10)
server.sendall(b'HELO example.com\n')
print(recv_all(server))
server.sendall(b'MAIL FROM:' + sender.encode() + b'\n')
print(recv_all(server))
server.sendall(b'RCPT TO:' + recipient.encode() + b'\n')
print(recv_all(server))
server.sendall(b'DATA\n')
print(recv_all(server))
server.sendall(b'Subject:' + subject.encode() + b'\n')
server.sendall(b'From:' + sender.encode() + b'\n')
server.sendall(b'To:' + recipient.encode() + b'\n\n')
server.sendall(content.encode() + b'\n')
server.sendall(b'.\n')
print(recv_all(server))
server.sendall(b'QUIT\n')
print(recv_all(server))
# 关闭连接
server.close()
if __name__ == '__main__':
sendmail('johndoe@example.com', 'janedoe@example.com', 'test email', 'hello,world!')
以上代码中,一个邮件服务是由邮件服务器和发送者和接收者两个 socket 实例组成,每个发送 socket 实例开启 select 异步输入输出数据交换机制,读入用户的 mail 消息进行后续处理。在 select 的监听机制下,实现多任务完成信道相互隔离的通信操作。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Python网络编程使用select实现socket全双工异步通信功能示例 - Python技术站