首页 > 解决方案 > 如何使用不实现 Copy 的参数每 X 秒调用一次函数?

问题描述

我正在用 Rust 创建一个基本的 Minecraft 服务器。一旦玩家登录到我的服务器,我需要KeepAlive每 20 秒发送一个数据包以确保客户端仍然连接。

我认为这可以通过产生一个新线程来解决,该线程在休眠 20 秒后发送数据包。(第 35 行)。不幸的是,该Server类型没有实现Copy.

错误

error[E0382]: borrow of moved value: `server`
  --> src/main.rs:22:9
   |
20 |     let mut server = Server::from_tcpstream(stream).unwrap();
   |         ---------- move occurs because `server` has type `ozelot::server::Server`, which does not implement the `Copy` trait
21 |     'listenloop: loop {
22 |         server.update_inbuf().unwrap();
   |         ^^^^^^ value borrowed here after move
...
35 |                     thread::spawn(move || {
   |                                   ------- value moved into closure here, in previous iteration of loop

代码:

use std::thread;
use std::io::Read;
use std::fs::File;
use std::time::Duration;
use std::io::BufReader;
use ozelot::{Server, clientbound, ClientState, utils, read};
use ozelot::serverbound::ServerboundPacket;

use std::net::{TcpListener, TcpStream};

fn main() -> std::io::Result<()> {
    let listener = TcpListener::bind("0.0.0.0:25565")?;
    for stream in listener.incoming() {
        handle_client(stream?);
    }
    Ok(())
}

fn handle_client(stream: TcpStream) {
    let mut server = Server::from_tcpstream(stream).unwrap();
    'listenloop: loop {
        server.update_inbuf().unwrap();
        let packets = server.read_packet().unwrap_or_default();

        'packetloop: for packet in packets {

            match packet {

                //...
                ServerboundPacket::LoginStart(ref p) => {

                    //...

                    //send keep_alive every 20 seconds
                    thread::spawn(move || {
                        thread::sleep(Duration::from_secs(20));
                        send_keep_alive(server)
                    });

                    fn send_keep_alive(mut server: Server) {

                        server.send(clientbound::KeepAlive::new(728)).unwrap();

                    }

                    //...

                },
                //...
                _ => {},

            }
        }
        thread::sleep(Duration::from_millis(50));
    }
}

标签: multithreadingrust

解决方案


注意大多数函数是如何ozelot::Server作为&mut self参数的。这意味着它们是在对 a 的可变引用上调用的ozelot::Server

来自Rust 文档

您只能对特定范围内的特定数据有一个可变引用

问题是 - 我们如何ozelot::Server在主线程和新线程之间共享?

模块中的很多东西std::sync正是解决了这类问题。在您的情况下,您希望从主线程读取访问权限并从线程调用写入访问权限send_keep_alive。这需要Mutex- 一次独占读/写访问,并且


将服务器包装在Arc<Mutex>

use std::sync::{Arc, Mutex};

// --snip--

fn handle_client(stream: TcpStream) {
    let mut server = Server::from_tcpstream(stream).unwrap();
    let mut server = Arc::new(Mutex::new(server));

将 a 移动cloneArc线程中,并lock获得Mutex访问权限:

let server = Arc::clone(server);
thread::spawn(move || {
    thread::sleep(Duration::from_secs(20));
    send_keep_alive(&mut server)
});

并通过可变引用send_keep_alive接收:server

fn send_keep_alive(server: &mut Arc<Mutex<Server>>) {
    loop {
        let mut server = server.lock().unwrap();
        server.send(clientbound::KeepAlive::new(728)).unwrap();
    }
}

推荐阅读