Python非阻塞Socket实现P2P聊天单向通信问题求助
排查Python简易P2P聊天程序单向通信问题
我尝试用Python实现一个简易的点对点(P2P)聊天程序,第一个用户运行下方的server.py程序绑定Socket,另一用户可通过netcat连接进行通信。但目前程序仅能获取本地输入并发送给对方,无法接收对方发送的消息,相关代码如下:
import sys import socket import select import threading # Bind to socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind(('127.0.0.1', 11111)) s.listen() def chat(conn, addr): # Set blocking to false so that program can send and receive messages at the same time conn.setblocking(0) # Receive messages using select while conn in select.select([conn], [], [], 0)[0]: text = conn.recv(4096) if text: print("{}: {}".format(addr, text)) else: return # get user input and send message while True: msg = input(">>>") conn.send(msg.encode()) if __name__ == '__main__': ## Accept connections and start new thread (conn, addr) = s.accept() threading.Thread(target=chat, args=([conn, addr])).start()
问题原因
- 接收逻辑仅执行一次:接收消息的
while循环使用了超时时间为0的select(非阻塞检查),如果此时没有消息到达,会直接跳出接收循环,进入永久的输入发送循环,再也不会回头检查新消息。 - 收发逻辑串行阻塞:整个
chat函数里,接收和发送是串行执行的,没有同时监听输入和Socket消息的机制,导致程序只能专注于发送操作,无法处理后续的接收请求。
解决方法
需要让程序同时监听Socket连接的可读事件和标准输入的可读事件,实现收发并行。以下提供两种可行方案:
方案1:单线程用select同时监听Socket和标准输入
import sys import socket import select # Bind to socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind(('127.0.0.1', 11111)) s.listen() def chat(conn, addr): # 同时监听Socket连接和标准输入 inputs = [conn, sys.stdin] while True: readable, _, _ = select.select(inputs, [], []) for sock in readable: if sock == conn: # 处理对方发送的消息 text = sock.recv(4096) if text: # 用\r和end=""修正打印格式,避免打断输入提示符 print(f"\r{addr}: {text.decode()}\n>>>", end="") else: print("\n对方已断开连接") return elif sock == sys.stdin: # 处理本地输入并发送 msg = input(">>>") conn.send(msg.encode()) if __name__ == '__main__': conn, addr = s.accept() chat(conn, addr)
方案2:双线程分别处理接收和发送
import sys import socket import threading # Bind to socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind(('127.0.0.1', 11111)) s.listen() def receive_messages(conn, addr): while True: try: text = conn.recv(4096) if text: print(f"\r{addr}: {text.decode()}\n>>>", end="") else: print("\n对方已断开连接") break except Exception as e: print(f"\n接收消息出错: {e}") break def send_messages(conn): while True: try: msg = input(">>>") conn.send(msg.encode()) except Exception as e: print(f"\n发送消息出错: {e}") break if __name__ == '__main__': conn, addr = s.accept() # 启动接收线程,设为守护线程随主线程退出 threading.Thread(target=receive_messages, args=(conn, addr), daemon=True).start() # 主线程处理发送逻辑 send_messages(conn)
关键改动说明
- 方案1通过
select.select同时监控Socket和标准输入,只要其中一个有可读事件就触发处理,实现了收发并行。 - 方案2拆分出独立的接收线程专门监听对方消息,主线程负责输入发送,两个线程并行运行,避免了串行阻塞。
- 加入
\r和end=""修正打印格式,防止对方消息打断输入提示符的正常显示。
内容的提问来源于stack exchange,提问作者Nagarajan Sree
相关产品推荐
相关产品推荐

