【问题标题】:How do I close a Unix socket in Rust?如何在 Rust 中关闭 Unix 套接字?
【发布时间】:2016-10-24 12:26:44
【问题描述】:

我有一个打开并监听 Unix 域套接字的测试。套接字已打开并毫无问题地读取数据,但它不会正常关闭。

这是我第二次尝试运行测试时遇到的错误:

线程“test_1”在Err 上的“调用Result::unwrap()”处惊慌失措 值:错误 { repr:Os { 代码:48,消息:“地址已在使用中” } }', ../src/libcore/result.rs:799 注意:使用RUST_BACKTRACE=1 运行 用于回溯。

代码可用at the Rust playground,还有一个Github Gist for it

use std::io::prelude::*;
use std::thread;
use std::net::Shutdown;
use std::os::unix::net::{UnixStream, UnixListener};

测试用例:

#[test]
fn test_1() {
    driver();
    assert_eq!("1", "2");
}

主入口函数

fn driver() {
    let listener = UnixListener::bind("/tmp/my_socket.sock").unwrap();

    thread::spawn(|| socket_server(listener));

    // send a message 
    busy_work(3);

    // try to disconnect the socket
    let drop_stream = UnixStream::connect("/tmp/my_socket.sock").unwrap();
    let _ = drop_stream.shutdown(Shutdown::Both);
}

间隔发送数据的功能

#[allow(unused_variables)]
fn busy_work(threads: i32) {
    // Make a vector to hold the children which are spawned.
    let mut children = vec![];
    for i in 0..threads {
        // Spin up another thread
        children.push(thread::spawn(|| socket_client()));
    }
    for child in children {
        // Wait for the thread to finish. Returns a result.
        let _ = child.join();
    }
}

fn socket_client() {
    let mut stream = UnixStream::connect("/tmp/my_socket.sock").unwrap();
    stream.write_all(b"hello world").unwrap();
}

处理数据的功能

fn handle_client(mut stream: UnixStream) {
    let mut response = String::new();
    stream.read_to_string(&mut response).unwrap();
    println!("got response: {:?}", response);
}

监听传入消息的服务器套接字

#[allow(unused_variables)]
fn socket_server(listener: UnixListener) {
    // accept connections and process them, spawning a new thread for each one
    for stream in listener.incoming() {
        match stream {
            Ok(mut stream) => {
                /* connection succeeded */
                let mut response = String::new();
                stream.read_to_string(&mut response).unwrap();
                if response.is_empty() {
                    break;
                } else {
                    thread::spawn(|| handle_client(stream));
                }                
            }
            Err(err) => {
                /* connection failed */
                break;
            }
        }
    }
    println!("Breaking out of socket_server()");
    drop(listener);
}

【问题讨论】:

    标签: sockets unix rust


    【解决方案1】:

    请学习创建minimal reproducible example,然后花时间去做。在这种情况下,不需要线程、函数或测试框架;运行整个程序两次会重现错误:

    use std::os::unix::net::UnixListener;
    
    fn main() {
        UnixListener::bind("/tmp/my_socket.sock").unwrap();
    }
    

    如果您在测试之前和之后查看文件系统,您会发现文件/tmp/my_socket.sock 在第一次运行之前不存在,而在第二次运行之前存在。删除文件允许程序再次运行完成(此时它重新创建文件)。

    这个问题是not unique to Rust:

    请注意,一旦创建,此套接字文件将继续存在,即使在服务器退出后也是如此。如果服务器随后重新启动,该文件会阻止重新绑定:

    [...]

    因此,服务器应该在绑定之前取消链接套接字路径名。

    您可以选择在套接字周围添加一些包装器,当它被丢弃或create a temporary directory that is cleaned when it is dropped 时会自动删除它,但我不确定它的效果如何。您还可以创建一个包装函数,在打开套接字之前删除文件。

    当套接字被丢弃时取消链接

    use std::path::{Path, PathBuf};
    
    struct DeleteOnDrop {
        path: PathBuf,
        listener: UnixListener,
    }
    
    impl DeleteOnDrop {
        fn bind(path: impl AsRef<Path>) -> std::io::Result<Self> {
            let path = path.as_ref().to_owned();
            UnixListener::bind(&path).map(|listener| DeleteOnDrop { path, listener })
        }
    }
    
    impl Drop for DeleteOnDrop {
        fn drop(&mut self) {
            // There's no way to return a useful error here
            let _ = std::fs::remove_file(&self.path).unwrap();
        }
    }
    

    您可能还需要考虑实现 Deref / DerefMut 以使其成为套接字的智能指针:

    impl std::ops::Deref for DeleteOnDrop {
        type Target = UnixListener;
    
        fn deref(&self) -> &Self::Target {
            &self.listener
        }
    }
    
    impl std::ops::DerefMut for DeleteOnDrop {
        fn deref_mut(&mut self) -> &mut Self::Target {
            &mut self.listener
        }
    }
    

    在套接字打开之前取消链接

    这要简单得多:

    use std::path::Path;
    
    fn bind(path: impl AsRef<Path>) -> std::io::Result<UnixListener> {
        let path = path.as_ref();
        std::fs::remove_file(path)?;
        UnixListener::bind(path)
    }
    

    请注意,您可以结合使用这两种解决方案,以便在创建之前和删除套接字时删除套接字。

    我认为在创建期间删除是一个不太理想的解决方案:如果您启动第二台服务器,您将阻止第一台服务器接收更多连接。最好是出错并告诉用户。

    【讨论】:

    • 我的错,我被问题的范围冲昏了头脑并发布了整个解决方案。完成解决方案实施后,我会更新问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-13
    • 2017-10-05
    • 2014-03-06
    • 1970-01-01
    相关资源
    最近更新 更多