Python TCP 服务端的正确实现姿势

2次阅读

socket.accept()需用try/except捕获osError(errno 4)并重试;recv()返回b”表示连接关闭,须显式判断;多客户端应避免单线程轮询,改用i/o多路复用、多线程或asyncio;bind()前须设so_reuseaddr。

Python TCP 服务端的正确实现姿势

socket.accept() 必须在 try/except 里包裹

阻塞式 accept() 可能被信号中断(比如 Ctrl+C、系统发送的 SIGCHLD),此时会抛出 OSError: [Errno 4] Interrupted system call。不捕获就崩,尤其在 linux 守护进程中很常见。

  • try/except OSError as e: 捕获,检查 e.errno == 4 后直接重试 accept()
  • 别只捕获 socket.errorpython 3 已弃用)或宽泛的 Exception
  • 如果用了 signal.signal(signal.SIGINT, handler),更要小心——自定义信号处理会让 accept() 更容易被中断

recv() 返回空字节意味着客户端已关闭连接

recv() 返回 b'' 不是错误,而是 TCP 连接被对方正常关闭(FIN 包到达)。很多新手误以为是网络故障,接着继续 recv() 或忽略,结果导致死循环或资源泄漏。

  • 每次 recv() 后必须显式判断:if not data: break
  • 不要依赖超时来“等数据”,recv() 在连接关闭时立刻返回空,不等超时
  • 若协议需要粘包处理(如自定义消息头),也要先确认连接是否还活着,再解析;否则可能对 b'' 做解包,触发 Struct.unpack() 报错

多客户端场景下别用单线程轮询 recv()

一个线程里对多个 socket 调用 recv() 是典型阻塞陷阱:只要一个客户端不发数据,整个服务就卡住,其他连接全被饿死。

  • 要么用 select() / poll() / epoll() 做 I/O 多路复用(注意 windowsselect() 是唯一通用选项)
  • 要么用 Threading.Thread 为每个连接启一个线程(适合低并发,注意 threading.stack_size() 和 GIL 影响)
  • 要么用 asyncio + async def handle_client()(推荐,但得确保所有 I/O 都是 async 的,别混用 time.sleep() 或同步 DB 调用)

bind() 前记得 setsockopt(SO_REUSEADDR)

服务重启时经常报 OSError: [Errno 98] Address already in use,本质是 TIME_WAIT 状态占着端口。不设 SO_REUSEADDR,内核拒绝绑定。

立即学习Python免费学习笔记(深入)”;

  • 必须在 bind() 前调用:s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  • Windows 和 Linux 都支持,无需条件判断
  • 别和 SO_REUSEPORT 混淆——后者是 Linux 3.9+ 才有,用于多进程负载均衡,普通服务端不需要

真正难的不是写通连接,而是让 accept() 不崩、recv() 不卡、多连接不互掐、重启不失败。这些点漏掉任何一个,上线后都得半夜爬起来看日志。

text=ZqhQzanResources