TCP服务端应使用with管理socket生命周期,捕获KeyboardInterrupt、OSError等异常,选择多线程或asyncio实现并发,循环处理recv/send以应对粘包与半包问题。

用 socket 模块实现一个健壮、可维护的 TCP 服务端,核心是处理连接管理、异常、并发和资源释放,而不是只写几行 bind + listen + accept。
使用上下文管理器确保 socket 正确关闭
手动调用 close() 容易遗漏,尤其在异常路径中。推荐用 with 语句自动管理 socket 生命周期:
- 服务端 socket 用
socket.socket(...)创建后立即包进with - 每个客户端连接也应在独立的
with中处理(或显式try/finally) - 避免“忘记
close()导致端口占用、文件描述符泄漏”这类低级但高频问题
必须捕获常见异常并合理退出
TCP 服务端运行中会遇到多种中断信号和网络异常,不能让未捕获异常直接崩溃:
-
KeyboardInterrupt(Ctrl+C):应优雅关闭监听 socket 并退出 -
OSError或ConnectionAbortedError:客户端异常断开时 recv 返回空或抛异常,需跳过或清理连接 -
socket.timeout:若设置了settimeout(),需捕获并继续循环,而非终止服务
支持多客户端需明确选择并发模型
单线程 accept + 阻塞 recv 只能服务一个客户。生产环境常用两种方式:
立即学习“Python免费学习笔记(深入)”;
-
多线程:每 accept 到一个连接,就
threading.Thread(target=handle_client, args=(conn, addr)).start();适合 I/O 密集、连接数不超百的场景 -
select / epoll / asyncio:用事件驱动避免线程开销;Python 3.7+ 推荐
asyncio.start_server(),代码简洁且性能好 - 不建议用简单 for 循环轮询多个连接(效率低、不可扩展)
收发数据要处理粘包与半包
TCP 是流式协议,recv(n) 不保证一次收齐 n 字节,send() 也可能只发出部分数据:
- 接收时用循环读取直到满足预期长度,或按协议解析(如首 4 字节为包长)
- 发送时检查
send()返回值,若小于待发字节数,需缓存剩余部分并重试 - 不要假设
recv(1024)一定收到 1024 字节 —— 这是新手最常踩的坑










