网络编程是连接程序与世界的桥梁。从底层的 socket 到高层的 HTTP 请求,本文带你掌握 Python 中网络通信的每个层次。
目录
- 网络编程层次概览
- Socket 基础:TCP 客户端/服务端
- UDP 编程:无连接通信
- HTTP 客户端:requests vs urllib
- 异步 HTTP:aiohttp
- 用 Python 写 Web 服务器
- WebSocket 实时通信
- 网络调试技巧
1. 网络编程层次概览
应用层:HTTP、FTP、DNS
│
传输层:TCP(可靠连接)、UDP(无连接)
│
网络层:IP(寻址与路由)
│
链路层:以太网、WiFi
Python 可在每个层次编程:
| 层次 | Python 工具 | 难度 |
|---|---|---|
| 应用层(HTTP) | requests, aiohttp, urllib | ⭐⭐ |
| 传输层 | socket | ⭐⭐⭐ |
| 底层 | scapy(数据包操作) | ⭐⭐⭐⭐ |
TCP 核心原理与连接建立
要写出高效可靠的网络程序,必须理解 TCP 的工作机制。TCP 是面向连接的可靠传输协议,其核心特性包括三次握手建立连接、四次挥手断开连接、滑动窗口流量控制和拥塞控制算法。
三次握手与四次挥手
客户端 服务端
│ SYN seq=x │
│ ─────────────────▶ │
│ │
│ SYN seq=y, ACK=x+1 │
│ ◀───────────────── │
│ │
│ ACK=y+1 │
│ ─────────────────▶ │
│ 【连接建立】 │
Python 的 socket 模块在 connect() 和 accept() 时自动完成三次握手,开发者无需手动发送 SYN 包。但理解这个过程对于排查连接问题至关重要:如果服务端 SYN backlog 队列满,新的连接请求会被静默丢弃,客户端表现为 connect timeout。
滑动窗口与拥塞控制
TCP 通过滑动窗口机制实现流量控制(接收方告知发送方自己的缓冲能力)和拥塞控制(发送方根据网络拥塞程度动态调整发送速率)。现代 Linux 内核默认使用 CUBIC 拥塞控制算法,在高带宽延迟积网络(如跨国链路)中,BBR 算法表现更优:
# 查看当前拥塞控制算法
cat /sys/class/net/eth0/queues/rx-0/rps_cpus
sysctl net.ipv4.tcp_congestion_control
# 切换到 BBR(需要内核 4.9+)
sudo sysctl -w net.ipv4.tcp_congestion_control=bbr
调优建议:对于高并发服务端,
net.ipv4.tcp_tw_reuse = 1可复用 TIME_WAIT 状态的连接,但tcp_tw_recycle在 NAT 环境下会导致问题(已被内核 4.12+ 移除),应避免使用。
2. Socket 基础:TCP 客户端/服务端
2.1 TCP 服务端
import socket
import threading
def handle_client(conn, addr):
"""处理客户端连接"""
print(f"客户端 {addr} 已连接")
with conn:
while True:
data = conn.recv(1024)
if not data:
break
print(f"收到: {data.decode()}")
conn.sendall(b"Echo: " + data)
print(f"客户端 {addr} 已断开")
def start_server(host="0.0.0.0", port=8888):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((host, port))
s.listen(5)
print(f"服务器启动于 {host}:{port}")
while True:
conn, addr = s.accept()
thread = threading.Thread(target=handle_client, args=(conn, addr))
thread.start()
if __name__ == "__main__":
start_server()
2.2 TCP 客户端
import socket
def send_message(message, host="127.0.0.1", port=8888):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((host, port))
s.sendall(message.encode())
response = s.recv(1024)
print(f"响应: {response.decode()}")
send_message("Hello, Server!")
2.3 使用 selectors 实现高效 I/O(单线程多连接)
import selectors
import socket
sel = selectors.DefaultSelector()
def accept(sock):
conn, addr = sock.accept()
conn.setblocking(False)
sel.register(conn, selectors.EVENT_READ, read)
def read(conn):
data = conn.recv(1024)
if data:
conn.send(data)
else:
sel.unregister(conn)
conn.close()
server = socket.socket()
server.bind(("0.0.0.0", 8888))
server.listen()
server.setblocking(False)
sel.register(server, selectors.EVENT_READ, accept)
while True:
events = sel.select()
for key, mask in events:
callback = key.data
callback(key.fileobj)
3. UDP 编程:无连接通信
import socket
# UDP 服务端
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
s.bind(("0.0.0.0", 9999))
print("UDP 服务器启动")
while True:
data, addr = s.recvfrom(1024)
print(f"来自 {addr}: {data.decode()}")
s.sendto(b"Received: " + data, addr)
# UDP 客户端
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
s.sendto(b"Hello UDP", ("127.0.0.1", 9999))
data, _ = s.recvfrom(1024)
print(data.decode())
4. HTTP 客户端:requests vs urllib
4.1 requests(最常用)
import requests
# GET
resp = requests.get("https://api.github.com/users/python")
print(resp.status_code)
print(resp.json()["public_repos"])
# 带参数
resp = requests.get("https://httpbin.org/get", params={"key": "value"})
# POST JSON
resp = requests.post(
"https://httpbin.org/post",
json={"name": "Alice", "age": 25},
headers={"Authorization": "Bearer token123"},
)
# 下载文件
resp = requests.get("https://example.com/large.zip", stream=True)
with open("large.zip", "wb") as f:
for chunk in resp.iter_content(chunk_size=8192):
f.write(chunk)
# Session(保持 cookies/连接池)
with requests.Session() as s:
s.headers.update({"User-Agent": "MyApp/1.0"})
s.get("https://example.com/login")
4.2 urllib(标准库,无需安装)
import urllib.request
import urllib.parse
# GET
with urllib.request.urlopen("https://httpbin.org/get") as resp:
print(resp.read().decode())
# POST
data = urllib.parse.urlencode({"key": "value"}).encode()
req = urllib.request.Request("https://httpbin.org/post", data=data, method="POST")
with urllib.request.urlopen(req) as resp:
print(resp.read().decode())
4.3 httpx(现代化 HTTP 客户端)
# pip install httpx
import httpx
# 同步 API(兼容 requests)
resp = httpx.get("https://api.example.com/items")
# 异步 API(原生支持 async/await)
async with httpx.AsyncClient() as client:
resp = await client.get("https://api.example.com/items")
print(resp.json())
# HTTP/2 支持
client = httpx.Client(http2=True)
5. 异步 HTTP:aiohttp
# pip install aiohttp
import aiohttp
import asyncio
async def fetch_all(urls):
async with aiohttp.ClientSession() as session:
tasks = [fetch_one(session, url) for url in urls]
return await asyncio.gather(*tasks)
async def fetch_one(session, url):
async with session.get(url) as resp:
return await resp.text()
urls = ["https://httpbin.org/get"] * 5
results = asyncio.run(fetch_all(urls))
print(f"获取 {len(results)} 个页面")
6. 用 Python 写 Web 服务器
6.1 最简 HTTP 服务器
# 一行命令启动文件服务器
# python -m http.server 8000
# 程序化使用
from http.server import HTTPServer, SimpleHTTPRequestHandler
server = HTTPServer(("0.0.0.0", 8000), SimpleHTTPRequestHandler)
print("Serving on port 8000")
server.serve_forever()
6.2 自定义请求处理
from http.server import HTTPServer, BaseHTTPRequestHandler
import json
class APIHandler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == "/api/users":
data = [{"id": 1, "name": "Alice"}]
self._send_json(data)
else:
self.send_error(404)
def do_POST(self):
content_length = int(self.headers['Content-Length'])
body = self.rfile.read(content_length)
data = json.loads(body)
self.send_response(201)
self._send_json({"created": True, "data": data})
def _send_json(self, data):
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(json.dumps(data).encode())
server = HTTPServer(("0.0.0.0", 8000), APIHandler)
server.serve_forever()
Python 网络编程性能优化
网络服务的性能瓶颈往往不在计算而在 I/O。理解不同 I/O 模型的适用场景,是写出高性能网络程序的关键。
| I/O 模型 | Python 实现 | 适用场景 | 并发能力 |
|---|---|---|---|
| 阻塞 I/O | threading | 简单应用,低并发 | ~1000 线程 |
| 非阻塞 I/O | select/poll/epoll | 高并发连接,无计算 | 万级连接 |
| 多路复用 | selectors | 现代标准,跨平台 | 万级连接 |
| 异步 I/O | asyncio + aiohttp | 现代推荐方案 | 十万级连接 |
线程池 vs 异步 I/O:线程池的硬伤是 GIL(全局解释器锁),纯 Python 代码无法真正并行;但 threading 处理 I/O 密集型任务时(如网络请求),GIL 会在 I/O 操作时释放,此时线程池仍然有效。对于 CPU 密集型 + 网络 I/O 混合场景,asyncio 配合线程池执行器是最佳组合:
import asyncio
from concurrent.futures import ThreadPoolExecutor
# CPU 密集型计算 + 网络 I/O
async def mixed_work():
loop = asyncio.get_running_loop()
# 网络 I/O:asyncio 原生
http_task = asyncio.create_task(fetch_api())
# CPU 密集型:放到线程池避免阻塞事件循环
with ThreadPoolExecutor(max_workers=4) as pool:
cpu_result = await loop.run_in_executor(
pool,
heavy_computation,
data
)
api_result = await http_task
return process(cpu_result, api_result)
Zero-Copy 与内存优化
对于文件传输等高吞吐量场景,Python 3.3+ 提供了 socket.sendfile() 实现零拷贝传输——数据直接从内核文件缓存发送到网卡,不经过用户空间:
import socket
def send_file_zero_copy(sock: socket.socket, file_path: str):
"""零拷贝文件传输,CPU 占用极低"""
with open(file_path, 'rb') as f:
# sendfile 直接将文件内容从内核推送到 socket
# offset=None 表示从当前位置开始
sock.sendfile(f)
适用条件:
sendfile()要求传输的是磁盘文件而非内存对象;对于加密传输(如 TLS),零拷贝不可用,因为数据需要先解密/加密才能发送。
7. WebSocket 实时通信
# pip install websockets
import asyncio
import websockets
async def handler(websocket, path):
async for message in websocket:
print(f"收到: {message}")
await websocket.send(f"Echo: {message}")
async def main():
async with websockets.serve(handler, "localhost", 8765):
print("WebSocket server on ws://localhost:8765")
await asyncio.Future() # 永远运行
asyncio.run(main())
# 客户端
async def ws_client():
async with websockets.connect("ws://localhost:8765") as ws:
await ws.send("Hello WebSocket!")
response = await ws.recv()
print(response)
asyncio.run(ws_client())
网络协议安全:TLS/SSL 加密通信
生产环境的网络通信必须使用 TLS 加密,防止中间人攻击和数据窃听。Python 3.7+ 的 ssl 模块已经内置了对 OpenSSL 的完整封装。
TLS 服务端配置
import ssl
import socket
# 创建 TLS 上下文
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
# 加载服务器证书和私钥
context.load_cert_chain(certfile='server.crt', keyfile='server.key')
# 强制要求客户端证书(双向 TLS)
# context.verify_mode = ssl.CERT_REQUIRED
# context.load_verify_locations('ca.crt')
# 包装普通 socket 为 TLS socket
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.bind(('0.0.0.0', 8443))
sock.listen(5)
conn, addr = sock.accept()
with context.wrap_socket(conn, server_side=True) as tls_conn:
data = tls_conn.recv(1024)
tls_conn.sendall(b"Encrypted response")
TLS 安全配置检查清单
| 检查项 | 推荐配置 | 原因 |
|---|---|---|
| 协议版本 | TLS 1.2+ | TLS 1.0/1.1 已被主流浏览器弃用 |
| 加密套件 | ECDHE+AESGCM | 前向保密 + AEAD 认证加密 |
| 证书有效期 | ≤ 397 天 | 浏览器强制要求 |
| HSTS | max-age=31536000 | 防止 SSL stripping 攻击 |
| OCSP Stapling | 启用 | 减少首次握手延迟 |
# 快速检查服务端 TLS 配置
python -c "import ssl; print(ssl.OPENSSL_VERSION)"
# OpenSSL 3.0+ 支持 QUIC/TLS 1.3
最佳实践:使用 Let’s Encrypt 自动续期证书(
certbot),配合acme.sh在 Python 应用中自动热加载新证书,无需重启服务。
生产环境部署与监控
将 Python 网络服务部署到生产环境需要考虑多个维度:进程管理、负载均衡、日志收集、健康检查。
多进程架构(绕过 GIL)
import os
import socket
from multiprocessing import Pool
def worker(sock):
"""每个进程独立监听套接字(SO_REUSEPORT 实现内核级负载均衡)"""
while True:
conn, addr = sock.accept()
handle_client(conn, addr)
def run_multi_process(host='0.0.0.0', port=8888, workers=4):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Linux 3.9+ 支持 SO_REUSEPORT:内核将新连接均匀分配给多个进程
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
sock.bind((host, port))
sock.listen(128)
with Pool(workers) as pool:
# 所有 worker 共享同一个监听 socket
pool.map(worker, [sock] * workers)
if __name__ == '__main__':
run_multi_process()
生产部署检查清单
- 进程管理:使用
systemd或supervisord,配置自动重启、日志轮转 - 反向代理:Nginx 前置处理静态文件、TLS 终止、限流、压缩
- 监控指标:Prometheus 采集连接数、请求延迟、错误率;Grafana 可视化
- 日志规范:结构化 JSON 日志,包含 trace_id 用于链路追踪
# Nginx 反向代理 WebSocket + HTTP
upstream backend {
least_conn; # 最少连接负载均衡
server 127.0.0.1:8001;
server 127.0.0.1:8002;
}
server {
listen 443 ssl http2;
ssl_certificate /etc/ssl/certs/server.crt;
ssl_certificate_key /etc/ssl/private/server.key;
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /ws {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400;
}
}
8. 网络调试技巧
# 1. 查看 DNS 解析
import socket
print(socket.gethostbyname("example.com"))
print(socket.getaddrinfo("example.com", 80))
# 2. 获取本机 IP
hostname = socket.gethostname()
print(socket.gethostbyname(hostname))
# 3. 端口是否开放
sock = socket.socket()
result = sock.connect_ex(("localhost", 5432))
print("开放" if result == 0 else "关闭")
延伸阅读
- Python asyncio 极速异步编程完全指南 —— 异步网络核心
- Python Web 框架:FastAPI 实战 —— 高层 Web 开发
- Python 并发与性能深度指南 —— 高并发网络服务
网络编程的核心不在于记住 API,而在于理解 TCP 连接的三次握手、HTTP 的请求/响应模型、以及 I/O 类型(阻塞/非阻塞/异步)的差异。掌握这些,任何网络库都能快速上手。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。