核心概念
- 阻塞模式:默认情况下,
socket的recv()方法是阻塞的,这意味着如果你的程序调用client_socket.recv(1024),但客户端还没有发送任何数据,程序会暂停,一直等待,直到收到数据或连接关闭,这对于编写简单的服务器非常有用,但在复杂的应用中需要小心处理。 - 缓冲区大小:
recv(size)方法中的size参数指定了每次最多接收多少字节的数据,这个值不是硬性限制,而是建议值。recv()会返回一个字节串,其长度最多为size。- 如果发送方发送了 500 字节,而
size是 1024,recv()会返回包含全部 500 字节的字节串。 - 如果发送方发送了 2000 字节,而
size是 1024,recv()会先返回前 1024 字节,你需要再次调用recv()来获取剩下的数据。
- 如果发送方发送了 500 字节,而
- 返回值:
- 成功时,
recv()返回一个包含接收到的数据的字节串 (bytes)。 - 如果连接的另一端正常关闭了连接(调用了
close()),recv()会返回一个空字节串 (b''),这是判断客户端是否断开连接的重要标志。 - 如果发生严重错误(如网络中断),
recv()会抛出异常。
- 成功时,
基本接收流程(以服务器为例)
下面是一个最简单的 TCP 服务器,它接收客户端发来的数据并打印出来。

服务器端代码 (server.py)
import socket
# 1. 创建一个 socket 对象
# AF_INET 表示使用 IPv4 地址
# SOCK_STREAM 表示使用 TCP 协议
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 绑定 IP 地址和端口号
# '' 表示监听本机所有可用的网络接口
# 8888 是端口号,范围是 0-65535,建议使用 1024 以上的
server_socket.bind(('', 8888))
# 3. 开始监听,允许的最大连接数是 5
server_socket.listen(5)
print("服务器已启动,正在等待客户端连接...")
# 4. 接受客户端连接
# accept() 是一个阻塞方法,会一直等待直到有客户端连接
# 它返回一个元组:(client_socket, client_address)
# client_socket 是一个用于与该客户端通信的新 socket
# client_address 是客户端的 IP 地址和端口号
client_socket, client_address = server_socket.accept()
print(f"已接受来自 {client_address} 的连接!")
# 5. 接收数据
# 循环接收,直到客户端关闭连接
while True:
# 设置一个缓冲区大小,1024 字节
# recv() 是阻塞的,如果没有数据,程序会停在这里
data = client_socket.recv(1024)
# 如果返回的数据是空字节串,说明客户端已经关闭了连接
if not data:
print(f"客户端 {client_address} 已断开连接。")
break
# 将接收到的字节串解码为字符串(假设是 UTF-8 编码)
# .strip() 用于移除字符串两端的空白字符(如换行符 \n)
message = data.decode('utf-8').strip()
print(f"从 {client_address} 收到消息: {message}")
# 6. 关闭客户端 socket
client_socket.close()
# 7. 关闭服务器 socket
server_socket.close()
客户端代码 (client.py)
为了测试服务器,我们需要一个客户端。
import socket
import time
# 1. 创建 socket 对象
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 连接服务器
# IP 地址是 '127.0.0.1' (本地回环地址),端口号是 8888
server_address = ('127.0.0.1', 8888)
print(f"正在连接到 {server_address}...")
client_socket.connect(server_address)
print("已连接到服务器!")
# 3. 发送数据
messages = [
"你好,服务器!",
"这是第二条消息。",
"请发送一个长一点的消息来测试缓冲区限制,看看服务器是否能正确处理分块接收的数据。"
]
for msg in messages:
# 发送前需要将字符串编码为字节串
client_socket.sendall(msg.encode('utf-8'))
print(f"已发送: {msg}")
time.sleep(1) # 每次发送后等待1秒,方便观察
# 4. 关闭连接
print("发送完毕,关闭连接。")
client_socket.close()
如何运行:
- 先在终端运行
python server.py,你会看到 "服务器已启动..." 的提示。 - 再打开另一个终端,运行
python client.py。 - 观察服务器终端的输出,它会依次打印出客户端发来的三条消息。
重要细节和进阶用法
正确处理长消息(循环接收)
如前所述,recv(1024) 可能无法一次性读取完整的长消息,一个健壮的接收逻辑应该循环读取,直到读取完毕。
一个常见的做法是先读取消息头,获取消息的总长度,然后再循环读取直到凑够这个长度。

