【问题标题】:Why does shutdown write in the client cause the connection to be closed?为什么客户端中的shutdown write会导致连接关闭?
【发布时间】:2022-01-21 05:16:35
【问题描述】:

注意:删除客户端的关机不会出现错误

// server.rs
use std::io::BufReader;
use std::io::Read;
use std::io::Write;
use std::net::Shutdown;
use std::net::TcpListener;
use std::thread;

fn main() {
    let listener = TcpListener::bind("127.0.0.1:4000").unwrap();

    for stream in listener.incoming() {
        let mut stream = stream.unwrap();

        thread::spawn(move || {
            let mut reader = BufReader::new(&stream);
            let mut buffer = [0; 1024];
            let len = reader.read(&mut buffer).unwrap();

            // no sleep no error, just simulating a time-consuming operation
            thread::sleep(std::time::Duration::from_secs(1));

            stream.write_all(&buffer[0..len]).unwrap();
            stream.shutdown(Shutdown::Write).unwrap();
        });
    }
}
// client.rs
use std::io::{Read, Write};
use std::net::TcpStream;
use std::thread;

fn main() {
    let mut clients = Vec::new();

    for _ in 0..1000 {
        clients.push(thread::spawn(move || {
            let mut client = TcpStream::connect("127.0.0.1:4000").unwrap();
            client.write_all("hello".as_bytes()).unwrap();
            
            // no error if remove the following line
            client.shutdown(std::net::Shutdown::Write).unwrap();

            let mut buffer = Vec::new();
            client.read_to_end(&mut buffer).unwrap();
            println!("{}", std::str::from_utf8(&buffer).unwrap());
        }));
    }

    for client in clients.into_iter() {
        client.join().unwrap();
    }
}

据我了解,关闭写操作会在发送之前的数据后附加FIN,然后对端(服务器)仍然可以继续写数据。但是在这1000个客户端中,出现了一些错误:

// server
<unnamed>' panicked at 'called `Result::unwrap()` on an `Err` value: Os { code: 107, kind: NotConnected, message: "Transport endpoint is not connected" }', src/bin/server.rs:22:46
// client
thread '<unnamed>' panicked at 'called `Result::unwrap()` on an `Err` value: Os { code: 104, kind: ConnectionReset, message: "Connection reset by peer" }', src/bin/client.rs:15:45

在客户端关闭后似乎连接关闭了。

更新1: 我使用了 Wireshark,这是错误的连接之一:

No.     Time        Source      Destination Protocol    Length  Info
1101    13.738139   127.0.0.1   127.0.0.1   TCP         56      10628 → 4000 [SYN] Seq=0 Win=65535 Len=0 MSS=65495 WS=256 SACK_PERM=1
1104    13.738157   127.0.0.1   127.0.0.1   TCP         44      4000 → 10628 [RST, ACK] Seq=409345761 Ack=1 Win=0 Len=0
1234    14.251615   127.0.0.1   127.0.0.1   TCP         56      [TCP Retransmission] [TCP Port numbers reused] 10628 → 4000 [SYN] Seq=0 Win=65535 Len=0 MSS=65495 WS=256 SACK_PERM=1
1250    14.251690   127.0.0.1   127.0.0.1   TCP         56      [TCP Port numbers reused] 4000 → 10628 [SYN, ACK] Seq=0 Ack=1 Win=65535 Len=0 MSS=65495 WS=256 SACK_PERM=1
1266    14.251726   127.0.0.1   127.0.0.1   TCP         44      10628 → 4000 [ACK] Seq=1 Ack=1 Win=2161152 Len=0
1376    14.251949   127.0.0.1   127.0.0.1   TCP         49      10628 → 4000 [PSH, ACK] Seq=1 Ack=1 Win=2161152 Len=5
1387    14.251970   127.0.0.1   127.0.0.1   TCP         44      4000 → 10628 [ACK] Seq=1 Ack=6 Win=2161152 Len=0
1402    14.251996   127.0.0.1   127.0.0.1   TCP         44      10628 → 4000 [FIN, ACK] Seq=6 Ack=1 Win=2161152 Len=0
1412    14.252013   127.0.0.1   127.0.0.1   TCP         44      4000 → 10628 [ACK] Seq=1 Ack=7 Win=2161152 Len=0
2092    15.261312   127.0.0.1   127.0.0.1   TCP         49      4000 → 10628 [PSH, ACK] Seq=1 Ack=7 Win=2161152 Len=5
2101    15.261384   127.0.0.1   127.0.0.1   TCP         44      10628 → 4000 [RST, ACK] Seq=7 Ack=6 Win=0 Len=0

更新2: 正确连接之一:

