You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python多客户端单TCP服务器:如何向指定客户端发送消息

问题分析与解决方案

核心问题梳理

你的代码目前存在几个关键问题导致无法实现向指定客户端发送消息:

  • pending_messages被重复重置:main循环里每次处理连接都会把pending_messages设为[(fd, b"helloWorld\r")],且fd未正确赋值(accept_wrapper无返回值,此处fd处于未定义状态)
  • 不必要的可写事件监听:连接建立后就一直注册EVENT_WRITE,导致select持续触发可写事件,造成资源浪费
  • fd概念混淆:fd是操作系统给socket分配的文件描述符,不是selector分配的,key.fd和key.fileobj.fileno()是同一个值
  • 缺乏消息路由逻辑:没有处理客户端发来的消息,无法解析出目标客户端标识和要发送的内容

修正后的代码

import socket
import selectors
import types

sel = selectors.DefaultSelector()
# 维护所有已连接客户端:fd -> (socket, data)
clients = {}

def accept_wrapper(sock):
    conn, addr = sock.accept()
    fd = conn.fileno()
    print(f"Accepted connection from {addr}, FD# {fd}")
    conn.setblocking(False)
    data = types.SimpleNamespace(addr=addr, inb=b"", outb=b"")
    # 只注册读事件,有数据要发时再注册写事件
    sel.register(conn, selectors.EVENT_READ, data=data)
    clients[fd] = (conn, data)

def service_connection(key, mask):
    sock = key.fileobj
    data = key.data
    fd = key.fd

    if mask & selectors.EVENT_READ:
        recv_data = sock.recv(1024)
        if recv_data:
            print(f"Received {recv_data!r} from {data.addr}")
            # 解析消息格式:假设客户端发送的是 b"[目标FD] [消息内容]"
            try:
                parts = recv_data.strip().split(b" ", 1)
                target_fd = int(parts[0])
                message = parts[1] + b"\r"
                if target_fd in clients:
                    # 给目标客户端添加待发送数据
                    target_sock, target_data = clients[target_fd]
                    target_data.outb += message
                    # 为目标客户端注册写事件,让selector感知可写
                    sel.modify(target_sock, selectors.EVENT_READ | selectors.EVENT_WRITE, data=target_data)
                    print(f"Queued message {message!r} for FD# {target_fd}")
                else:
                    error_msg = b"Error: Target client not found\r"
                    data.outb += error_msg
                    sel.modify(sock, selectors.EVENT_READ | selectors.EVENT_WRITE, data=data)
            except (IndexError, ValueError):
                # 消息格式错误,返回提示
                error_msg = b"Error: Invalid format, use '[FD] [message]'\r"
                data.outb += error_msg
                sel.modify(sock, selectors.EVENT_READ | selectors.EVENT_WRITE, data=data)
        else:
            print(f"Closing connection to {data.addr}, FD# {fd}")
            sel.unregister(sock)
            sock.close()
            del clients[fd]
    if mask & selectors.EVENT_WRITE:
        if data.outb:
            print(f"Sending {data.outb!r} to {data.addr}")
            sent = sock.send(data.outb)
            data.outb = data.outb[sent:]
            # 数据发完后,取消写事件注册
            if not data.outb:
                sel.modify(sock, selectors.EVENT_READ, data=data)

def main(host, port):
    lsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    lsock.bind((host, port))
    lsock.listen()
    print(f"Listening on {(host, port)}")
    lsock.setblocking(False)
    sel.register(lsock, selectors.EVENT_READ, data=None)
    
    try:
        while True:
            events = sel.select(timeout=None)
            for key, mask in events:
                if key.data is None:
                    accept_wrapper(key.fileobj)
                else:
                    service_connection(key, mask)
    except KeyboardInterrupt:
        print("Caught keyboard interrupt, exiting")
    finally:
        sel.close()

if __name__ == '__main__':
    main("", 2000)

关键逻辑说明

  • 客户端管理:用clients字典存储所有已连接客户端,key是fd,value是对应的socket和数据对象,方便快速查找目标客户端
  • 可写事件按需注册:只有当客户端有数据要发送时,才通过sel.modify注册EVENT_WRITE,数据发送完成后取消注册,避免无效的select触发
  • 消息路由:假设客户端发送的消息格式为[目标FD] [消息内容],解析后将消息加入目标客户端的outb缓冲区,并触发其可写事件
  • fd的正确理解:fd是操作系统给socket分配的文件描述符,不是selector分配的,key.fd与key.fileobj.fileno()返回的是同一个值

内容的提问来源于stack exchange,提问作者Chris

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 08:56:10