使用async_std实现Rust聊天服务器时用unsafe存全局客户端Vec是否合理
问题解答
现有写法的问题
你当前的写法不符合Rust安全规范,存在多个严重问题:
static mut在多线程异步环境下没有任何同步保护,多任务同时读写USERS会直接触发数据竞争,属于未定义行为,大概率会出现程序崩溃、数据错乱等问题- 读取消息后直接使用整个1024字节的buffer,没有截断到实际读取的长度
len,会导致发送多余的空白/历史残留数据 - 连接断开后没有从
USERS中移除对应的TcpStream实例,会导致内存持续泄漏,列表里堆积大量无效死连接 - 广播时任意一个连接写入失败都会直接中断整个广播流程,其余在线用户无法收到当前消息
无unsafe的可行解决方案
Rust中跨异步任务共享可变状态不需要用到unsafe,只需要搭配线程安全的智能指针+同步原语即可实现,以下是最小改动的兼容方案:
方案1:Arc包装共享状态(最易迁移)
放弃全局static mut变量,改为在main函数中创建共享的用户列表,通过clone所有权的方式传递给每个连接任务:
首先导入依赖:
use async_std::sync::{Mutex, Arc}; use std::io; use async_std::net::{TcpListener, TcpStream, SocketAddr}; use async_std::task;
修改main函数:
#[async_std::main] async fn main() -> io::Result<()>{ let listener = TcpListener::bind("127.0.0.1:14530").await?; // 创建共享用户列表,Arc负责跨任务共享所有权,Mutex负责读写同步 let users = Arc::new(Mutex::new(Vec::new())); loop { let (stream, addr) = listener.accept().await?; // 克隆一份Arc指针,传递给新的连接任务 let users_clone = users.clone(); // 直接加锁写入,不需要unsafe users.lock().await.push(stream.clone()); task::spawn(on_connection(stream, addr, users_clone)); } }
修改on_connection函数,增加共享列表参数,调整广播和断开逻辑:
async fn on_connection(mut stream:TcpStream, addr:SocketAddr, users: Arc<Mutex<Vec<TcpStream>>>) -> io::Result<()> { println!("New Connection: {}", addr.to_string()); let mut buffer = [0u8; 1024]; loop { let len = stream.read(&mut buffer).await?; if len > 0 { print!("Message from {} => {}", addr.to_string(), String::from_utf8_lossy(&buffer[..len])); // 加锁获取用户列表,锁的作用域会在代码块结束后自动释放 let mut users_lock = users.lock().await; let mut remove_idx = Vec::new(); // 遍历广播,记录写入失败的无效连接 for (idx, user) in users_lock.iter_mut().enumerate() { if let Err(_) = user.write(&buffer[..len]).await { remove_idx.push(idx); } } // 倒序删除无效连接,避免索引错乱 for idx in remove_idx.into_iter().rev() { users_lock.remove(idx); } } else { println!("Disconnected: {}", addr.to_string()); // 断开时从列表中移除当前连接 let mut users_lock = users.lock().await; users_lock.retain(|x| x.peer_addr().ok() != Some(addr)); break } }; Ok(()) }
方案2:广播通道实现(更优雅)
如果不想维护共享用户列表,可以单独启动一个广播任务,所有连接任务收到消息后都发送给广播任务,由广播任务统一分发到所有存活连接,完全避免显式锁的使用,性能和可维护性更好。
内容的提问来源于stack exchange,提问作者eminfedar
相关产品推荐
相关产品推荐

