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,无连接、不可靠 |
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 完整流程:一张图说清楚
下面这张图把服务器和客户端的调用顺序串起来了。你对照着看,逻辑就清晰了。
2.9 避坑清单
最后,我把这些年遇到的典型问题列出来。你写代码时对照着检查,能省不少调试时间。
| 问题 | 原因 | 解决办法 |
|---|---|---|
Address already in use |
端口被上一个进程占用 | 设置 SO_REUSEADDR |
recv() 卡死 |
对方没发数据,也没关闭 | 设置 settimeout() |
send() 没发完 |
TCP 缓冲区满,只发了部分 | 循环发送,检查返回值 |
Connection refused |
服务器没在监听 | 客户端加 try-except |
| 端口被占用 | 没调用 close() |
用 with 或 finally 确保关闭 |