向使用多个WebSocket的客户端发送消息

4

我在使用Rust的actix-web时有一个问题。

很遗憾,我无法在没有代码示例的情况下进行解释,因此让我从这里开始。

struct MyWs {
    game: Arc<RwLock<Game>>,
}

impl Actor for MyWs {
    type Context = ws::WebsocketContext<Self>;
}

impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for MyWs {
    fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
        match msg {
            Ok(ws::Message::Text(text)) => {
                debug!("Echoing text with {:?}", text);
                self.game.write().unwrap().some_method();
                ctx.text(text)
            },
            _ => (),
        }
    }
}

struct Game {
    websockets: Vec<Arc<RwLock<MyWs>>>,
}

impl Game {
    pub fn new() -> GameWrapper {
        GameWrapper {
            websockets: vec![],
        }
    }

    pub fn add_websocket(&mut self, my_ws: Arc<RwLock<MyWs>>) {
        self.websockets.push(my_ws);
    }

    pub fn some_method(&mut self) {
        // Do something to influence internal state.
        self.push_state();
    }

    pub fn push_state(&self) {
        for w in self.websockets {
            // I'm not sure about this part, idk how to access the
            // WebsocketContext with which I can send stuff back to the client.
            let game_state = get_game_state_or_something();
            w.write().unwrap().ctx.text(self.game_state);
        }
    }
}

struct GameWrapper {
    pub game: Arc<RwLock<Game>>,
}

impl GameWrapper {
    pub fn new(game: Arc<RwLock<Game>>) -> GameWrapper {
        GameWrapper { game }
    }
}

#[actix_rt::main]
async fn main() -> std::io::Result<()> {
    let game = Arc::new(RwLock::new(Game::new()));
    let game_wrapper = RwLock::new(GameWrapper::new(game.clone()));
    let game_wrapper_data = web::Data::new(game_wrapper);
    HttpServer::new(move || {
        App::new()
            .app_data(game_wrapper_data.clone())
            .route("/play_game", web::get().to(play_game))
    })
    .bind(ip_port)?
    .run()
    .await
}

pub async fn play_game(
    req: HttpRequest,
    stream: web::Payload,
    game_wrapper: web::Data<GameWrapper>,
) -> impl Responder {
    let my_ws = MyWs { game: game_wrapper.game.clone() };
    let my_ws = Arc::new(RwLock::new(my_ws));
    let mut game = game_wrapper.game.write().unwrap();
    game.add_websocket(my_ws);
    let resp = ws::start(my_ws, &req, stream);  // This is the problem.
    let resp = match resp {
        Ok(resp) => resp,
        Err(e) => return HttpResponse::from_error(e),
    };
    debug!("Successfully upgraded to websocket");
    resp
}

让我先解释一下我试图做什么。当客户端连接时,我会与他们建立websocket连接。我需要一个这些websocket的列表,以便在游戏中发生变化时,可以向所有客户端推送更新。我将play_game函数绑定为play_game路由的处理程序。在这个函数中,我将HTTP GET请求升级为websocket,在此之前,我复制了Arc+RwLock的Game,并将其传递到MyWs中。您可以在MyWs impl的StreamHandler的handle函数中看到我修改了Game(使用some_method函数)。到目前为止都很好。
当我尝试获取对websocket的多个引用时,问题就出现了。您可以在play_game中看到我调用add_websocket,给Game一个对它的引用,以便在发生任何更改时可以将更新推回所有客户端。例如,在调用some_method后,我们将调用push_updates。问题在于,ws::start不接受Arc,必须接受实现WebSocketContext的Actor。
因此,我的两个主要问题是:
1.我需要一种方法来保留websocket的多个引用,以便我可以从多个位置(即:线程)与客户端对话。 2.我需要某种方法来实现这一点。我不确定如何在MyWs actor的上下文之外实际向客户端发送消息,在handle中,框架将WebSocketContext传递给了我,但我不知道如何自己拿到它。
我的解决方案:
1.在MyWs的handle(或started)功能中,将对Context的引用传递给self.game。这行不通,因为我正在移出可变的引用。 2.制作自己的ws::start可以接受一个引用。我还没有尝试过这个方法,因为看起来我会重写很多内容。 3.在Arc上实现Actor和StreamHandler,或者使用内部可变性/允许我保留多个引用的自己的结构。
这并没有真正帮助我发送消息回去,因为我仍然不知道如何在handle函数的上下文之外通过websocket发送消息。
简而言之,我如何在actix-web中获得websocket的多个引用并向客户端发送消息?

我试图打破 ws::start 但遇到了类似的问题:https://pastebin.com/WDC99Uup。 - Daniel Porteous
1个回答

2

好的,所以我在这里的困境的解决方案并不令人意外,需要改变我尝试解决这个问题的方式。与其持有多个WebSocket的引用,我真正需要的是对每个持有WebSocket的Actor的引用。我想这就是你应该这样做的方式,因为Actix是一个Actor框架。

这意味着代码应该像这样:

impl Game {
    ...

    pub fn register_actor(&mut self, actor: Addr<MyWs>) {
        self.actors.push(actor);
    }
}

pub async fn play_game(
    req: HttpRequest,
    stream: web::Payload,
    game_wrapper: web::Data<GameWrapper>,
) -> impl Responder {
    let my_ws = MyWs { game: game_wrapper.game.clone() };
    let my_ws = Arc::new(RwLock::new(my_ws));
    let mut game = game_wrapper.game.write().unwrap();
    let res = ws::start_with_addr(my_ws, &req, stream);
    let (addr, resp) = match res {
        Ok(res) => res,
        Err(e) => return HttpResponse::from_error(e),
    };
    game_manager.register_actor(handle, addr);
    debug!("Successfully upgraded to websocket");
    resp
}

你可以通过 Addr<MyWs> 向演员发送消息。
我会暂时离开这个问题,以防其他人有更好的做法。

1
你是如何通过 Addr<MyWs> 发送 Websocket 消息的?由于 Addr 是 actix 的构造,Addr::send() 接受一个 actix::Message,而 a_w_a::ws::Message 并没有实现该接口... - Momo
1
是的,你不能直接这样做,你需要使用 ws::Message 中的信息构建一个 actix::Message。 - Daniel Porteous
作为参考,我最终使用 warp 而不是 actix 来满足我的 WebSocket 需求。 - Momo

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