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

Python Socket聊天程序随机停止运行并抛出socket.timeout错误如何解决

问题背景

我此前基于Socket编写了一款聊天程序原型,功能逻辑为客户端连接服务端后,发送的消息会被广播给所有已连接的客户端。另有一个client2.py文件,与给出的客户端代码逻辑完全一致,仅发送的内容为MESSAGE2。
该程序此前可以正常运行,后续开发完优化版本后再运行该原型代码时已经无法正常工作:程序可以成功建立连接,也能向客户端回传一条消息被客户端正常接收,之后服务端报超时错误,客户端侧仅不断执行错误代码分支,不会崩溃。

服务端代码

import socket as sk
import selectors as sl
import types
import time

def accept(key, mask):
    sock = key.fileobj
    conn, addr = sock.accept()
    print("accepted", conn, "from", addr)
    conn.setblocking(False)
    conn.settimeout(0.5)
    #this is really quite sloppy but basically this timeout
    #is required since the newly accepted
    #socket has not yet been registered
    conn.send("nick".encode("utf-8"))
    nick = conn.recv(1024)
    print(nick)
    print("registering", nick)
    sel.register(conn, sl.EVENT_READ|sl.EVENT_WRITE,data=(handle,nick))
    clientlist.append(conn)

def handle(key, mask):
    conn = key.fileobj

    data = key.data
    message = conn.recv(1024)
    broadcast(message)



def broadcast(message):

    #for key, mask in clients:

    #    print("sending",message,"to",key.data[1])
    #    key.fileobj.send(message)

    for client in clientlist:
        client.send(message)

host = "127.0.0.1"
port = 65432

sel = sl.DefaultSelector()

server=sk.socket(sk.AF_INET,sk.SOCK_STREAM)
#this server socket will listen for clients
#and register them for communication
server.bind((host,port))
server.listen()
server.setblocking(False)

sel.register(server, sl.EVENT_READ, data=(accept,None))
#our listening socket is only interested in reading
#getting data from the clients to accept or decline it

clientlist=[]

print("initialisation complete")


while True:
    clients=sel.select()
    for key, mask in clients:
        callback = key.data[0]
        callback(key, mask)

客户端代码

import socket as sk
import threading as thr
import time

class Client:

    def __init__(self,hospor):

        self.nick = input("enter your nick: ")
        self.nickGiven=False

        self.hospor = hospor
        self.soc = sk.socket(sk.AF_INET,sk.SOCK_STREAM)
        self.soc.connect(hospor)


        msgThread = thr.Thread(target = self.msgLoop)
        receiveThread = thr.Thread(target = self.receiveLoop)


        receiveThread.start()
        msgThread.start()

    def msgLoop(self):

        while True:
            if self.nickGiven:
                msg = "MESSAGE"
                print("sending message")
                self.soc.send(msg.encode("utf-8"))
                time.sleep(10)


    def receiveLoop(self):

        while True:
            message = self.soc.recv(1024)
            if message == b"nick":
                print("received")
                self.soc.send(self.nick.encode("utf-8"))
                self.nickGiven=True
            else:
                print(message)

client=Client(("127.0.0.1",65432))

服务端报错信息

initialisation complete
accepted <socket.socket fd=268, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 65432), raddr=('127.0.0.1', 65011)> from ('127.0.0.1', 65011)
b'nick'
registering b'nick'
Traceback (most recent call last):
  File "C:\Users\Ottie_boi\Documents\00000 actual documents\0 School\0 Computer Science\0 NEA\repo\Actual-NEA\Testing\chatapp proto\v1\server.py", line 66, in <module>
    callback(key, mask)
  File "C:\Users\Ottie_boi\Documents\00000 actual documents\0 School\0 Computer Science\0 NEA\repo\Actual-NEA\Testing\chatapp proto\v1\server.py", line 26, in handle
    message = conn.recv(1024)
socket.timeout: timed out

Process finished with exit code 1

错误原因
  1. socket模式冲突:你在accept函数中先给新连接设置了setblocking(False),随后调用settimeout(0.5)覆盖了之前的非阻塞配置,将socket改为了带0.5s超时的阻塞模式,不符合selector管理非阻塞socket的要求。
  2. 多余的写事件监听:注册客户端socket时同时监听了EVENT_READ和EVENT_WRITE,而TCP socket绝大多数时间都是可写状态,会导致selector频繁触发handle回调。
  3. 无判断直接调用recv:handle函数没有判断当前触发的是读事件还是写事件,只要触发回调就直接调用recv,此时如果没有客户端发来的数据,就会触发socket的超时异常。
  4. 客户端发消息间隔长:客户端设置了每10秒才发一次消息,远长于服务端socket的0.5s超时时间,进一步放大了无数据可读的问题。
修复方案

服务端修改点

  • 拿到客户端昵称后,将socket改回非阻塞模式,移除超时配置
  • 注册客户端socket时仅监听EVENT_READ事件,不需要监听写事件
  • handle函数中先判断当前是否为读事件再调用recv,同时添加异常捕获处理连接断开、无数据的场景
  • 广播消息时添加异常捕获,处理客户端断开的情况

修复后服务端代码

import socket as sk
import selectors as sl
import time

def accept(key, mask):
    sock = key.fileobj
    conn, addr = sock.accept()
    print("accepted", conn, "from", addr)
    # 临时设置超时用于同步获取昵称
    conn.settimeout(0.5)
    conn.send("nick".encode("utf-8"))
    nick = conn.recv(1024)
    print(nick)
    print("registering", nick)
    # 获取昵称后改回非阻塞模式,移除超时
    conn.setblocking(False)
    # 仅监听读事件即可
    sel.register(conn, sl.EVENT_READ, data=(handle,nick))
    clientlist.append(conn)

def handle(key, mask):
    conn = key.fileobj
    # 仅处理读事件
    if mask & sl.EVENT_READ:
        try:
            message = conn.recv(1024)
            # 客户端断开连接时recv会返回空字节
            if not message:
                print(f"客户端 {key.data[1]} 断开连接")
                sel.unregister(conn)
                clientlist.remove(conn)
                conn.close()
                return
            broadcast(message)
        except (BlockingIOError, ConnectionResetError):
            print(f"客户端 {key.data[1]} 连接异常")
            sel.unregister(conn)
            clientlist.remove(conn)
            conn.close()
            return

def broadcast(message):
    # 遍历发送时添加异常捕获
    invalid_clients = []
    for client in clientlist:
        try:
            client.send(message)
        except:
            invalid_clients.append(client)
    # 清理无效连接
    for c in invalid_clients:
        clientlist.remove(c)
        try:
            sel.unregister(c)
            c.close()
        except:
            pass

host = "127.0.0.1"
port = 65432

sel = sl.DefaultSelector()

server=sk.socket(sk.AF_INET,sk.SOCK_STREAM)
server.bind((host,port))
server.listen()
server.setblocking(False)

sel.register(server, sl.EVENT_READ, data=(accept,None))

clientlist=[]

print("initialisation complete")

while True:
    clients=sel.select()
    for key, mask in clients:
        callback = key.data[0]
        callback(key, mask)

客户端可选优化

可以给客户端的recv也添加异常捕获,处理服务端断开的场景,避免客户端无限阻塞。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 04:27:03