正常退出TcpListener.incoming()

时间:2019-06-20 19:48:38

标签: tcp rust

来自rust std网络库:

let listener = TcpListener::bind(("127.0.0.1", port)).unwrap();

info!("Opened socket on localhost port {}", port);

// accept connections and process them serially
for stream in listener.incoming() {
    break;
}

info!("closed socket");

如何使收听者停止收听?它在API中说,当侦听器被删除时,它会停止。但是,如果incoming()是阻塞呼叫,我们如何删除它?最好不要使用外部包装箱,例如tokio / mio。

4 个答案:

答案 0 :(得分:2)

您将要使用set_nonblocking()方法将TcpListener置于非阻止模式,如下所示:

use std::io;
use std::net::TcpListener;

let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
listener.set_nonblocking(true).expect("Cannot set non-blocking");

for stream in listener.incoming() {
    match stream {
        Ok(s) => {
            // do something with the TcpStream
            handle_connection(s);
        }
        Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
            // Decide if we should exit
            break;
            // Decide if we should try to accept a connection again
            continue;
        }
        Err(e) => panic!("encountered IO error: {}", e),
    }
}

而不是等待连接,incoming()调用将立即返回Result <>类型。如果Result为Ok(),则建立了连接,您可以对其进行处理。如果Result是Err(WouldBlock),则实际上这不是错误,只是在incoming()检查套接字的那一刻没有连接挂起。

请注意,在WouldBlock情况下,您可能需要在继续之前放置sleep()或其他内容,否则您的程序将快速轮询传入的()函数以检查连接,从而导致CPU使用率高。

代码示例改编自here

答案 1 :(得分:2)

您可以使用poll eventfd来使用套接字,该套接字用于发送信号。 我为此写了一个助手。

let shutdown = EventFd::new();
let listener = TcpListener::bind("0.0.0.0:12345")?;
let incoming = CancellableIncoming::new(&listener, &shutdown);

for stream in incoming {
    // Your logic
}

// While in other thread
shutdown.add(1);  // Light the shutdown signal, now your incoming loop exits gracefully.
use nix;
use nix::poll::{poll, PollFd, PollFlags};
use nix::sys::eventfd::{eventfd, EfdFlags};
use nix::unistd::{close, write};
use std;
use std::net::{TcpListener, TcpStream};
use std::os::unix::io::{AsRawFd, RawFd};

pub struct EventFd {
    fd: RawFd,
}

impl EventFd {
    pub fn new() -> Self {
        EventFd {
            fd: eventfd(0, EfdFlags::empty()).unwrap(),
        }
    }

    pub fn add(&self, v: i64) -> nix::Result<usize> {
        let b = v.to_le_bytes();
        write(self.fd, &b)
    }
}

impl AsRawFd for EventFd {
    fn as_raw_fd(&self) -> RawFd {
        self.fd
    }
}

impl Drop for EventFd {
    fn drop(&mut self) {
        let _ = close(self.fd);
    }
}

// -----
//
pub struct CancellableIncoming<'a> {
    listener: &'a TcpListener,
    eventfd: &'a EventFd,
}

impl<'a> CancellableIncoming<'a> {
    pub fn new(listener: &'a TcpListener, eventfd: &'a EventFd) -> Self {
        Self { listener, eventfd }
    }
}

impl<'a> Iterator for CancellableIncoming<'a> {
    type Item = std::io::Result<TcpStream>;
    fn next(&mut self) -> Option<std::io::Result<TcpStream>> {
        use nix::errno::Errno;

        let fd = self.listener.as_raw_fd();
        let evfd = self.eventfd.as_raw_fd();
        let mut poll_fds = vec![
            PollFd::new(fd, PollFlags::POLLIN),
            PollFd::new(evfd, PollFlags::POLLIN),
        ];

        loop {
            match poll(&mut poll_fds, -1) {
                Ok(_) => break,
                Err(nix::Error::Sys(Errno::EINTR)) => continue,
                _ => panic!("Error polling"),
            }
        }

        if poll_fds[0].revents().unwrap() == PollFlags::POLLIN {
            Some(self.listener.accept().map(|p| p.0))
        } else if poll_fds[1].revents().unwrap() == PollFlags::POLLIN {
            None
        } else {
            panic!("Can't be!");
        }
    }
}

答案 2 :(得分:2)

注意设置

listener.set_nonblocking(true).expect("Cannot set non-blocking");

@effect's answer 中推荐的那样,在所有流上也会自动将非阻塞设置为 true。也许你想要这个,也许你不想要;我不得不将它们设置回 false

stream.set_nonblocking(false);

答案 3 :(得分:0)

标准库没有为此提供API,但是您可以使用特定于平台的API来关闭套接字上的读取,这将导致incoming迭代器返回错误。收到错误后,您可以中断处理连接。例如,在Unix系统上:

use std::net::TcpListener;
use std::os::unix::io::AsRawFd;
use std::thread;

let listener = TcpListener::bind("localhost:0")?;

let fd = listener.as_raw_fd();

let handle = thread::spawn(move || {
  for connection in listener.incoming() {
    match connection {
      Ok(connection) => /* handle connection */
      Err(_) => break,
  }
});

libc::shutdown(fd, libc::SHUT_RD);

handle.join();