分层与包传递
应用定义消息,传输协议连接端点,IP 在网络间路由包,链路技术在本地一跳传帧。各层添加自身职责所需信息。套接字端点有地址与端口,地址选目的地,端口选服务上下文。包可经过多个路由器,路线不同于应用会话。丢失、延迟与乱序都是协议需处理的情况。
为何主机除 IP 还需端口?
完整解答
区分主机上的服务端点。
名字与 DNS
DNS 把域名映射到包括地址的记录,解析器查询缓存与分布层次。缓存有生存期,修改不会立即影响所有客户端。域名并非永久唯一 IP。URL 还有协议、路径、可能的端口和查询。实验用数字回环隔离外部 DNS 与路由,并非完整公网解析演示。
改 DNS 记录会立即使所有缓存失效吗?
完整解答
不会,缓存可能保留到生存期结束。
TCP 与 UDP
TCP 提供有序字节流及传输可靠性与流控,不保留应用消息边界:一次发送可需多次读取,多次发送也可一次读取。recv 返回空字节表示对端关闭发送方向。UDP 保留数据报边界,但不保证交付或顺序。按应用要求选择,超时不代表对端一定没执行工作。
recv(100) 可返回少于百字节吗?
完整解答
可以,需循环到帧完整或连接结束。
分帧与防御性解析
协议可用分隔符、固定宽度或长度前缀。帧包含四字节无符号大端长度,后跟相应 UTF-8 字节。完整读头、验证最大长度、完整读体再解码。长度统计字节而非字符。辅助函数每次最多读两字节,明确练习部分读取。截断帧应拒绝,不能当有效消息。
为何先验证长度再分配或读取?
完整解答
限制不可信输入造成的资源消耗。
HTTP、HTTPS 与请求语义
HTTP 请求含方法、目标与头,响应含状态、头与可选体。GET 获取表示,POST 请求资源特定处理。200 成功,404 无对应资源。JSON 是一种体格式,不等于 HTTP。HTTPS 使用 TLS 验证服务器并保护传输,但不能替代应用验证与授权。超时后重试修改需幂等设计避免重复。
HTTPS 本身防 SQL 注入吗?
完整解答
不能,安全传输与安全查询解决不同问题。
常见误解
- recv 大小是上限,不承诺完整消息。
- 本地成功不验证公网路由、DNS 或 TLS。
实验准备
下载脚本,在终端中使用 Python 3.11 或更新版本运行:python m11_stream.py. Windows 也可使用 py -3;部分系统使用 python3。实验仅用标准库。先预测结果,再运行并完成变体。不要使用 -O,以保留断言。下方输出由构建器实际运行捕获,两种语言使用相同代码与输出。
实验 1 — 流分帧
有限服务器发送一帧后关闭,客户端组装部分读取并检查 1024 字节上限。
"""Receive a length-prefixed message even when reads return partial chunks."""
import socket
from threading import Thread
def read_exact(stream, count):
result = bytearray()
while len(result) < count:
chunk = stream.recv(min(2, count - len(result)))
if not chunk:
raise EOFError("connection closed before complete frame")
result.extend(chunk)
return bytes(result)
with socket.socket() as listener:
listener.bind(("127.0.0.1", 0)); listener.listen(1); listener.settimeout(5)
payload = b"Dune,Foundation"
errors = []
def serve():
try:
connection, _ = listener.accept()
with connection:
connection.sendall(len(payload).to_bytes(4, "big") + payload)
except Exception as error:
errors.append(error)
worker = Thread(target=serve); worker.start()
with socket.create_connection(listener.getsockname(), timeout=5) as client:
length = int.from_bytes(read_exact(client, 4), "big")
if length > 1024: raise ValueError("frame too large")
result = read_exact(client, length)
worker.join(timeout=5)
assert not worker.is_alive() and not errors and result == payload
print("frame bytes:", length, "decoded:", result.decode("utf-8"))
frame bytes: 15 decoded: Dune,Foundation
- 用含中的 UTF-8 文字并重算字节长度。
- 声明比实际更长的体。
- 声明五千长度,验证早拒绝。
完整解答
先编码再统计字节。截断体在对端关闭时抛 EOFError,五千在读取体前拒绝。保留等待线程和套接字上下文以清理。
实验 2 — HTTP 目录响应
查看 JSON 体、类型与状态。服务器仅本地绑定并在检查后关闭;http.server 用于学习,不是生产服务器。
"""A finite localhost HTTP experiment with JSON, statuses and cleanup."""
import json
from http.server import BaseHTTPRequestHandler, HTTPServer
from threading import Thread
from urllib.error import HTTPError
from urllib.request import urlopen
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path != "/books":
self.send_error(404); return
body = json.dumps([{"id": 1, "title": "Dune"}]).encode("utf-8")
self.send_response(200)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Content-Length", str(len(body)))
self.end_headers(); self.wfile.write(body)
def log_message(self, *args):
pass
server = HTTPServer(("127.0.0.1", 0), Handler)
thread = Thread(target=server.serve_forever); thread.start()
try:
base = f"http://127.0.0.1:{server.server_port}"
with urlopen(base + "/books", timeout=5) as response:
books = json.load(response)
print("GET /books:", response.status, books)
assert response.status == 200 and books[0]["title"] == "Dune"
try:
urlopen(base + "/missing", timeout=5)
except HTTPError as error:
print("GET /missing:", error.code); assert error.code == 404; error.close()
else:
raise AssertionError("missing resource must be 404")
finally:
server.shutdown(); server.server_close(); thread.join(timeout=5)
assert not thread.is_alive()
GET /books: 200 [{'id': 1, 'title': 'Dune'}]
GET /missing: 404
- 加入返回 JSON 的 /health。
- 空目录返回空列表。
- 解释缺失路径与零匹配图书的区别。
完整解答
有效 /health 返回 200 与状态对象。/books 空列表仍 200,缺失路由为 404。Content-Length 应等于编码后的体字节数。
练习与完整解答
先尝试,再展开解答。★ 应用概念;★★ 结合概念;★★★ 进行设计或证明。
同 IP 的两服务靠什么区分?
完整解答
传输协议与端口及地址上下文。
域名可对应多地址吗?
完整解答
可以,多地址记录且答案可变化。
六字节分为二、一、三到达,如何读?
完整解答
累积至六,不把不完整体当完成消息解码。
A中 声明多少帧长度?
完整解答
四字节,而非两码位。
空 /books 与未知路径的状态?
完整解答
有效空集合 200,未知资源 404。
借阅 POST 传出后超时,能假定失败吗?
完整解答
不能,可能已提交但响应丢失;用稳定请求 ID 与保存结果去重。
规定有界文字消息格式。
完整解答
四字节无符号大端长度,最多 1024 字节,随后有效 UTF-8;拒绝超大、截断与编码错误,规定零长度是否有效。
TCP 可靠性保证数据库事务提交吗?
完整解答
不,传输交付不同于应用验证、执行与提交,需应用响应约定。
自测
选择答案查看反馈,重置后可重做。无需 JavaScript 也可阅读答案表。
TCP 提供什么?
UDP 保留什么?
长度前缀统计什么?
HTTP 404 是什么?
TLS 代替授权吗?
超时证明服务器未执行吗?
答案表
- A — 应用分帧另行定义。
- B — 边界不等于可靠性。
- C — 传输承载字节。
- A — 它是应用响应状态。
- B — 传输安全不授予访问权。
- C — 可能完成后响应丢失。
引导阅读
- Python 套接字指南 — 阅读发送接收与分帧,解释部分读取。
- HTTP 语义 RFC 9110 — 阅读 GET 与状态码定义,无需全文。
复习与下一步
分别跟踪字节帧与 HTTP 响应,解释超时不能证明什么。模块 12 将用关系数据库持久保存目录。
关键术语
| 术语 | 含义 |
|---|---|
| 分帧 | 在传输数据中识别消息边界。 |
| 幂等性 | 重复应用与一次的预期效果相同。 |
| 回环 | 指向本地主机的网络通信。 |