我的问题是结合使用actix-web和Rust。
不幸的是,如果没有一个繁琐的代码示例,我将无法解释这一点,所以让我从此开始。
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。我需要这些网络套接字的列表,因此当Game中发生某些变化时,我可以将更新推送给所有客户端。
我将play_game
函数绑定为play_game
路由的处理程序。在此功能中,我将HTTP get请求升级到websocket。在此之前,我制作了游戏的Arc + RwLock的副本,并将其传递到websocket结构MyWs中。您可以在StreamHandler的MyWs impl的handle
函数中看到我修改了Game(使用some_method
函数)。到目前为止很好。
当我尝试获取WebSocket的多个引用时,事情发生了爆炸。您可以在play_game
中看到我称为add_websocket
,为Game提供了对它的引用,因此当发生更改时,它可以将更新推送回所有客户端。例如,在调用some_method
之后,我们将调用push_updates
。问题在于,ws::start
不能容纳弧线,它必须容纳可通过WebSocketContext向StreamHandler施加作用的Actor。
所以我的两个主要问题是:
handle
,但我不知道该如何亲自操作。我的解决方法:
handle
(或started
)函数中,将对Context的引用传递到self.game
中。这不起作用,因为我要移走可变的引用。ws::start
,可以作为参考。我还没有尝试过,因为看来我最终要重写很多。这实际上并没有帮助我发回邮件,因为我仍然不知道如何在handle
函数的上下文之外通过websocket发回邮件。
很抱歉,这个问题的长度。 tl; dr是如何在actix-web中获得对websocket的多个引用,并与之一起向客户端发送消息?
以下是我正在使用的每个组件的相关文档:
答案 0 :(得分:0)
好的,所以解决我的困境的方法毫无疑问地改变了我试图解决这个问题的方式。我真正需要的是对持有websocket的每个参与者的引用,而不是对websocket的多个引用。考虑到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>
来向演员发送消息。
如果其他人对如何更好地完成这件事有想法,我将暂时离开这个问题。