rust
學習紀錄
這段程式碼是一個範例的 WebSocket 伺服器,它使用了 Rust 語言的 axum
和 tower-http
函式庫。它提供了一個 WebSocket 伺服器,用於與瀏覽器或其他 WebSocket 客戶端建立連接。
程式碼的主要結構如下:
axum
函式庫建立一個路由器 app
,設定了一些路由。axum::Server
綁定並啟動伺服器,監聽指定的 Socket 地址。ws_handler
函數,作為處理 WebSocket 請求的處理器。它接收 WebSocket 的升級請求,並從中提取出一些資訊,如用戶代理(user agent)和連接的 IP 地址。handle_socket
函數,作為處理 WebSocket 連接的狀態機。它處理了與客戶端的握手、收發消息等操作。process_message
函數,用於處理收到的 WebSocket 消息,並將其內容輸出到控制台。這段程式碼使用了非同步的特性(async/await
)和事件驅動的方式處理 WebSocket 連接。它示範了如何在 Rust 中使用 axum
構建 WebSocket 伺服器,以及如何處理 WebSocket 的收發消息和其他事件。
main
函數是程序的入口點,以下是 main
函數的詳細操作:tracing_subscriber::registry()
創建一個日誌記錄器,並配置日誌過濾器和格式化器。日誌過濾器從環境變量中獲取,如果未設置,則使用默認值。PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("assets")
構建靜態文件目錄的路徑。其中,env!("CARGO_MANIFEST_DIR")
是 Cargo 生成的項目根目錄路徑。Router::new()
創建一個路由器對象。.route("/ws", get(ws_handler))
註冊一個 WebSocket 處理程序,將路徑 "/ws" 映射到 ws_handler
函數。.fallback_service(ServeDir::new(assets_dir).append_index_html_on_directories(true))
註冊一個靜態文件服務,將靜態文件目錄作為回退服務。這將處理所有未匹配的路由,查找對應的靜態文件並返回。.layer(TraceLayer::new_for_http().make_span_with(DefaultMakeSpan::default().include_headers(true)))
添加一個 HTTP 跟踪日誌中間件。這將記錄 HTTP 請求和響應的詳細信息,並包括請求和響應的頭部信息。axum::Server::bind(&addr).serve(app.into_make_service_with_connect_info::<SocketAddr>())
綁定服務器地址,並將應用程序轉換為服務並運行。服務器地址是 SocketAddr
類型的實例,指定了服務器監聽的 IP 地址和端口。總結來說,main
函數初始化日誌記錄器,設置靜態文件目錄,創建應用程序路由器,並註冊 WebSocket 處理程序和靜態文件服務。然後,添加了一個 HTTP 跟踪日誌中間件。最後,綁定服務器地址並運行服務器。該函數將在程序啟動時執行。
ws_handler
函數是處理 HTTP 請求的處理程序,當 HTTP GET 請求到達 WebSocket 協商的起點時會調用該函數。以下是 ws_handler
函數的詳細操作:WebSocketUpgrade
參數:函數接收 WebSocketUpgrade
參數,該參數表示 WebSocket 的升級過程。user_agent
和 ConnectInfo(addr)
:通過 extract
模組中的 TypedHeader
和 ConnectInfo
提取器,從 HTTP 頭部獲取 UserAgent
和從客戶端連接獲取的 SocketAddr
。user_agent
的存在:檢查是否獲取到了 user_agent
。如果獲取到了,則將其轉換為字符串;如果沒有獲取到,則將其設置為 "Unknown browser"。'{user_agent}' at {addr} connected.
。ws.on_upgrade
函數,將 WebSocket 連接的升級過程完成的回調函數返回。在此回調函數中,可以自定義升級過程的行為,例如傳遞額外的信息。IntoResponse
的結果:將回調函數返回的結果轉換為實現 IntoResponse
trait 的類型,以使其可以作為 HTTP 響應返回。總結來說,ws_handler
函數負責處理 HTTP 請求,提取相關信息(例如使用者代理和客戶端地址),輸出連接信息,並完成 WebSocket 的升級過程。該函數將在接收到 HTTP GET 請求時被調用,並返回用於升級 WebSocket 連接的回調函數。
handle_socket
函數是一個處理單個 WebSocket 連接的狀態機。以下是該函數的詳細操作:Pinged {who}...
,其中 {who}
是客戶端的 Socket 地址;否則輸出 Could not send ping {who}!
,並結束處理。client {who} abruptly disconnected
,並結束處理。for
迴圈,它向客戶端連續發送多個消息。在每次迴圈中,它使用 socket.send
方法發送一個包含文字內容的 Text 消息,消息內容為 "Hi {i} times!"
,其中 {i}
是迴圈的索引。如果發送消息失敗,則輸出 client {who} abruptly disconnected
,並結束處理。在每次迴圈之間,通過 tokio::time::sleep
函數暫停一段時間,以模擬某種等待或事件。socket.split
方法將 WebSocket 分離為發送和接收兩個部分,分別獲得 sender
和 receiver
。sender
,它會在獨立的任務(task)中向客戶端發送多個非請求的消息。在每次迴圈中,它使用 sender.send
方法發送一個 Text 消息,消息內容為 "Server message {i} ..."
,其中 {i}
是迴圈的索引。如果發送消息失敗,則結束發送任務。receiver
,它在另一個獨立的任務中從客戶端接收消息。它使用 receiver.next()
方法獲取下一個消息,並在每次接收到消息時調用 process_message
函數處理該消息。如果 process_message
函數返回 ControlFlow::Break
,則結束接收任務。tokio::select!
宏,它等待任一任務完成。這意味著它將等待發送任務完成或接收任務完成(其中一個先完成即可)。tokio::select!
宏後,它檢查發送任務的狀態。如果發送任務成功完成,則輸出 Sender for {who} terminated gracefully.
;否則輸出 Sender for {who} abruptly terminated.
。ControlFlow
值:根據發送任務和接收任務的狀態,它返回適當的 ControlFlow
值。如果發送任務和接收任務都正常完成,則返回 ControlFlow::Continue
,以指示狀態機繼續運行。否則,返回 ControlFlow::Break
,以指示狀態機結束。總結來說,handle_socket
函數負責處理單個 WebSocket 連接的狀態。它通過發送和接收消息來與客戶端進行通信,並使用非請求的消息和控制流來管理連接的狀態。
process_message
函數是用於處理接收到的 WebSocket 消息的輔助函數。以下是該函數的詳細操作:>>> {who} sent str: {t}
,其中 {who}
是客戶端的 Socket 地址,{t}
是消息的內容。>>> {who} sent {d.len()} bytes: {d}
,其中 {who}
是客戶端的 Socket 地址,{d.len()}
是消息的字節數,{d}
是消息的內容。>>> {who} sent close with code {cf.code} and reason
{cf.reason}``,其中 {who}
是客戶端的 Socket 地址,{cf.code}
是關閉消息的狀態碼,{cf.reason}
是關閉消息的原因。如果不存在 CloseFrame,則輸出 >>> {who} somehow sent close message without CloseFrame
。>>> {who} sent pong with {v}
,其中 {who}
是客戶端的 Socket 地址,{v}
是 Pong 消息的內容。>>> {who} sent ping with {v}
,其中 {who}
是客戶端的 Socket 地址,{v}
是 Ping 消息的內容。ControlFlow::Continue
:函數返回 ControlFlow::Continue
,以指示狀態機繼續運行。總結來說,process_message
函數根據接收到的 WebSocket 消息的類型,對消息進行適當的處理並輸出相關信息。它用於在 handle_socket
函數中顯示接收到的消息的內容和元數據,以及進行相應的處理。