我有一个测试可以打开并侦听 Unix 域套接字。套接字打开并读取数据没有问题,但它不会正常关闭。
这是我第二次尝试运行测试时遇到的错误:
线程'test_1'在'调用
Result::unwrap()一个Err值时恐慌:错误{repr:Os{代码:48,消息:“地址已在使用中”}}',../src/libcore/result.rs:799注意:运行RUST_BACKTRACE=1用于回溯。
该代码可在 Rust 游乐场获得,并且有一个Github Gist。
use std::io::prelude::*;
use std::thread;
use std::net::Shutdown;
use std::os::unix::net::{UnixStream, UnixListener};
Run Code Online (Sandbox Code Playgroud)
测试用例:
#[test]
fn test_1() {
driver();
assert_eq!("1", "2");
}
Run Code Online (Sandbox Code Playgroud)
主入口点函数
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);
}
Run Code Online (Sandbox Code Playgroud)
间隔发送数据的功能
#[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();
}
Run Code Online (Sandbox Code Playgroud)
处理数据的函数
fn handle_client(mut stream: UnixStream) {
let mut response = String::new();
stream.read_to_string(&mut response).unwrap();
println!("got response: {:?}", response);
}
Run Code Online (Sandbox Code Playgroud)
侦听传入消息的服务器套接字
#[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);
}
Run Code Online (Sandbox Code Playgroud)
She*_*ter 15
请学习创建一个最小的可重现示例,然后花时间这样做。在这种情况下,不需要线程、函数或测试框架;两次运行整个程序会重现错误:
use std::os::unix::net::UnixListener;
fn main() {
UnixListener::bind("/tmp/my_socket.sock").unwrap();
}
Run Code Online (Sandbox Code Playgroud)
如果您查看测试前后的文件系统,您将看到该文件/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();
}
}
Run Code Online (Sandbox Code Playgroud)
您可能还需要考虑实现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
}
}
Run Code Online (Sandbox Code Playgroud)
这要简单得多:
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)
}
Run Code Online (Sandbox Code Playgroud)
请注意,您可以组合这两种解决方案,以便在创建之前和删除套接字时删除套接字。
我认为在创建过程中删除是一个不太理想的解决方案:如果您启动了第二台服务器,您将阻止第一台服务器接收更多连接。最好是出错并告诉用户。
| 归档时间: |
|
| 查看次数: |
2983 次 |
| 最近记录: |