核心概念
在 Python 的 socket 编程中,主要有两种超时:

- 连接超时 (
connect timeout): 客户端在尝试连接到服务器时,如果在指定时间内没有成功建立连接,就会抛出超时异常。 - 操作超时 (
socket timeout或I/O timeout): 在已经建立连接的套接字上进行发送(send)或接收(recv)操作时,如果在指定时间内没有完成数据交换,就会抛出超时异常。
设置连接超时
连接超时通常在创建套接字后、调用 connect() 方法之前设置,这是通过 settimeout() 方法实现的。
工作原理:
- 调用
sock.settimeout(timeout_value)。 - 然后调用
sock.connect(address)。 - 如果在
timeout_value秒内连接成功,程序继续执行。 - 如果超时,
socket模块会抛出socket.timeout异常。
示例代码:
import socket
import time
# 目标地址和端口
host = 'www.google.com' # 或者一个不存在的地址,如 '192.0.2.1' (RFC 5737保留地址)
port = 80
try:
# 1. 创建一个 socket 对象
# AF_INET 表示使用 IPv4 地址
# SOCK_STREAM 表示使用 TCP 协议
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 设置连接超时时间为 5 秒
# 5 秒内无法连接,将触发超时
s.settimeout(5.0)
print(f"正在尝试连接到 {host}:{port}...")
# 3. 尝试连接
# 这一步会阻塞,直到连接建立或超时
start_time = time.time()
s.connect((host, port))
end_time = time.time()
print(f"连接成功!耗时: {end_time - start_time:.2f} 秒")
# 4. 如果连接成功,可以进行数据收发...
# s.send(b"GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n")
# response = s.recv(4096)
# print(response.decode('utf-8'))
except socket.timeout:
print("错误:连接超时!服务器在指定时间内没有响应。")
except ConnectionRefusedError:
print("错误:连接被拒绝!服务器可能没有运行或防火墙阻止了连接。")
except socket.error as e:
print(f"发生 socket 错误: {e}")
finally:
# 5. 确保套接字被关闭
if 's' in locals() and s:
print("正在关闭套接字。")
s.close()
设置操作超时
操作超时用于控制 send() 和 recv() 等I/O操作,它同样使用 settimeout() 方法,但通常在连接建立之后设置。

工作原理:
- 在
connect()成功后,调用sock.settimeout(timeout_value)。 - 之后调用
sock.send(data)或sock.recv(buffer_size)。 - 如果操作在指定时间内没有完成(对端没有发送数据,
recv一直等待),就会抛出socket.timeout异常。
示例代码:
import socket
import time
host = 'time.nist.gov' # 一个提供时间服务的服务器
port = 13 # daytime 端口,连接后会返回当前时间
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
print("已连接到时间服务器。")
# 设置操作超时为 3 秒
s.settimeout(3.0)
# daytime 服务通常在连接后立即发送数据,不需要 send
# 但如果服务器不响应,recv 会在这里超时
print("正在等待服务器响应...")
start_time = time.time()
data = s.recv(1024) # 这一步可能会阻塞
end_time = time.time()
if data:
print(f"收到数据: {data.decode('utf-8').strip()}")
print(f"接收耗时: {end_time - start_time:.2f} 秒")
except socket.timeout:
print("错误:操作超时!在等待数据时超时。")
except socket.error as e:
print(f"发生 socket 错误: {e}")
finally:
if 's' in locals() and s:
print("正在关闭套接字。")
s.close()
更精细的控制:setblocking()
除了 settimeout(),还有一个更底层的方法 setblocking(flag)。
s.setblocking(True): 阻塞模式 (默认)。connect,send,recv等操作会一直等待,直到完成或发生错误,这相当于s.settimeout(None)。s.setblocking(False): 非阻塞模式。connect,send,recv操作会立即返回,如果操作不能立即完成,会抛出BlockingIOError异常,这相当于s.settimeout(0.0)。
非阻塞模式的使用场景:
非阻塞模式通常用于更高级的并发编程,例如结合 select 模块来监控多个套接字的状态,你不会在一个循环里不断尝试 recv,而是让 select 告诉你哪个套接字“准备好了”,然后再去操作它。

示例(非阻塞模式概念):
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setblocking(False) # 设置为非阻塞模式
try:
# 非阻塞模式下,connect会立即返回
s.connect(('www.google.com', 80))
except BlockingIOError:
# 这个异常是正常的,表示连接操作已经开始,但尚未完成
print("连接操作已启动,正在后台进行...")
# 现在你可以做其他事情,或者使用 select 来检查套接字是否可写(连接成功)
# 这是一个简化的概念,实际使用会更复杂
上下文管理器:with 语句
从 Python 3.5 开始,socket 对象支持上下文管理器协议,可以使用 with 语句来自动管理资源的关闭,使代码更简洁、安全。
结合超时使用 with 语句:
import socket
host = 'www.python.org'
port = 80
# 使用 with 语句,套接字会在块结束时自动关闭
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(5.0) # 在 with 块内设置超时
try:
s.connect((host, port))
print("连接成功!")
# 可以在这里进行收发操作
except socket.timeout:
print("连接超时!")
except socket.error as e:
print(f"连接错误: {e}")
# with 块结束后,s.close() 会被自动调用,无需手动关闭
print("套接字已自动关闭。")
总结与最佳实践
| 方法 | 作用 | 超时类型 | 代码位置 |
|---|---|---|---|
s.settimeout(seconds) |
设置套接字超时 | 连接超时 / 操作超时 | socket 创建后,connect 前或 connect 后 |
s.setblocking(True/False) |
设置阻塞模式 | 连接超时 / 操作超时 | socket 创建后 |
s.gettimeout() |
获取当前超时值 | - | 任何需要查询超时设置的地方 |
最佳实践:
- 总是使用
try...except块:任何涉及网络 I/O 的操作都应该被包裹在try...except块中,以捕获socket.timeout和其他可能的socket.error异常。 - 在
finally块中关闭套接字:确保无论是否发生异常,套接字资源都能被正确释放,或者,优先使用with语句。 - 为不同的操作设置合理的超时:连接超时可以稍长(如5-10秒),而数据收发超时可以稍短(如2-5秒),具体取决于网络环境和应用场景。
- 区分
timeout和ConnectionRefusedError:超时是等待时间过长,而连接被拒绝是目标主机明确拒绝了你的连接请求(端口未开放),它们是两种不同的情况,应该分别处理。
