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

Python实现ChatServer/ChatClient时客户端连接8800端口失败排查

问题原因

代码里存在3个直接导致连接失败、程序崩溃的核心bug,按影响优先级排序:

  • 服务端监听socket被意外提前关闭
    ChatServer.run()方法末尾的self.server.close()缩进错误,被写在了while running循环内部,服务端第一次处理完可读事件(比如收到客户端连接请求)后,就会直接关闭监听socket,直接导致服务端无法响应后续请求、甚至进程异常退出。
  • receive()工具函数逻辑完全错误
    接收数据时使用buf = channel.recv(...)直接覆盖缓冲区,而非增量拼接已接收的数据,会导致永远无法拿到完整的序列化消息,触发反序列化异常,直接打断服务端和客户端的连接流程。
  • 服务端消息处理分支缩进完全错乱
    处理客户端消息的代码块缩进错误,if data:判断下只有注释,消息转发、客户端断开处理逻辑的缩进完全不匹配,会直接触发运行时错误,导致服务端崩溃退出。

另外还有几个隐性问题会影响稳定性:

  • send()函数直接用send()发送数据,TCP流传输可能出现部分发送的情况,会导致对端解析消息失败
  • 结构体打包用"L"格式是平台相关长度,跨Windows/Linux运行会出现长度解析错误
  • 异常捕获范围太窄,反序列化、索引错误等非socket异常会直接让程序崩溃退出

修复步骤
  1. 调整self.server.close()的缩进,把它移到while running循环外部,仅在服务端退出循环后才关闭监听socket
  2. 重写receive()函数的缓冲区逻辑,改用增量拼接方式接收完整数据,统一用字节类型处理socket收发内容
  3. 修正服务端处理客户端消息分支的缩进,把消息转发逻辑放到if data:块内,客户端断开逻辑放到对应else块
  4. 把send()里的send()替换为sendall(),保证数据完整发送
  5. 调整结构体打包格式为定长网络字节序,避免跨平台兼容问题
  6. 补全必要的异常捕获,避免单个客户端错误导致整个服务端崩溃

修复后的可运行完整代码
import select
import socket
import sys
import signal
import pickle as cPickle
import struct
import argparse

SERVER_HOST = 'localhost'
CHAT_SERVER_NAME = 'server'
# 定长包头用网络字节序4字节无符号整数,跨平台兼容
PACKET_SIZE_FMT = "!I"
PACKET_HEADER_SIZE = struct.calcsize(PACKET_SIZE_FMT)

# 数据收发工具
def send(channel, *args):
    buffer = cPickle.dumps(args)
    pkt_size = len(buffer)
    size_header = struct.pack(PACKET_SIZE_FMT, pkt_size)
    # 用sendall保证数据完整发送
    channel.sendall(size_header)
    channel.sendall(buffer)

def receive(channel):
    # 先读固定长度包头
    header_data = b""
    while len(header_data) < PACKET_HEADER_SIZE:
        chunk = channel.recv(PACKET_HEADER_SIZE - len(header_data))
        if not chunk:
            return ''
        header_data += chunk
    try:
        pkt_size = struct.unpack(PACKET_SIZE_FMT, header_data)[0]
    except struct.error:
        return ''
    # 增量拼接读取完整包体
    buf = b""
    while len(buf) < pkt_size:
        chunk = channel.recv(pkt_size - len(buf))
        if not chunk:
            return ''
        buf += chunk
    return cPickle.loads(buf)[0]

