关闭 TcpStream / 不从套接字读取

问题描述 投票:0回答:1

我正在开发一个 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 中实现这一目标的见解或建议将不胜感激。

sockets rust networking tcp rust-tokio
1个回答
0
投票

TCP 中确实没有办法从客户端向服务器发送消息,并告诉对方(在 TCP 层)您不想消耗响应中发送的字节。但是,您可以简单地关闭套接字而不接收它们。技巧是:如何最大限度地减少服务器发送的字节数?

通过将 TCP 会话的“接收窗口”减少到其通常/默认值以下,您可以最大限度地减少每个段发送的字节数(段 == TCP 在单个数据包中发送的数据块以及该段的数量)。发送者将在等待确认之前发送)。第二个部分是关闭套接字或等效地执行关闭读取操作,以便您自己的内核尽快发送回 RST 数据包(如果内核知道客户端已经关闭,则它不会接受更多数据) /关闭套接字;而是重置连接)。 因此,作为客户,您:

创建套接字
  • 将套接字上的接收缓冲区大小配置为可接受的最小值
  • 连接到服务器
  • 将您的请求发送到服务器
  • 关闭/关闭套接字
  • 演示玩具程序(用 python 编写,因为它对我来说制作原型更快)。这是服务器,它只是接受连接,读取单个“请求”并尝试发回 50K 数据块。

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 网络连接。)

© www.soinside.com 2019 - 2024. All rights reserved.