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

跨平台TCP中继服务器非阻塞Socket异常疑问

问题描述

我正在编写一款可同时部署在Windows和Linux平台的TCP中继服务器(共用代码库),涉及两个Socket操作,需要明确以下内容:

异常场景确认

  • 非阻塞Socket的recv()在无数据可读时触发的具体异常;
  • sendall()无法完成全部数据发送时触发的具体异常。

细节确认

  • 处理非阻塞Socket的recv()时,是否需要同时检查socket.EWOULDBLOCK和socket.EAGAIN两种errno?
  • sendall()失败时会返回何种errno(即e.args[0])?

核心顾虑

sendall()失败时是否会返回EAGAIN或EWOULDBLOCK?若如此会引发问题。

附当前代码:

try:
    socket1.setblocking(False)
    socket2.setblocking(False)

    while True:
        try:
            sock1_data = socket1.recv(1024)
            if sock1_data:
                socket2.sendall(sock1_data)
        except socket.error as e:
            if e.args[0] != socket.EAGAIN and e.args[0] != socket.EWOULDBLOCK:
                raise e

        try:
            sock2_data = socket2.recv(1024)
            if sock2_data:
                socket1.sendall(sock2_data)
        except socket.error as e:
            if e.args[0] != socket.EAGAIN and e.args[0] != socket.EWOULDBLOCK:
                raise e
except:
    pass
finally:
    if socket2:
        socket2.close()
    if socket1:
        socket1.close()
问题解答

1. 非阻塞recv()无数据时的异常

非阻塞模式下,recv()无数据可读时会抛出socket.error异常:

  • Linux下,errno是EAGAIN或EWOULDBLOCK(二者数值相同);
  • Windows下,errno仅为EWOULDBLOCK。

2. sendall()无法完成发送时的异常

sendall()会循环调用底层send()直到所有数据发完,中途出错直接抛出socket.error:

  • 非阻塞模式下,若发送缓冲区满导致无法继续写入,会抛出带EAGAIN/EWOULDBLOCK的异常;
  • 若遇到连接断开、网络故障等问题,会抛出对应errno的异常(比如ECONNRESET)。

3. 是否需要同时检查EAGAIN和EWOULDBLOCK?

必须同时检查。虽然Linux上两个errno值相同,但Windows只定义了EWOULDBLOCK,没提供EAGAIN。不同时检查的话,Windows环境下会漏判非阻塞recv()的正常无数据场景,导致错误抛出异常。

4. sendall()失败时的errno

  • 缓冲区满导致发送中断:返回EAGAIN(Linux)或EWOULDBLOCK(Windows);
  • 连接类错误:返回ECONNRESET(对方重置连接)、EPIPE(连接已关闭)等;
  • 其他网络错误:返回对应标准errno(如ENETUNREACH表示网络不可达)。

核心顾虑回应

是的,非阻塞Socket调用sendall()确实会返回EAGAIN/EWOULDBLOCK——当发送缓冲区已满,无法继续写入数据时就会触发。你的当前代码有隐患:sendall()抛出的这类异常会被外层的except:直接捕获忽略,导致数据发送不完整却被当成正常情况处理,最终丢数据。

代码改进建议

要给sendall()相关逻辑添加异常处理,区分EAGAIN/EWOULDBLOCK和其他错误,同时还要缓存未发送的数据后续重试(否则会丢数据):

import socket

try:
    socket1.setblocking(False)
    socket2.setblocking(False)
    # 新增:缓存未发送的数据
    send_buf1 = b""
    send_buf2 = b""

    while True:
        # 处理socket1接收并转发到socket2
        try:
            sock1_data = socket1.recv(1024)
            if sock1_data:
                send_buf2 += sock1_data
                # 尝试发送缓存的数据
                while send_buf2:
                    try:
                        sent = socket2.send(send_buf2)
                        send_buf2 = send_buf2[sent:]
                    except socket.error as e:
                        if e.args[0] not in (socket.EAGAIN, socket.EWOULDBLOCK):
                            raise e
                        break  # 缓冲区满,退出循环等待下次可写
        except socket.error as e:
            if e.args[0] not in (socket.EAGAIN, socket.EWOULDBLOCK):
                raise e

        # 处理socket2接收并转发到socket1
        try:
            sock2_data = socket2.recv(1024)
            if sock2_data:
                send_buf1 += sock2_data
                # 尝试发送缓存的数据
                while send_buf1:
                    try:
                        sent = socket1.send(send_buf1)
                        send_buf1 = send_buf1[sent:]
                    except socket.error as e:
                        if e.args[0] not in (socket.EAGAIN, socket.EWOULDBLOCK):
                            raise e
                        break
        except socket.error as e:
            if e.args[0] not in (socket.EAGAIN, socket.EWOULDBLOCK):
                raise e
except Exception as e:
    # 替换原有的pass,添加错误日志便于排查
    print(f"服务器异常: {str(e)}")
finally:
    if 'socket2' in locals() and socket2:
        socket2.close()
    if 'socket1' in locals() and socket1:
        socket1.close()

另外,当前的空循环方式效率极低,建议改用selectors模块实现IO多路复用,监听Socket的可读/可写事件,避免无意义的CPU占用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 23:25:27