class ChatServer(object):

    def __init__(self, port, backlog=5):
        self.clients = 0
        self.clientmap = {}
        self.outputs = []
        self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 允许端口复用
        self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.server.bind((SERVER_HOST, port))
        print('Server listening to port: %s...' % port)
        self.server.listen(backlog)
        # 捕获Ctrl+C中断信号
        signal.signal(signal.SIGINT, self.sighandler)

    def sighandler(self, signum, frame):
        print("\nShutting down server...")
        # 关闭所有客户端连接
        for output in self.outputs:
            output.close()
        self.server.close()
        sys.exit(0)

    def get_client_name(self,client):
        info = self.clientmap[client]
        host, name = info[0][0], info[1]
        return '@'.join((name, host))

    def run(self):
        inputs = [self.server, sys.stdin]
        self.outputs = []
        running = True
        while running:
            try:
                readable, writeable, exceptional = select.select(inputs, self.outputs, [])
            except select.error:
                break
            for sock in readable:
                if sock == self.server:
                    # 处理新连接
                    client, address = self.server.accept()
                    print("Chat Server: got connection %d from %s" % (client.fileno(), address))
                    # 读取客户端登录名
                    try:
                        cname = receive(client).split('NAME: ')[1]
                    except Exception:
                        client.close()
                        continue
                    self.clients += 1
                    send(client, 'CLIENT: ' + str(address[0]))
                    inputs.append(client)
                    self.clientmap[client] = (address, cname)
                    # 广播新客户端上线通知
                    msg = "\n(Connected: New client (%d) from %s)" % (self.clients, self.get_client_name(client))
                    for output in self.outputs:
                        try:
                            send(output, msg)
                        except Exception:
                            pass
                    self.outputs.append(client)
                elif sock == sys.stdin:
                    # 处理服务端控制台输入,输入任意内容回车即可关闭服务端
                    junk = sys.stdin.readline()
                    running = False
                else:
                    # 处理客户端消息
                    try:
                        data = receive(sock)
                        if data:
                            # 转发消息给其他所有客户端
                            msg = '\n[' + self.get_client_name(sock) + ']>>' + data
                            for output in self.outputs:
                                if output != sock:
                                    try:
                                        send(output, msg)
                                    except Exception:
                                        pass
                        else:
                            # 客户端断开连接
                            print("Chat server: %d hung up" % sock.fileno())
                            self.clients -= 1
                            sock.close()
                            inputs.remove(sock)
                            self.outputs.remove(sock)
                            # 广播客户端离线通知
                            msg = "\n(Now hung up: Client from %s)" % self.get_client_name(sock)
                            for output in self.outputs:
                                try:
                                    send(output, msg)
                                except Exception:
                                    pass
                    except socket.error:
                        # 清理异常断开的客户端
                        if sock in inputs:
                            inputs.remove(sock)
                        if sock in self.outputs:
                            self.outputs.remove(sock)
                        sock.close()
        # 循环结束后才关闭服务端监听socket
        self.server.close()

class ChatClient(object):

    def __init__(self, name, port, host=SERVER_HOST):
        self.name = name
        self.connected = False
        self.host = host
        self.port = port
        # 初始化命令行提示符
        self.prompt = '[' + '@'.join((name, socket.gethostname().split('.')[0])) + ']> '
        # 连接服务端
        try:
            self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.sock.connect((host, self.port))
            print("Now connected to chat server@ port %d" % self.port)
            self.connected = True
            # 发送登录用户名
            send(self.sock, 'NAME: ' + self.name)
            data = receive(self.sock)
            # 解析服务端返回的客户端地址,更新提示符
            addr = data.split('CLIENT: ')[1]
            self.prompt = '[' + '@'.join((self.name, addr)) + ']> '
        except Exception:
            print("Failed to connect to chat server @ port %d" % self.port)
            sys.exit(1)

    def run(self):
        while self.connected:
            try:
                sys.stdout.write(self.prompt)
                sys.stdout.flush()
                # 同时监听控制台输入和服务端消息
                readable, writable, exceptional = select.select([0, self.sock], [], [])
                for sock in readable:
                    if sock == 0:
                        # 读取控制台输入发送给服务端
                        data = sys.stdin.readline().strip()
                        if data:
                            send(self.sock, data)
                    elif sock == self.sock:
                        # 读取服务端转发的消息
                        data = receive(self.sock)
                        if not data:
                            print('Client shutting down')
                            self.connected = False
                            break
                        else:
                            sys.stdout.write(data + '\n')
                            sys.stdout.flush()

            except KeyboardInterrupt:
                print("\nClient interrupted")
                self.sock.close()
                break


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='Socket Chat Room with Select')
    parser.add_argument('--name', action="store", dest="name", required=True)
    parser.add_argument('--port', action="store", dest="port", type=int, required=True)
    given_args = parser.parse_args()
    port = given_args.port
    name = given_args.name
    if name == CHAT_SERVER_NAME:
        server = ChatServer(port)
        server.run()
    else:
        client = ChatClient(name=name, port=port)
        client.run()

运行验证
  1. 开第一个终端启动服务端:python chat.py --name=server --port=8800,正常输出Server listening to port: 8800...即启动成功
  2. 开第二个终端启动客户端:python chat.py --name=client1 --port=8800,正常输出Now connected to chat server@ port 8800、出现聊天提示符即连接成功,可以正常发送消息
  3. 支持开启多个客户端用不同name参数连接,消息会自动广播给所有在线客户端

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 01:45:42