改进的服务器端代码(处理长消息):
# ... (前面的代码相同) ...
print(f"已接受来自 {client_address} 的连接!")
while True:
# 1. 先接收消息头(假设前4个字节是消息长度)
header = client_socket.recv(4)
if not header:
break
# 将字节串转换为整数
message_length = int.from_bytes(header, byteorder='big') # 假设使用大端序
print(f"准备接收一条长度为 {message_length} 的消息...")
# 2. 循环接收消息体
received_data = b''
while len(received_data) < message_length:
# 每次最多再接收 1024 字节
chunk = client_socket.recv(1024)
if not chunk: # 如果对方异常断开
print("客户端异常断开。")
break
received_data += chunk
if len(received_data) == message_length:
message = received_data.decode('utf-8')
print(f"完整收到消息: {message}")
else:
print("接收到的数据不完整。")
# ... (后面的代码相同) ...
对应的客户端代码(发送带长度的消息):
# ... (前面的代码相同) ...
messages = [
"你好,服务器!",
"这是一个非常非常非常非常非常长的消息,用来测试服务器的分块接收能力。"
]
for msg in messages:
# 1. 先发送消息长度(4字节,大端序)
msg_length = len(msg.encode('utf-8'))
client_socket.sendall(msg_length.to_bytes(4, byteorder='big'))
# 2. 再发送消息内容
client_socket.sendall(msg.encode('utf-8'))
print(f"已发送消息: '{msg}' (长度: {msg_length})")
time.sleep(1)
# ... (后面的代码相同) ...
非阻塞模式
recv() 的阻塞行为有时会不方便,我们可以将 socket 设置为非阻塞模式,这样 recv() 会立即返回,如果没有数据可读,就会抛出 BlockingIOError 异常。
import socket
# 创建 socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1', 8888))
# 设置为非阻塞模式
s.setblocking(False)
try:
data = s.recv(1024)
except BlockingIOError:
# 没有数据可读,这是预期的行为
print("当前没有数据可读,程序可以继续做其他事情。")
except ConnectionResetError:
print("连接已重置。")
# 在非阻塞模式下,你通常会结合 select, poll 或 asyncio 等机制
# 来等待 socket 变为“可读”状态,然后再调用 recv(),这样可以避免忙等待。
使用 recv_into() 优化性能
如果你需要将接收到的数据直接写入一个预先分配好的缓冲区(bytearray),可以使用 recv_into(),这可以避免一次又一次地分配新的内存来存储 recv() 返回的字节串,对于处理大量数据或高性能场景很有用。
# 创建一个 1024 字节的缓冲区
buffer = bytearray(1024)
# 接收数据并直接填充到 buffer 中
n_bytes_received = client_socket.recv_into(buffer)
# buffer[0:n_bytes_received] 包含了实际接收到的数据
message = buffer[0:n_bytes_received].decode('utf-8')
print(f"收到 {n_bytes_received} 字节的数据: {message}")
| 方法 | 描述 | 特点 |
|---|---|---|
recv(size) |
最常用的方法,从 socket 接收数据。 | 返回 bytes 对象,如果连接关闭,返回空 b'',默认是阻塞的。 |
recv_into(buffer) |
将接收到的数据直接写入一个可写的缓冲区(如 bytearray)。 |
性能更高,避免了内存分配,返回接收到的字节数。 |
recvfrom(size) |
主要用于 UDP 协议。 | 除了返回数据,还会返回发送方的地址元组 (ip, port)。 |
recvfrom_into(buffer) |
UDP 版本的 recv_into。 |
同样用于高性能 UDP 接收,并返回发送方地址。 |
对于初学者来说,掌握 socket.recv(size) 在阻塞模式下的基本用法,并理解如何通过循环 recv() 来处理长消息,是最重要的。 非阻塞模式和高级优化可以在后续需要性能提升时再深入学习。
