如何在 Rust 中关闭 Unix 套接字?

4
我有一个测试程序,它打开并监听 Unix Domain Socket。套接字已经打开并能够读取数据,但它无法正常关闭。
当我尝试第二次运行测试时,会出现以下错误:
“线程'test_1'在一个'Err'值上调用'Result::unwrap()'方法: 错误{repr:Os {code:48, message:"Address already in use"}}”,../src/libcore/result.rs:799 注意:使用'RUST_BACKTRACE=1'来获取回溯信息。
代码可以在Rust Playground中查看,也可以在Github Gist中找到。
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);
}
1个回答

18
请学习如何创建一个最小可重现示例,并花时间去做。在这种情况下,不需要线程、函数或测试框架;两次运行整个程序即可重现错误:
use std::os::unix::net::UnixListener;

fn main() {
    UnixListener::bind("/tmp/my_socket.sock").unwrap();
}

如果你在测试之前和之后查看文件系统,你会发现文件/tmp/my_socket.sock在第一次运行之前不存在,在第二次运行之前存在。删除该文件允许程序再次完成运行(此时会重新创建该文件)。
这个问题不仅仅是Rust的问题

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

[...]

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

你可以选择在套接字周围添加一些包装器,在其被丢弃或创建一个在丢弃时被清理的临时目录时自动删除它,但我不确定那样是否有效。你也可以创建一个包装函数,在打开套接字之前删除该文件。

在丢弃时取消链接套接字

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)
}

请注意,您可以将这两种解决方案结合起来,即在创建套接字之前和删除套接字时进行操作。
我认为在创建期间删除不是最优解决方案:如果您启动第二个服务器,您将阻止第一个服务器接收更多连接。最好出现错误并告知用户。

我的错,我被问题的范围带走了,把整个解决方案都发布了。一旦我完成实施解决方案,我会更新问题。 - Sam Khawase

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接