Socket(套接字)是网络编程的底层接口,可以把它理解为网络上的“插座”:两台机器上的程序通过各自的 socket 建立连接,就能像读写文件一样收发数据。Python 的 socket 模块提供了标准的 BSD Socket API,封装良好,可以轻松实现跨平台的网络通信。
核心概念:TCP vs UDP
两者都是传输层协议,但设计目标不同,选型直接决定了通信的行为。
- TCP(传输控制协议)
- 面向连接:通信前需要通过“三次握手”建立连接,结束后“四次挥手”断开。
- 可靠传输:数据不会丢失、不会乱序、不会重复,底层有超时重传、流量控制等机制。
- 流式传输:发送方多次发送的数据在接收方可能被合并或拆分,没有天然的消息边界。实际编程中需要自己定义消息边界(如长度前缀、特殊分隔符)。
- 适用场景:HTTP、WebSocket、文件传输、邮件、远程登录等——任何不能容忍数据丢失的场景。
- UDP(用户数据报协议)
- 无连接:直接发送数据报,不需要建立连接,开销低。
- 不保证可靠:数据可能丢包、乱序、重复,应用程序需要自行处理。
- 保留消息边界:每个数据报都是一个完整单位,不会粘包。
- 适用场景:视频直播、语音通话、实时游戏、DNS 查询等——能容忍少量丢包但要求低延迟的场景。
TCP 通信实现
服务端(server)
import socket
def tcp_server(host='127.0.0.1', port=9999):
# 1. 创建 socket 对象 (AF_INET 表示 IPv4, SOCK_STREAM 表示 TCP)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_socket:
# 2. 绑定地址和端口
server_socket.bind((host, port))
# 3. 开始监听,参数是等待连接的最大排队数
server_socket.listen(5)
print(f"TCP 服务端启动,监听 {host}:{port}")
while True:
# 4. 接受客户端连接,返回新 socket 和客户端地址
client_socket, addr = server_socket.accept()
with client_socket:
print(f"客户端 {addr} 已连接")
# 5. 接收数据(最多接收 1024 字节)
data = client_socket.recv(1024)
if data:
print(f"收到: {data.decode('utf-8')}")
# 6. 发送响应
client_socket.sendall(f"服务端已收到: {data.decode('utf-8')}".encode('utf-8'))
# 循环结束后自动关闭 server_socket
客户端(client)
import socket
def tcp_client(host='127.0.0.1', port=9999, message='Hello TCP'):
# 1. 创建 socket,同样使用 AF_INET + SOCK_STREAM
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_socket:
# 2. 连接服务端
client_socket.connect((host, port))
# 3. 发送数据
client_socket.sendall(message.encode('utf-8'))
# 4. 接收响应
response = client_socket.recv(1024)
print(f"服务端响应: {response.decode('utf-8')}")
运行要点
- 先启动服务端,后启动客户端。服务端
accept()会阻塞等待客户端连接。 - 上述示例是一次性交互,实际长连接需要循环收发。
sendall()保证全部数据发出,而send()可能只发送一部分,需要自己处理余下内容。- TCP 粘包问题:如果连续发送多次小数据,接收方可能一次
recv()读到多个消息。简单的处理方式是用特殊字符(如\n)分隔消息,或在消息前添加固定长度的长度字段。
UDP 通信实现
UDP 通信双方都可以直接用 sendto 和 recvfrom,没有连接的概念。
简单回显服务端
import socket
def udp_server(host='127.0.0.1', port=9999):
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as server_socket:
server_socket.bind((host, port))
print(f"UDP 服务端启动,监听 {host}:{port}")
while True:
# 接收数据和客户端地址
data, addr = server_socket.recvfrom(1024)
print(f"收到来自 {addr} 的消息: {data.decode('utf-8')}")
# 回复客户端
server_socket.sendto(f"已收到: {data.decode('utf-8')}".encode('utf-8'), addr)
客户端
import socket
def udp_client(host='127.0.0.1', port=9999, message='Hello UDP'):
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as client_socket:
# 直接发送数据报,不需要 connect
client_socket.sendto(message.encode('utf-8'), (host, port))
# 接收回复
data, server_addr = client_socket.recvfrom(1024)
print(f"服务端回复: {data.decode('utf-8')}")
UDP 也可以使用 connect()
虽然 UDP 无连接,但调用 connect((host, port)) 后可以绑定远程地址,后续直接用 send() 和 recv()(不需要 sendto),方便固定通信对象的程序。
关键注意事项
- 异常处理:网络通信不稳定,实际代码中务必用
try/except捕获socket.error、ConnectionResetError等异常,避免程序崩溃。 - 资源释放:尽量使用
with语句或finally确保 socket 关闭;不关闭会造成端口占用或资源泄漏。 - 字节编码:网络传输只能是字节流,发送字符串前需
.encode('utf-8'),接收后需.decode('utf-8')。双方编码必须一致。 - 阻塞与非阻塞:默认 socket 是阻塞模式,
accept()、recv()会卡住整个线程。高并发场景下可结合select、asyncio或使用多线程/多进程处理。 - 端口复用:服务端重启时可能遇到端口仍被占用(TIME_WAIT 状态),可在
bind()前设置server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)快速重用端口。
掌握这些基础后,就可以动手实现简单的网络工具、文件传输脚本或自定义协议的服务,也为理解 Web 框架底层原理和网络编程模型打下扎实基础。