我正在开发一个 Rust 应用程序,我需要向服务器发送 HTTPS 请求,但故意不读取响应。
我的主要目标是最大限度地减少资源利用率、处理时间,并且主要是不消耗字节,因为我使用代理,并且我想通过完全避免处理响应数据来削减成本。据我了解,TCP/IP 协议本质上需要请求-响应模式,并且操作系统会缓冲套接字的传入数据,直到应用程序读取该数据。但是,对于我的特定用例,我想在发送请求后立即关闭套接字,或者以某种方式指示操作系统不要消耗套接字上的数据。
我目前分别使用
tokio
和 tokio-rustls
来支持异步网络和 TLS。这是我当前方法的简化版本:
let mut root_cert_store = RootCertStore::empty();
root_cert_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
let config = ClientConfig::builder()
.with_root_certificates(root_cert_store)
.with_no_client_auth();
let connector = TlsConnector::from(Arc::new(config));
let host = "www.rust-lang.org";
let stream = TcpStream::connect(format!("{}:443", host))
.await
.expect("Failed to connect");
let domain = pki_types::ServerName::try_from(host.to_string())
.unwrap()
.to_owned();
let mut stream = connector
.connect(domain, stream)
.await
.expect("Failed to connect");
let request = format!(
"GET / HTTP/1.1\r\nHost: {}\r\nConnection: close\r\n\r\n",
host
);
stream
.write_all(request.as_bytes())
.await
.expect("Failed to write");
stream.shutdown().await.unwrap();
我正在使用
tcpdump
来监控是否收到数据,并且我看到即使没有从流中 read
也能恢复网站。
在 Python 中,我可以通过显式关闭我假设的
socket
来实现此功能。
据我了解,无法使用
Read
关闭 Write
流,只能关闭 stream.shutdown
流,但是,这仍然会导致接收到字节。
我还尝试创造性地在
ClientConnection
上发送 close_notify,仍然消耗字节。
let client_conn = stream.get_mut().1;
client_conn.send_close_notify();
任何关于如何在 Rust 中实现这一目标的见解或建议将不胜感激。
TCP 中确实没有办法从客户端向服务器发送消息,并告诉对方(在 TCP 层)您不想消耗响应中发送的字节。但是,您可以简单地关闭套接字而不接收它们。技巧是:如何最大限度地减少服务器发送的字节数?
通过将 TCP 会话的“接收窗口”减少到其通常/默认值以下,您可以最大限度地减少每个段发送的字节数(段 == TCP 在单个数据包中发送的数据块以及该段的数量)。发送者将在等待确认之前发送)。第二个部分是关闭套接字或等效地执行关闭读取操作,以便您自己的内核尽快发送回 RST 数据包(如果内核知道客户端已经关闭,则它不会接受更多数据) /关闭套接字;而是重置连接)。 因此,作为客户,您:
创建套接字
import socket
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(("", 5555))
s.listen(5)
conn, ca = s.accept()
x = conn.recv(1000)
print("GOT", x)
conn.sendall(b'12345' * 10000)
x = conn.recv(1000)
这是客户端(我在不同的系统上运行):
import socket
s = socket.socket()
x = s.getsockopt(socket.SOL_SOCKET,socket.SO_RCVBUF)
print("RCVBUF BEFORE", x)
s.setsockopt(socket.SOL_SOCKET,socket.SO_RCVBUF, 1)
x = s.getsockopt(socket.SOL_SOCKET,socket.SO_RCVBUF)
print("RCVBUF AFTER", x)
s.connect(("192.168.1.48", 5555))
s.shutdown(socket.SHUT_RD)
s.sendall(b"xyzzy")
两个系统都是linux。它允许我配置的最小有效
SO_RCVBUF
实际上是 2304。生成的 TCP 段大小似乎是 (576) 的 1/4。
如果启动服务器,然后运行客户端并在另一个窗口中观察 tcpdump 输出,我会看到在服务器识别 RST 之前发送了两个 576 字节的段作为响应。如果我注释掉
s.setsockopt(socket.SOL_SOCKET,socket.SO_RCVBUF, 1)
,那么在识别 RST 之前通常会发送大约 10 个 1448 字节段。
(不确定,但一些细节可能取决于我的特定网络设置,其中两个盒子通过 wifi 网络连接。)