Python Socket服务器非阻塞按键检测与客户端列表推送需求
Hey there! Great job getting multi-client socket connections working already—let's get that console input handling integrated smoothly.
The core challenge here is making your server listen for two things at once: new client connections and console input, without blocking either. Let's break down two solid approaches you can use, with code examples tailored to your needs.
Approach 1: Use selectors (Recommended, Cross-Platform)
Python's built-in selectors module lets you monitor multiple I/O sources (like your server socket and standard input) in a single loop, which is perfect for non-blocking scenarios. Here's how to adapt your server code:
First, set up the selector and track connected clients:
import selectors import socket import sys # Initialize selector sel = selectors.DefaultSelector() client_sockets = [] accept_new_connections = True # Set up server socket server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.bind(("localhost", 12345)) # Use your port server_socket.listen() server_socket.setblocking(False) sel.register(server_socket, selectors.EVENT_READ, data=None)
Next, define functions to handle new connections and console input:
def accept_new_client(sock): """Handle incoming client connections""" conn, addr = sock.accept() print(f"New connection from {addr}") conn.setblocking(False) client_sockets.append(conn) def handle_console_input(): """Process console triggers and send client list""" global accept_new_connections input_str = sys.stdin.readline().strip() # Trigger on enter (empty string) or "list" command if input_str == "" or input_str.lower() == "list": accept_new_connections = False print("Stopping new connections — sending client list to all clients...") # Build client list message client_list = "\n".join([ f"Client {idx+1}: {sock.getpeername()}" for idx, sock in enumerate(client_sockets) ]) message = f"Current connected clients:\n{client_list}".encode("utf-8") # Send to all clients (handle disconnects gracefully) for sock in client_sockets.copy(): # Use copy to avoid modification during loop try: sock.send(message) except Exception as e: print(f"Failed to send to {sock.getpeername()}: {str(e)}") client_sockets.remove(sock) sock.close() # Optional: Add a "resume" command to start accepting connections again elif input_str.lower() == "resume": accept_new_connections = True print("Resuming new client connections...") # Register standard input with the selector sel.register(sys.stdin, selectors.EVENT_READ, data=handle_console_input)
Finally, run the main loop:
try: while True: # Wait for I/O events (blocks until something happens) events = sel.select(timeout=None) for key, mask in events: if key.data is None: # Handle new client only if we're accepting connections if accept_new_connections: accept_new_client(key.fileobj) else: # Run the console input handler key.data() except KeyboardInterrupt: print("\nServer shutting down...") finally: # Cleanup all sockets sel.close() for sock in client_sockets: sock.close() server_socket.close()
Approach 2: Use Threading (Simpler for Beginners)
If you find the selector approach a bit abstract, you can use a separate thread to listen for console input while your main thread handles client connections. Just remember to use a lock to safely access shared data (like the client_sockets list):
import socket import sys import threading client_sockets = [] accept_new_connections = True lock = threading.Lock() # For thread-safe access to shared variables def console_listener(): """Thread to listen for console input""" global accept_new_connections while True: input_str = sys.stdin.readline().strip() if input_str == "" or input_str.lower() == "list": with lock: accept_new_connections = False # Build and send client list client_list = "\n".join([ f"Client {idx+1}: {sock.getpeername()}" for idx, sock in enumerate(client_sockets) ]) message = f"Current connected clients:\n{client_list}".encode("utf-8") for sock in client_sockets.copy(): try: sock.send(message) except Exception as e: print(f"Error sending to client: {str(e)}") client_sockets.remove(sock) sock.close() elif input_str.lower() == "resume": with lock: accept_new_connections = True print("Resuming new connections...") # Start the console listener thread (daemon thread exits when main thread does) threading.Thread(target=console_listener, daemon=True).start() # Main server loop server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.bind(("localhost", 12345)) server_socket.listen() try: while True: with lock: if not accept_new_connections: continue # Skip accepting new connections if disabled conn, addr = server_socket.accept() print(f"New connection from {addr}") with lock: client_sockets.append(conn) except KeyboardInterrupt: print("\nServer shutting down...") finally: with lock: for sock in client_sockets: sock.close() server_socket.close()
Key Notes to Remember
- Handle Client Disconnects: Always wrap send calls in try/except blocks—if a client disconnects unexpectedly, you'll get an error, and you should remove it from your client list.
- Encoding Consistency: Make sure your client code uses the same encoding (like
utf-8) when receiving messages. - Cross-Platform Compatibility: The
selectorsapproach works on Windows, macOS, and Linux. If you're on Windows and want a simpler key-press detector (instead of waiting for enter), you can use themsvcrtmodule'skbhit()function, but it's not cross-platform.
Pick the approach that feels most comfortable for your skill level—both will get the job done!
内容的提问来源于stack exchange,提问作者Varshil Anavadia