No.     Time        Source      Destination Protocol    Length  Info
162     13.731960   127.0.0.1   127.0.0.1   TCP         56      10927 → 4000 [SYN] Seq=0 Win=65535 Len=0 MSS=65495 WS=256 SACK_PERM=1
166     13.731997   127.0.0.1   127.0.0.1   TCP         56      4000 → 10927 [SYN, ACK] Seq=0 Ack=1 Win=65535 Len=0 MSS=65495 WS=256 SACK_PERM=1
169     13.732013   127.0.0.1   127.0.0.1   TCP         44      10927 → 4000 [ACK] Seq=1 Ack=1 Win=2161152 Len=0
176     13.732035   127.0.0.1   127.0.0.1   TCP         49      10927 → 4000 [PSH, ACK] Seq=1 Ack=1 Win=2161152 Len=5
181     13.732046   127.0.0.1   127.0.0.1   TCP         44      4000 → 10927 [ACK] Seq=1 Ack=6 Win=2161152 Len=0
187     13.732059   127.0.0.1   127.0.0.1   TCP         44      10927 → 4000 [FIN, ACK] Seq=6 Ack=1 Win=2161152 Len=0
191     13.732074   127.0.0.1   127.0.0.1   TCP         44      4000 → 10927 [ACK] Seq=1 Ack=7 Win=2161152 Len=0
1495    14.746260   127.0.0.1   127.0.0.1   TCP         49      4000 → 10927 [PSH, ACK] Seq=1 Ack=7 Win=2161152 Len=5
1502    14.746369   127.0.0.1   127.0.0.1   TCP         44      10927 → 4000 [ACK] Seq=7 Ack=6 Win=2161152 Len=0
1505    14.746423   127.0.0.1   127.0.0.1   TCP         44      4000 → 10927 [FIN, ACK] Seq=6 Ack=7 Win=2161152 Len=0
1512    14.746529   127.0.0.1   127.0.0.1   TCP         44      10927 → 4000 [ACK] Seq=7 Ack=7 Win=2161152 Len=0

【问题讨论】:

  • “但是在这1000个客户端中,出现了一些错误” - 这是否意味着错误只发生了一次,其他999个连接没有问题?这可以重现吗?
  • 它是可重现的。对我来说,这种情况发生在 5% 到 50% 的客户身上。我怀疑它与未接受连接的容量有关(来自 strace 的 listen(3, 128) 输出),但还没有一个很好的解释。
  • @kmdreko 是的,它是可重现的。
  • 'no sleep no error'........睡眠是为了什么?
  • @MartinJames 只是模拟一个耗时的操作

标签: linux sockets rust


【解决方案1】:

我强烈怀疑这与积压有关,即操作系统 TCP 堆栈接受但尚未由您的应用程序处理的连接数。 std 不允许你控制这个数字,默认为 128。

为了控制数量,我在 tokio 中重新实现了您的服务器(嗯,它实际上只是 tokio README 中的主要示例),并将积压工作设置为 3000。

use std::time::Duration;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpSocket};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let addr = "127.0.0.1:4000".parse().unwrap();
    let socket = TcpSocket::new_v4()?;
    socket.bind(addr)?;
    let listener: TcpListener = socket.listen(3000)?;

    loop {
        let (mut socket, _) = listener.accept().await?;
        tokio::spawn(async move {
            let mut buf = [0; 1024];
            loop {
                let n = match socket.read(&mut buf).await {
                    Ok(n) if n == 0 => return,
                    Ok(n) => n,
                    Err(e) => {
                        eprintln!("failed to read from socket; err = {:?}", e);
                        return;
                    }
                };
                tokio::time::sleep(Duration::from_secs(1)).await;
                if let Err(e) = socket.write_all(&buf[0..n]).await {
                    eprintln!("failed to write to socket; err = {:?}", e);
                    return;
                }
            }
        });
    }
}

这使问题消失了。将其减少到socket.listen(128) 使其重新出现。 (免责声明:我不建议 3000 是积压的合理数字。)

我说我强烈怀疑这就是原因,因为我无法完全解释睡眠是如何导致问题的。可能是许多休眠线程减慢了调度程序,从而降低了服务器接受连接的速度。但那是猜测。

(旁注:我打开文件的默认 ulimit 相当低。我必须使用 ulimit -nS 10000 增加它,以免在测试时妨碍。)

【讨论】:

  • 但是在同样的环境下,删除client中的shutdown也不会报错。这是我的主要问题。
  • 啊,我错过了那个评论。抱歉,不知道。
猜你喜欢
  • 1970-01-01
  • 2020-09-30
  • 2021-02-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-13
  • 2017-01-01
  • 1970-01-01
相关资源
最近更新 更多