如果我在端口 5000 上以开发模式运行一个简单的烧瓶应用程序(因此 Web 服务器和 Web 应用程序是烧瓶库)。有没有办法查看客户端和服务器之间交换的 SYN、SYN-ack、ack 请求?例如,打印它们?我不想使用 Wireshark 之类的工具来查看流量,我想知道谁在处理这个过程。
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return 'hello'
当我做curl localhost:5000/
curl 首先发送 SYN?谁来回答?烧瓶?
我该怎么做才能在我自己的应用程序上看到所有这些过程?另外,我想看看 HTTP/1.1 的 keep-alive 机制是如何工作的?
两者都不。对于 TCP,握手完全由操作系统处理。
cURL 使用操作系统提供的“sockets API”启动连接——通常是 socket() 和 connect() 函数——而 Flask 的开发服务器使用 listen() 和 accept() 接收它。当 accept() 函数返回时,整个 TCP 握手已经完成,Flask 可以使用套接字获取数据了。
这意味着您尝试查看的过程根本不是您的应用程序的一部分。Flask 只使用 Python 的
socket.socket()
对象,然后直接调用相应的 OS 函数——您需要使用 dtrace/bpftrace/systemtap 等 OS 跟踪工具来查看发生的内部内核调用。尽管请注意 QUIC(如在 HTTP/3 中)的工作方式略有不同——操作系统中没有直接的 QUIC 支持,因此执行 QUIC 握手的是cURL 和 Nginx,尽管它们仍然使用像 ngtcp2 或 MsQuic 之类的库来完成所有操作工作。(端点仍然使用“套接字 API”与操作系统对话,但对于 QUIC,它们创建了 UDP 套接字,这些套接字不实现自己的握手。)
与数据包捕获工具类似,还有数据包生成工具可让您制作自定义数据包(甚至可能创建自己的 TCP 堆栈)。其中之一是
scapy
,它是用 Python 编写的,可让您使用 Python REPL 手动构建数据包。另一个类似的选项是pypacker
模块,同样在 Python 中。此类工具通常使用“原始套接字”,这与 UDP 非常相似,只是应用程序可以包含自己的 IP 标头。您通常需要 root 权限才能使用它。
通常,一旦 TCP 连接打开,它就会保持打开状态,除非进程故意关闭它(或者除非它退出,在这种情况下操作系统会关闭连接)。操作系统不跟踪“请求”——这是应用程序问题;TCP可以随时向任何方向传输数据。
所以整个HTTP/1.1“keep-alive”机制是服务器在响应发送后实际上并没有关闭TCP连接,即它没有调用shutdown()或close()。这允许客户端通过该连接发送更多 HTTP 请求。没有什么别的了——客户端和服务器都没有做任何努力来保持连接活跃,他们只是避免一开始就终止它。
(有一个可选的 HTTP标头允许 HTTP/1.0 客户端请求保持活动,或 HTTP/1.1 客户端阻止它。)
当然,该机制仅适用于在请求之间保持运行(并保持 OS 套接字打开)的客户端。例如,可以为
curl
或wget
CLI 工具提供多个 URL 以供下载,并且在为第一个 URL 建立连接后,它们将为下一个 URL 保持打开状态(假设它来自同一服务器)。但是一旦工具退出,连接总是会关闭——curl
第二次运行必须建立一个全新的连接。类似地,Python将使用“keep-alive”在多个or调用
requests.Session()
中自动重用连接。但是,如果脚本退出,则连接将随之关闭。.get()
.post()
但是请注意,其他一些协议也具有“保持活动”功能,其工作方式不同,通过发送明确的“我仍在运行”数据包(例如,可能每分钟发送一个保持活动数据包)。这可以通过以下两种方式之一完成 - 数据包携带应用程序生成的实际数据,或者它们是应用程序启用此功能后操作系统发送的 0 长度 TCP 数据包。(后者被称为“TCP keepalives”;它们不需要保持两台主机之间的正常连接打开,但它们有时用于防止中间系统——比如你的家庭 NAT 路由器——强行关闭它。)