2. Python Socket 模块:核心 API 详解

Socket 编程,说白了就是让两台机器能互相传数据。Python 的 socket 模块把这套东西封装得挺顺手,但每个函数背后都有它的脾气。我刚开始写网络程序时,总觉得这些 API 背下来就行,后来踩过几次坑才明白——理解它们的工作顺序和阻塞行为,比记住函数名重要得多。

这一节我们逐个拆解。你跟着我过一遍,心里就有谱了。

2.1 socket():创建端点

一切从 socket() 开始。它创建一个通信端点,返回一个套接字对象。

import socket

# 创建一个 TCP 套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

两个参数决定了套接字的“身份”:

参数 常用值 含义
family AF_INET IPv4 地址族,最常用
AF_INET6 IPv6 地址族
type SOCK_STREAM TCP,面向连接、可靠
SOCK_DGRAM UDP,无连接、不可靠
我的习惯:写 HTTP 服务器只用 AF_INET + SOCK_STREAM。除非你明确需要 UDP 或 IPv6,否则别乱改。

2.2 bind():绑定地址和端口

服务器端必须绑定一个固定的 IP 和端口,客户端才知道往哪连。

sock.bind(('0.0.0.0', 8080))

第一个参数是 IP 地址,第二个是端口号。

  • '0.0.0.0':监听所有网络接口。我建议服务器都用这个,省得换网段就断连。
  • '127.0.0.1':只允许本机访问。调试时用这个更安全。
  • 端口号:0–1023 需要 root 权限,开发时选 8080、8000 这种就行。
我曾经踩过的坑:端口被占用时 bind() 会抛 OSError。如果你频繁重启服务器,记得设置 sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1),否则要等 2–4 分钟才能重新绑定。

2.3 listen():开启监听

listen() 告诉操作系统:这个套接字准备好接受连接了。参数是等待队列的长度。

sock.listen(5)

这个数字不是最大连接数,而是未完成三次握手的连接数上限。我一般设 5 或 10,高并发场景可以调大,但别超过 128。

嗯,这里要注意:listen() 之后,套接字就变成“被动”的了——它不再主动发数据,只等着别人来连。

2.4 accept():接受连接

这是服务器端最核心的阻塞调用。它等在那里,直到有客户端连上来。

conn, addr = sock.accept()
print(f'收到来自 {addr} 的连接')

返回值有两个:

  • conn:新的套接字对象,专门和这个客户端通信。
  • addr:客户端的 (IP, 端口) 元组。

关键理解:accept() 返回的 conn 和原来的 sock 不是同一个东西。sock 继续监听,conn 负责收发。你想想看,如果监听套接字也参与收发,那新连接就没人管了。

2.5 connect():发起连接

客户端用 connect() 主动连服务器。它也是阻塞的,直到连接建立或超时。

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8080))

参数是一个 (host, port) 元组。如果服务器没在监听,会抛 ConnectionRefusedError。我在项目中遇到过这种情况——服务器还没启动,客户端就急着连,结果程序直接崩了。所以客户端一定要加 try-except。

2.6 send() 与 recv():收发数据

连接建立后,数据就靠这两个函数传。

# 发送
conn.send(b'Hello, client!')

# 接收
data = conn.recv(1024)

send() 返回实际发送的字节数。不一定一次发完所有数据,所以需要循环发送。

recv() 的参数是缓冲区大小。我习惯用 1024 或 4096。如果对方关闭连接,recv() 返回空字节串 b''——这是判断连接是否断开的标志。

我曾经踩过的坑:send() 和 recv() 都是阻塞的。如果对方不发送数据,recv() 会一直等下去。生产环境一定要设置超时:sock.settimeout(5.0)。

2.7 close():释放资源

通信结束后,必须关闭套接字。否则端口一直被占用,别人连不上。

conn.close()
sock.close()

我建议用 with 语句自动管理,或者放在 finally 块里。你想想看,如果程序中途异常退出,没关的套接字就成了“孤儿连接”。

2.8 完整流程:一张图说清楚

下面这张图把服务器和客户端的调用顺序串起来了。你对照着看,逻辑就清晰了。

Socket 编程核心流程 服务器 socket() bind() listen() accept() 阻塞等待 recv() / send() close() 客户端 socket() connect() send() / recv() close() 三次握手 连接建立

2.9 避坑清单

最后,我把这些年遇到的典型问题列出来。你写代码时对照着检查,能省不少调试时间。

问题 原因 解决办法
Address already in use 端口被上一个进程占用 设置 SO_REUSEADDR
recv() 卡死 对方没发数据,也没关闭 设置 settimeout()
send() 没发完 TCP 缓冲区满,只发了部分 循环发送,检查返回值
Connection refused 服务器没在监听 客户端加 try-except
端口被占用 没调用 close() 用 with 或 finally 确保关闭
我的建议:刚开始学的时候,把上面这张流程图打印出来贴在显示器旁边。每次写 socket 代码,先看一遍调用顺序。顺序对了,程序就成功了一半。