Socket 通信流程
想象一下你正在打电话:

- 买电话(创建 Socket):你需要一部电话(一个 socket 对象)。
- 插上电话线并绑定号码(Bind & Listen):你把电话插上墙(绑定到一个 IP 地址和端口),并设置电话为响铃状态(监听来自客户端的连接)。
- 等待来电(Accept):电话响了,你接起电话(接受一个连接),这时,你和来电者之间建立了一条独立的通话线路(一个新的 socket)。
- 通话(Send/Recv):你和来电者通过这条线路开始对话(发送和接收数据)。
- 挂断(Close):通话结束,双方都挂断电话(关闭 socket)。
Python 的 socket 编程完全遵循这个逻辑。
最简单的服务器端示例(TCP)
这是一个最基础的 TCP 服务器,它只接受一个客户端的连接,接收一条消息,然后关闭。
# server_simple.py
import socket
# 1. 创建一个 socket 对象
# socket.AF_INET 表示使用 IPv4 地址
# socket.SOCK_STREAM 表示使用 TCP 协议
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 绑定 IP 地址和端口号
# '0.0.0.0' 表示监听本机所有可用的网络接口
# 8888 是端口号,可以自定义,但要注意不要与其他程序冲突
server_address = ('0.0.0.0', 8888)
server_socket.bind(server_address)
# 3. 开始监听 incoming connections
# 5 是挂起队列的最大连接数
server_socket.listen(5)
print(f"服务器启动,正在监听 {server_address[0]}:{server_address[1]}...")
# 4. 等待并接受客户端连接
# accept() 是一个阻塞函数,程序会在这里等待,直到有客户端连接
# 它会返回一个 (conn, addr) 元组
# conn: 一个新的 socket 对象,用于与这个特定的客户端通信
# addr: 客户端的地址 (IP 地址, 端口号)
conn, client_addr = server_socket.accept()
# 5. 与客户端通信
print(f"已接受来自 {client_addr} 的连接!")
# 接收客户端发送的数据
# recv(1024) 表示每次最多接收 1024 字节的数据
# recv() 也是一个阻塞函数
data = conn.recv(1024)
print(f"收到来自客户端的消息: {data.decode('utf-8')}")
# 向客户端发送响应
response = "你好,客户端!你的消息已收到。"
conn.sendall(response.encode('utf-8'))
# 6. 关闭连接
# 关闭与客户端的通信 socket
conn.close()
# 关闭服务器监听 socket
server_socket.close()
print("连接已关闭,服务器退出。")
如何运行这个例子?
- 保存代码:将上面的代码保存为
server_simple.py。 - 运行服务器:在终端中运行
python server_simple.py,你会看到 "服务器启动..." 的提示,程序会停在那里等待连接。 - 测试客户端:打开另一个终端,使用
netcat(nc) 或 Python 客户端来连接。- 使用
netcat(推荐,因为它简单):nc localhost 8888,然后输入一些文字并按回车。 - 或者,你可以运行下面这个简单的客户端脚本。
- 使用
一个简单的客户端示例(用于测试)
# client_simple.py
import socket
# 1. 创建一个 socket 对象
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 连接服务器
server_address = ('127.0.0.1', 8888) # 连接到本地的 8888 端口
client_socket.connect(server_address)
print(f"已连接到服务器 {server_address}")
# 3. 发送数据
message = "你好,服务器!"
client_socket.sendall(message.encode('utf-8'))
# 4. 接收服务器的响应
data = client_socket.recv(1024)
print(f"收到服务器的响应: {data.decode('utf-8')}")
# 5. 关闭连接
client_socket.close()
测试步骤:
- 在一个终端运行
python server_simple.py。 - 在另一个终端运行
python client_simple.py。 - 观察服务器终端的输出,它会显示客户端的连接和消息。
进阶:能够处理多个客户端的服务器
上面的服务器一次只能服务一个客户端,因为它在 accept() 之后会一直等待数据,无法再接受新的连接,要处理多个客户端,最简单的方法是使用多线程。

工作原理: 主线程负责监听和接受新的连接,每当有一个新的客户端连接时,就创建一个新线程来专门处理这个客户端的通信,而主线程则继续回去监听下一个连接。
# server_multi_thread.py
import socket
import threading
# 定义一个处理单个客户端连接的函数
def handle_client_connection(conn, client_addr):
"""与单个客户端进行通信"""
print(f"[新连接] {client_addr} 已连接。")
try:
while True:
# 接收数据
data = conn.recv(1024)
if not data:
# recv() 返回空数据,说明客户端已关闭连接
print(f"[客户端断开] {client_addr} 断开了连接。")
break
print(f"[来自 {client_addr}] {data.decode('utf-8')}")
# 发送响应
response = f"服务器已收到你的消息: {data.decode('utf-8')}"
conn.sendall(response.encode('utf-8'))
except ConnectionResetError:
print(f"[连接错误] {client_addr} 强制断开了连接。")
finally:
# 确保连接被关闭
conn.close()
print(f"[连接关闭] 与 {client_addr} 的连接已关闭。")
def main():
# 1. 创建并绑定服务器 socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = ('0.0.0.0', 8888)
server_socket.bind(server_address)
server_socket.listen(5)
print(f"服务器启动,正在监听 {server_address}...")
try:
while True:
# 2. 循环接受新的连接
conn, client_addr = server_socket.accept()
# 3. 为每个新连接创建一个新线程
# target 指定线程要执行的函数
# args 指定传递给函数的参数
client_thread = threading.Thread(target=handle_client_connection, args=(conn, client_addr))
# 将线程设置为守护线程,这样当主线程退出时,子线程也会强制退出
client_thread.daemon = True
# 启动线程
client_thread.start()
print(f"[活动连接数] {threading.active_count() - 1}") # 减去主线程本身
except KeyboardInterrupt:
print("\n服务器正在关闭...")
finally:
server_socket.close()
print("服务器已关闭。")
if __name__ == '__main__':
main()
如何测试多线程服务器?
- 运行
python server_multi_thread.py。 - 打开两个或更多终端,每个终端都运行
python client_simple.py(或者用nc连接)。 - 你会发现服务器能够同时响应所有连接的客户端,而不会阻塞其他连接。
关键点解析
| 代码/概念 | 解释 |
|---|---|
socket.socket(family, type) |
创建一个 socket 对象。family 通常是 AF_INET (IPv4) 或 AF_INET6 (IPv6)。type 通常是 SOCK_STREAM (TCP) 或 SOCK_DGRAM (UDP)。 |
socket.bind(address) |
将 socket 绑定到一个特定的地址和端口,地址是 ('host', port) 元组。'0.0.0.0' 表示监听所有可用的网络接口。 |
socket.listen(backlog) |
开始监听传入的连接。backlog 是挂起队列的最大连接数。 |
socket.accept() |
接受一个连接,它返回一个新的 socket 对象 (conn) 和客户端的地址 (addr),这是一个阻塞调用。 |
socket.recv(bufsize) |
接收数据。bufsize 是一次接收的最大字节数,这也是一个阻塞调用,如果没有数据,它会等待,当客户端关闭连接时,它会返回空数据 (b'')。 |
socket.sendall(data) |
发 |

