在Rust中如何高效實現WebSocket? - ahmadrosid

banq發表於2022-03-25

如果您正在學習 Web 套接字,您可能會編寫一個聊天伺服器,但今天讓我們做一些不同的事情。這是我們今天要介紹的內容:學習如何編寫 Web 套接字伺服器以提高生產力。
 
建立 websocket 伺服器:
  • 用於呈現 html 的索引端點。
  • 用於傳送事件檔案更改的 Websocket 端點。
  • 用於監聽來自 websocket 的事件檔案更改的 Javascript。

 

Actix Web
在 Rust 世界中,actix web 具有豐富的構建 Web 應用程式的功能。所以讓我們將這些庫添​​加到我們的專案中。

actix = "0.13"
actix-web = "4"
actix-web-actors = "4.1"


接下來讓我們建立用於在索引頁面呈現 html 的處理程式。
接下來,讓我們建立處理程式,在索引頁上渲染html。
在這種情況下,我們將讀取index.html檔案作為html模板,並替換table.html檔案中的內容。

#[get("/")]
async fn index() -> Result<HttpResponse> {
    let content = std::fs::read_to_string("table.html").expect("table not found");
    let body = include_str!("index.html")
        .to_string()
        .replace("{content}", &content);
    Ok(HttpResponse::build(StatusCode::OK)
        .content_type("text/html; charset=utf-8")
        .body(body))
}


併為我們的websocket伺服器新增入口點:

use websocket::FileWatcherWebsocket;

async fn echo_ws(req: HttpRequest, stream: web::Payload) -> Result<HttpResponse, Error> {
    ws::start(FileWatcherWebsocket::new(), &req, stream)
}


然後是websocket伺服器本身:

const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(1);
const CLIENT_TIMEOUT: Duration = Duration::from_secs(10);
const FILE_PATH: &'static str = "table.html";

pub struct FileWatcherWebsocket {
    hb: Instant,
    modified: SystemTime,
}

impl FileWatcherWebsocket {
    pub fn new() -> Self {
        let metadata = fs::metadata(FILE_PATH).unwrap();
        let modified = metadata.modified().unwrap();

        Self {
            hb: Instant::now(),
            modified,
        }
    }

    fn hb(&self, ctx: &mut <Self as Actor>::Context) {
        ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| {
            if Instant::now().duration_since(act.hb) > CLIENT_TIMEOUT {
                println!("Websocket Client heartbeat failed, disconnecting!");

                ctx.stop();

                return;
            }

            let modified = fs::metadata(FILE_PATH).unwrap().modified().unwrap();
            if modified.duration_since(act.modified).unwrap() > Duration::from_millis(0) {
                act.modified = modified;
                println!("Sending file changes event! {}", &FILE_PATH);
                ctx.text("file_changed")
            }

            ctx.ping(b"");
        });
    }
}

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

    fn started(&mut self, ctx: &mut Self::Context) {
        self.hb(ctx);
    }
}

impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for FileWatcherWebsocket {
    fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
        match msg {
            Ok(ws::Message::Ping(msg)) => {
                self.hb = Instant::now();
                ctx.pong(&msg);
            }
            Ok(ws::Message::Pong(_)) => {
                self.hb = Instant::now();
            }
            Ok(ws::Message::Text(text)) => ctx.text(text),
            Ok(ws::Message::Binary(bin)) => ctx.binary(bin),
            Ok(ws::Message::Close(reason)) => {
                ctx.close(reason);
                ctx.stop();
            }
            _ => ctx.stop(),
        }
    }
}


這個websocket伺服器將每隔一秒Ping一次客戶端,然後檢查最後修改的檔案table.html,如果有任何檔案變化,它將傳送*file_changed事件給客戶端。
 
 

客戶端
幸運的是,如今支援websocket客戶端開箱即用。因此,這裡是我們在index.html檔案中包含的客戶端的程式碼。

<script>
  let socket = new WebSocket("ws://localhost:8080/ws");

  socket.onopen = function(e) {
    console.log("[open] Connection established");
    console.log("Sending to server");
    socket.send("start_connection");
  };

  socket.onmessage = function(event) {
    console.log(`[message] Data received from server: ${event.data}`);
    if (event.data == "file_changed") {
      window.location.reload();
    }
  };

  socket.onclose = function(event) {
    if (event.wasClean) {
      console.log(`[close] Connection closed, code=${event.code} reason=${event.reason}`);
    } else {
      console.log('[close] Connection died');
    }
  };

  socket.onerror = function(error) {
    console.log(error)
  };

</script>


客戶端將監聽每個傳入的訊息,然後在有任何file_changed事件時重新載入瀏覽器。

這是一個非常簡單的實現,但為我節省了大量的時間。
 

結論
我認為每當我們學習新技術時,用它來解決我們的問題會非常有趣,在這種情況下,我用它來自動重新載入網頁,這讓我減少了除錯工作,我對此感到非常高興。
如果你想檢視完整的原始碼,你可以看這裡
 

相關文章