# Rust ###### tags: `rust` `學習紀錄` # 目前計畫 # 流水帳 DESC ## 20250608 * 跟 tauri 不同的另外兩種好像不錯的 GUI * [推薦文章](https://www.reddit.com/r/rust/comments/1hhwfdn/comment/m2vtne5/?tl=zh-hant&utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1&utm_content=share_button) * [iced](https://github.com/iced-rs/iced) * [egui](https://github.com/emilk/egui) ## 20250606 * [自製的圖片閱讀器](https://chatgpt.com/c/68427d0a-bb8c-8005-979a-d6b8905a66a6) * 現在使用的在讀取較大的檔案時會 lag * 想說用 rust 寫一個更適合自己的 ## 20250605 * [rust 版本下載功能](https://chatgpt.com/c/6841b3e2-8954-8005-9b49-5291e33f2ff5) * 有些想要客製的功能像是下載後壓縮成我要的格式 zip 之類的 ## 20250505 * [log 服務 Vector 鐵人賽](https://ithelp.ithome.com.tw/articles/10332767) * [使用一個日誌採集工具去從控制檯的標準輸出讀取日誌數據,然後將讀取到的數據發送到日誌存儲平臺](https://course.rs/logs/about-log.html) * [在重構的 app 中整合進 sqlx::migrate! 功能](https://github.com/launchbadge/sqlx/blob/main/examples/postgres/axum-social-with-tests/src/main.rs) * [範例 1](https://claude.ai/chat/95014134-5edd-45f5-9e43-873fc3f123c3) ## 20250503 * [TheAlgorithms Rust](https://github.com/TheAlgorithms/Rust/blob/master/DIRECTORY.md) ## 20250502 * [貪食蛇小遊戲](https://chatgpt.com/c/680512d7-184c-8005-9fcf-72775a219457) * 上線後有 lag 問題 手感不佳 ## 20250427 * [處理完 ping pong 機制的 websocket 範例](https://chatgpt.com/c/680e26ba-ba38-8005-a464-a05f3bd3773a) * [有趣的 rust 遊戲引擎](https://github.com/bevyengine/bevy) ## 20250424 * [rust job](https://jobs.letsgetrusty.com/?type=remote&page=1) ## 20250420 * [blogs 分頁優化](https://chatgpt.com/c/67665e13-0160-8005-bfff-a2a286ba7a0b) * 避免資料量大導致效能低落 不使用 offset 的方案 * 目前架構不太適合非 offset 的方法 思考處理方法中 ## 20250219 * [待改善的地方](https://claude.ai/chat/eb92b76b-eb76-4def-a30c-881f91b8f9a7) ## 20250113 * [build 相關知識](https://doc.rust-lang.org/cargo/reference/profiles.html) ## 20250102 * 將專案的 axum 版本更新至 0.8 ## 20250101 * [x] 除了 blogs 中有用到的圖片 定期刪除 ## 20241224 * 逐步將資料庫操作分離至對應的 repositories 中 ## 20241221 * 已經有初步可用的 blogs & 後台管理 的版本 :::spoiler * 仿 Hackmd 建立筆記的功能 用來方便撰寫 blog * 整理需要的功能 * 前端 * 前台 展示要展示的筆記 * 後台 管理寫好的筆記頁面 * 新增筆記按鈕後進入空白頁面 * 點擊既有筆記後進入編輯頁面 * 後端 * 前台 * 取得所有可展示筆記 * 取得特定筆記詳細內容 * 後台 * 新增筆記 * 取得所有筆記 * 取得特定筆記詳細內容 * 編輯特定筆記 * 刪除特定筆記 * 額外需求 * 使用 ctrl + v 貼上圖片的功能 * 參考 * [騰訊 markdown editor 其一](https://github.com/Tencent/cherry-markdown) ::: ## 20241213 * [cadvisor 參考](https://prometheus.golang.ac.cn/docs/guides/cadvisor/) * [cadvisor 參考2](https://blog.csdn.net/god_86/article/details/129654988) ## 20241212 * [想增加的 ELK 參考](https://github.com/deviantony/docker-elk?tab=readme-ov-file) ## 20241211 * [tracing log 持久化的 crate](https://github.com/tokio-rs/tracing/blob/master/examples/examples/appender-multifile.rs) * 使用 trait AppJob 方便後續新增 JOB ## 20241208 * [本地紀錄東西好用的 crate](https://github.com/rusqlite/rusqlite) # 進度 * [course.rs 目前進度](https://course.rs/basic/trait/generic.html) * [axum jwt 官網範例的他人範例](https://www.shuttle.dev/blog/2024/02/21/using-jwt-auth-rust) * [目前照做官網進度](https://rust-lang.tw/book-tw/ch07-05-separating-modules-into-different-files.html) * [文字學習進度](https://learn.lianglianglee.com/%E4%B8%93%E6%A0%8F/%E9%99%88%E5%A4%A9%20%C2%B7%20Rust%20%E7%BC%96%E7%A8%8B%E7%AC%AC%E4%B8%80%E8%AF%BE/13%20%E7%B1%BB%E5%9E%8B%E7%B3%BB%E7%BB%9F%EF%BC%9A%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8trait%E6%9D%A5%E5%AE%9A%E4%B9%89%E6%8E%A5%E5%8F%A3%EF%BC%9F.md) * [不錯的文字資料](https://shihyu.github.io/rust_hacks/ch11/02_more_about_trait.html) * [不錯的文字資料 2](https://github.com/chyyuu/aos_course_info/blob/master/sys-prog-in-rust.md) * [Rust P2P 网络应用实战 -1 P2P 网络核心概念及 Ping 程序](https://xie.infoq.cn/article/7a32cf7dc917c587fcc6b6788) # 近期 axum 網站想完成的小項目 * [ ] ws 增加 addr info * [ ] ws 增加踢掉特定連線功能 * [待實現 找到的改寫範例](https://chatgpt.com/c/670a0c38-0638-8005-b6cd-759fa7deb1b3) * [ ] [自製 extractor 的範例](https://users.rust-lang.org/t/axum-post-handler-that-accepts-multipart-and-image-jpeg/113182/3) * [x] [任務執行器參考](https://www.readfog.com/a/1752868223128276992) * [x] [bcrypt 加密 & 驗證](https://chatgpt.com/c/67473aa8-0d2c-8005-b7fc-43ad40e160eb) * [x] 前後端改成 JWT 認證 * [x] [加入 redis](https://github.com/tokio-rs/axum/blob/main/examples/tokio-redis/src/main.rs) # 影片 ## now * [連結](https://youtu.be/Pq1m1DC_WIE?list=PLrmY5pVcnuE-_CP7XZ_44HN-mDrLQV4nS&t=88) ## [教學影片](https://youtu.be/K5XoYy934Eo?list=PL3azK8C0kje1DUJbaOqce19j3R_-tIc4_) ## [axum 17h course](https://www.youtube.com/playlist?list=PLrmY5pVcnuE-_CP7XZ_44HN-mDrLQV4nS) ## [axum 80m](https://youtu.be/XZtlD_m59sM?t=1591) ## [Building Web APIs With Rust and Axum](https://youtu.be/q53xalVoc6w?t=618) ## [Rust Crate 大巡游](https://www.youtube.com/playlist?list=PL2XM89iiOzkvXwbYzwn8tjv0ES5cNhq8i) ## [Rust Programming](https://www.youtube.com/playlist?list=PL2XM89iiOzkud-BMooV19IWyBtfMVVNJj) ## [Rust 项目实操](https://www.youtube.com/playlist?list=PL2XM89iiOzksOyZsNssA2-WA8N2eZ46wG) ## [抖音商家端 Rust 业务实践@FEDAY2023_陈天壹](https://youtu.be/BImD4mHzzj4) ## [SQLx is my favorite PostgreSQL driver to use with Rust](https://youtu.be/TCERYbgvbq0) ## [Learning Rust web servers with Axum & SQLx (Twitch live stream)](https://youtu.be/lXiXdPmRqSk) ## [Mastering Rust Web Services: From Axum to CRUD Operations](https://youtu.be/JUWSy9pXgMQ) ## [替代 socket.io 的新 rust crate](https://youtu.be/HEhhWL1oUTM) # podcast * https://rustacean-station.org/ # 題目 # 實用文字記錄 ## [陈天 让Rust成为你的下一门主力语言](https://learn.lianglianglee.com/%E4%B8%93%E6%A0%8F/%E9%99%88%E5%A4%A9%20%C2%B7%20Rust%20%E7%BC%96%E7%A8%8B%E7%AC%AC%E4%B8%80%E8%AF%BE) * [同作者的其他 rust 影片](https://www.bilibili.com/video/BV1FL4y1x7MU/) * [課程 github](https://github.com/tyrchen/geektime-rust) ## [Designing a New Rust Class at Stanford: Safety in Systems Programming](https://reberhardt.com/blog/2020/10/05/designing-a-new-class-at-stanford-safety-in-systems-programming.html) * 史丹佛 110L 課程 * [簡中翻譯](https://zhuanlan.zhihu.com/p/356912397) * [影片](https://reberhardt.com/cs110l/spring-2020/) ## [Rust 程式設計語言](https://rust-lang.tw/book-tw/) ## [他人筆記 Rust 基本教學](https://hackmd.io/@ballfish/BkYVxV7Z8) ## [許多感覺很強的文章](https://www.readfog.com/t/rust) ## [用 rust 實作演算法](https://github.com/TheAlgorithms/Rust) ## [鐵人賽文章 記憶體 - stack 與 heap](https://ithelp.ithome.com.tw/articles/10218781) ## [鐵人賽文章 來玩 Rust 的框架吧! - Rocket - Part I](https://ithelp.ithome.com.tw/articles/10307215) ## [他人筆記 Rust: Rayon](https://hackmd.io/@RinHizakura/Syhq0x34F) ## [tokio 概览](https://course.rs/advance-practice/overview.html) ## [RUST中的turbofish语法(一)](https://www.jianshu.com/p/9107685ece03) ## [RUST中的turbofish语法(二)](https://www.jianshu.com/p/1dd0dbbf539b) ## docker build 不錯的選擇 * google 開放的一個安全性 & image 大小適中的選擇 ``` Q : 即使無發行版中有Linux發行版,它對我有什麼好處?它們仍然比普通的Docker鏡像小。 A : 好吧,應用程式依賴於操作系統的事實使得很難保持圖像苗條。讓我們來看看GoogleContainerTools製作的最流行的無發行版圖像(數據截至9年2023月<>日有效): gcr.io/distroless/static-debian11 - 2.34 MiB - 包括ca證書,時區數據,etc/passwd條目和/tmp目錄。靜態連結的應用程式將從此映像中受益,但是如果您的應用程式具有動態功能並且需要 libc,該怎麼辦? gcr.io/distroless/base-debian11 - 17.3 MiB - 建立在靜態映射之上,包括glibc,libssl和openssl。該圖像最適合動態連結的程式,但即使在這種情況下,您也可能需要額外的共用庫,這將我們帶到另一個級別(或層,就此而言)。 gcr.io/distroless/cc-debian11 - 19.6 MiB - 建立在基礎映射之上,包括帶有依賴項的libgcc1。解釋型或基於虛擬機的語言(Java,Python,JavaScript)呢? gcr.io/distroless/java11-debian11 - 210 MiB - 包括一個基本的Linux映射以及帶有依賴項的OpenJDK。 應用程式越動態,它需要的操作系統庫就越多,因此映像大小會膨脹。 ``` * [distroless image](https://github.com/GoogleContainerTools/distroless) * [distroless 相關資訊](https://segmentfault.com/a/1190000040255793) * [distroless 相關資訊2](https://bell-sw.com/blog/distroless-containers-for-security-and-size/) ## Rust Web 框架:Axum 入门一探 * [連結](https://juejin.cn/post/7233196954401636410) ## [鐵人賽 Day03 - 淺談Axum](https://ithelp.ithome.com.tw/articles/10314377) # News ## [NGINX 局限太多,Cloudflare 最终放弃它并用 Rust 自研了全新替代品 Pingora](https://www.infoq.cn/news/s2fa603MsEENsCmibTYI?fbclid=IwAR1utXIPFRo_TKiVu_Ggz3PNcDZrgfVZerVU1g3rLds08RdLAWs168oMOYQ) # 筆記 ## Rust 中資料儲存的方式 ### Stack ==堆疊== (中國叫==栈==) * 所有在堆疊上的資料都必須是==已知固定大小==。 * 在編譯時屬於未知或可能變更大小的資料必須儲存在堆積。 ### Heap ==堆積== (中國叫==堆==) * 堆積就比較==沒有組織==,當你要將資料放入堆積,你得要求一定大小的空間。 * 記憶體分配器(memory allocator)會找到一塊夠大的空位,標記為已佔用,然後回傳一個指標(pointer),指著該位置的位址。 * 這樣的過程稱為在==堆積上分配==(allocating on the heap),或者有時直接簡稱為分配(allocating)就好(將數值放入堆疊不會被視為是在分配)。 * 因為==指標是固定已知的大小==,所以你可以存在堆疊上。 * 但當你要存取實際資料時,你就得去透過指標取得資料。 ### 兩者在 Rust 的官網說明 * 將資料推入==堆疊==會比在堆積上分配還來的==快==,因為分配器不需要去搜尋哪邊才能存入新資料,其位置永遠在堆疊最上方。相對的,堆積就需要比較多步驟,分配器必須先找到一個夠大的空位來儲存資料,然後作下紀錄為下次分配做準備。 * 在==堆積==上取得資料也比在堆疊上取得來得==慢==,因為你需要用追蹤指標才找的到。 * 現代的處理器如果在記憶體間==跳轉越少==的話速度就越快。 * 讓我們繼續用餐廳做比喻,想像伺服器就是在餐廳為數個餐桌點餐。 * 最有效率的點餐方式就是依照餐桌順序==輪流==點餐。 * 如果幫餐桌 A 點了餐之後==跑到==餐桌 B 點,==又跑回==到 A 然後==又跑==到 B 的話,可以想像這是個浪費時間的過程。 * 同樣的道理,處理器在處理任務時,如果處理的資料==相鄰==很近(就如同存在堆疊)的話,當然比相鄰很遠(如同存在堆積)來得快。 * 當你的程式碼呼叫函式時,傳遞給函式的數值(可能包含指向堆積上資料的指標)與函式區域變數會被==推入堆疊==。 * 當函式結束時,這些數值就會被彈出。 * 追蹤哪部分的程式碼用到了堆積上的哪些資料、最小化堆積上的重複資料、以及清除堆積上沒在使用的資料確保你不會耗盡空間,這些問題都是==所有權==系統要處理的。 * 一旦你理解所有權後,你通常就不再需要經常考慮堆疊與堆積的問題,不過能理解所有權主要就是為了管理堆積有助於解釋為何它要這樣運作。 ## 字串 ### 轉換成其他型別 ```rust let guess: u32 = guess.trim() // 去掉左右的空格 & 分隔 & 換行 .parse() // 編譯成指定的型別 .expect("請輸入一個數字!"); // 錯誤時 ``` ### 取得使用者輸入的字串 ```rust use std::io; io::stdin() .read_line(&mut guess) // 把使用者 input 的值寫進變數 .expect("讀取該行失敗"); // 例外處理 ``` ## 數字 ### 必較 ```rust use std::cmp::Ordering; ``` * 給 match 用的比較大小,後有三種對應 * ![](https://i.imgur.com/C7knFJX.png) ## clipboard * 取得 clipboard 中複製的資訊 ### example ```rust // === Cargo.toml clipboard-win = "4.5.0" // === main.rs use clipboard_win::{formats, get_clipboard}; // 取得 clipboard 複製的資訊 let target: String = get_clipboard(formats::Unicode).expect("To set clipboard"); ``` ## open * 使用預設瀏覽器開啟特定網址 ### example ```rust // === Cargo.toml open = "3" // === main.rs // 組成 url get 的 string let url1 = format!("https://aaa.aaa/search/?q={}", target); let url2 = format!("https://bbb.bbb/search/?q={}&f=_all&s=create_time_DESC&syn=yes", target); let url3 = format!("https://ccc.ccc/?f_search={}", target); // 開啟 clipboard 複製的資訊 open::that(url1).unwrap(); open::that(url2).unwrap(); open::that(url3).unwrap(); ``` ## 使用 docker alpine 的問題 * 執行時發生 "not found" or "no such file" * [來源](https://mozillazg.com/2020/03/use-alpine-image-common-issues.rst.html#hidnot-found-no-such-file) * ![](https://i.imgur.com/H8QALOi.png) * 以下是網路上找到(似乎)可用的範例,未實際測試 ```dockerfile ################ #### Builder FROM rust:1.61.0-slim as builder WORKDIR /usr/src # Create blank project RUN USER=root cargo new medium-rust-dockerize # We want dependencies cached, so copy those first. COPY Cargo.toml Cargo.lock /usr/src/medium-rust-dockerize/ # Set the working directory WORKDIR /usr/src/medium-rust-dockerize # Install target platform (Cross-Compilation) --> Needed for Alpine RUN rustup target add x86_64-unknown-linux-musl # This is a dummy build to get the dependencies cached. RUN cargo build --target x86_64-unknown-linux-musl --release # Now copy in the rest of the sources COPY src /usr/src/medium-rust-dockerize/src/ # Touch main.rs to prevent cached release build RUN touch /usr/src/medium-rust-dockerize/src/main.rs # This is the actual application build. RUN cargo build --target x86_64-unknown-linux-musl --release ################ #### Runtime FROM alpine:3.16.0 AS runtime # Copy application binary from builder image COPY --from=builder /usr/src/medium-rust-dockerize/target/x86_64-unknown-linux-musl/release/medium-rust-dockerize /usr/local/bin EXPOSE 3030 # Run the application CMD ["/usr/local/bin/medium-rust-dockerize"] ``` * 使用上面的範例修改 thumbor 後,在 build 相關套件的 ring 時出現錯誤 * 猜測是 ring 不支援該編譯方式 * ![](https://i.imgur.com/3OCTT0v.png) ### 自己測試,可正常編譯 & docker run 的範例 * 沒使用額外套件 ```dockerfile # rust 使用 alpine build 的範例 FROM rust:1.69.0 AS builder # 移動到 /rust-server 資料夾 WORKDIR /rust-server # 將目前資料夾初始化成 cargo 專案,專案名稱跟資料夾同名 RUN cargo init COPY main.rs /rust-server/src/main.rs # 安裝給 alpine 使用的 architecture # https://doc.rust-lang.org/cargo/commands/cargo-build.html#compilation-options RUN rustup target add x86_64-unknown-linux-musl # 使用此 architecture build RUN cargo build --target x86_64-unknown-linux-musl --release # 最後階段 FROM alpine:3.17 WORKDIR /app # 路徑在 target 下的 architecture 名稱資料夾內,多了一層 architecture 名稱 COPY --from=builder /rust-server/target/x86_64-unknown-linux-musl/release/rust-server . EXPOSE 8080 # Set the command to run when the container starts CMD ["/app/rust-server"] ``` ## axum 源碼閱讀 ### [websocket](https://github.com/tokio-rs/axum/tree/main/examples/websockets) :::info #### 整體概述 這段程式碼是一個範例的 WebSocket 伺服器,它使用了 Rust 語言的 `axum` 和 `tower-http` 函式庫。它提供了一個 WebSocket 伺服器,用於與瀏覽器或其他 WebSocket 客戶端建立連接。 程式碼的主要結構如下: 1. 匯入必要的函式庫和模組。 2. 使用 `axum` 函式庫建立一個路由器 `app`,設定了一些路由。 3. 透過 `axum::Server` 綁定並啟動伺服器,監聽指定的 Socket 地址。 4. 定義了 `ws_handler` 函數,作為處理 WebSocket 請求的處理器。它接收 WebSocket 的升級請求,並從中提取出一些資訊,如用戶代理(user agent)和連接的 IP 地址。 5. 定義了 `handle_socket` 函數,作為處理 WebSocket 連接的狀態機。它處理了與客戶端的握手、收發消息等操作。 6. 定義了 `process_message` 函數,用於處理收到的 WebSocket 消息,並將其內容輸出到控制台。 這段程式碼使用了非同步的特性(`async/await`)和事件驅動的方式處理 WebSocket 連接。它示範了如何在 Rust 中使用 `axum` 構建 WebSocket 伺服器,以及如何處理 WebSocket 的收發消息和其他事件。 ::: :::info #### `main` 函數是程序的入口點,以下是 `main` 函數的詳細操作: 1. 初始化日誌記錄器:使用 `tracing_subscriber::registry()` 創建一個日誌記錄器,並配置日誌過濾器和格式化器。日誌過濾器從環境變量中獲取,如果未設置,則使用默認值。 2. 設置靜態文件目錄:通過 `PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("assets")` 構建靜態文件目錄的路徑。其中,`env!("CARGO_MANIFEST_DIR")` 是 Cargo 生成的項目根目錄路徑。 3. 創建應用程序路由器:使用 `Router::new()` 創建一個路由器對象。 4. 註冊 WebSocket 處理程序:使用 `.route("/ws", get(ws_handler))` 註冊一個 WebSocket 處理程序,將路徑 "/ws" 映射到 `ws_handler` 函數。 5. 註冊靜態文件服務:使用 `.fallback_service(ServeDir::new(assets_dir).append_index_html_on_directories(true))` 註冊一個靜態文件服務,將靜態文件目錄作為回退服務。這將處理所有未匹配的路由,查找對應的靜態文件並返回。 6. 添加 HTTP 跟踪日誌中間件:使用 `.layer(TraceLayer::new_for_http().make_span_with(DefaultMakeSpan::default().include_headers(true)))` 添加一個 HTTP 跟踪日誌中間件。這將記錄 HTTP 請求和響應的詳細信息,並包括請求和響應的頭部信息。 7. 綁定服務器地址並運行:使用 `axum::Server::bind(&addr).serve(app.into_make_service_with_connect_info::<SocketAddr>())` 綁定服務器地址,並將應用程序轉換為服務並運行。服務器地址是 `SocketAddr` 類型的實例,指定了服務器監聽的 IP 地址和端口。 總結來說,`main` 函數初始化日誌記錄器,設置靜態文件目錄,創建應用程序路由器,並註冊 WebSocket 處理程序和靜態文件服務。然後,添加了一個 HTTP 跟踪日誌中間件。最後,綁定服務器地址並運行服務器。該函數將在程序啟動時執行。 ::: :::info #### `ws_handler` 函數是處理 HTTP 請求的處理程序,當 HTTP GET 請求到達 WebSocket 協商的起點時會調用該函數。以下是 `ws_handler` 函數的詳細操作: 1. 獲取 `WebSocketUpgrade` 參數:函數接收 `WebSocketUpgrade` 參數,該參數表示 WebSocket 的升級過程。 2. 獲取 `user_agent` 和 `ConnectInfo(addr)`:通過 `extract` 模組中的 `TypedHeader` 和 `ConnectInfo` 提取器,從 HTTP 頭部獲取 `UserAgent` 和從客戶端連接獲取的 `SocketAddr`。 3. 檢查 `user_agent` 的存在:檢查是否獲取到了 `user_agent`。如果獲取到了,則將其轉換為字符串;如果沒有獲取到,則將其設置為 "Unknown browser"。 4. 輸出連接信息:輸出連接的相關信息,包括使用者代理(User Agent)和客戶端的 Socket 地址。輸出的格式為 `'{user_agent}' at {addr} connected.`。 5. 完成升級過程:調用 `ws.on_upgrade` 函數,將 WebSocket 連接的升級過程完成的回調函數返回。在此回調函數中,可以自定義升級過程的行為,例如傳遞額外的信息。 6. 返回實現 `IntoResponse` 的結果:將回調函數返回的結果轉換為實現 `IntoResponse` trait 的類型,以使其可以作為 HTTP 響應返回。 總結來說,`ws_handler` 函數負責處理 HTTP 請求,提取相關信息(例如使用者代理和客戶端地址),輸出連接信息,並完成 WebSocket 的升級過程。該函數將在接收到 HTTP GET 請求時被調用,並返回用於升級 WebSocket 連接的回調函數。 ::: :::info #### `handle_socket` 函數是一個處理單個 WebSocket 連接的狀態機。以下是該函數的詳細操作: 1. 發送一個 Ping 消息:首先,它向客戶端發送一個 Ping 消息,以確保客戶端能夠正常回應。如果成功發送 Ping 消息,則輸出 `Pinged {who}...`,其中 `{who}` 是客戶端的 Socket 地址;否則輸出 `Could not send ping {who}!`,並結束處理。 2. 接收客戶端的消息:接下來,它等待從客戶端接收一個消息。如果成功接收到消息,則進行下一步處理;否則輸出 `client {who} abruptly disconnected`,並結束處理。 3. 發送多個消息:使用 `for` 迴圈,它向客戶端連續發送多個消息。在每次迴圈中,它使用 `socket.send` 方法發送一個包含文字內容的 Text 消息,消息內容為 `"Hi {i} times!"`,其中 `{i}` 是迴圈的索引。如果發送消息失敗,則輸出 `client {who} abruptly disconnected`,並結束處理。在每次迴圈之間,通過 `tokio::time::sleep` 函數暫停一段時間,以模擬某種等待或事件。 4. 分離 WebSocket 的發送和接收分支:通過 `socket.split` 方法將 WebSocket 分離為發送和接收兩個部分,分別獲得 `sender` 和 `receiver`。 5. 發送非請求的消息:使用 `sender`,它會在獨立的任務(task)中向客戶端發送多個非請求的消息。在每次迴圈中,它使用 `sender.send` 方法發送一個 Text 消息,消息內容為 `"Server message {i} ..."`,其中 `{i}` 是迴圈的索引。如果發送消息失敗,則結束發送任務。 6. 接收客戶端的消息並輸出:使用 `receiver`,它在另一個獨立的任務中從客戶端接收消息。它使用 `receiver.next()` 方法獲取下一個消息,並在每次接收到消息時調用 `process_message` 函數處理該消息。如果 `process_message` 函數返回 `ControlFlow::Break`,則結束接收任務。 7. 等待任一任務完成:使用 `tokio::select!` 宏,它等待任一任務完成。這意味著它將等待發送任務完成或接收任務完成(其中一個先完成即可)。 8. 檢查發送任務的狀態:在 `tokio::select!` 宏後,它檢查發送任務的狀態。如果發送任務成功完成,則輸出 `Sender for {who} terminated gracefully.`;否則輸出 `Sender for {who} abruptly terminated.`。 9. 返回適當的 `ControlFlow` 值:根據發送任務和接收任務的狀態,它返回適當的 `ControlFlow` 值。如果發送任務和接收任務都正常完成,則返回 `ControlFlow::Continue`,以指示狀態機繼續運行。否則,返回 `ControlFlow::Break`,以指示狀態機結束。 總結來說,`handle_socket` 函數負責處理單個 WebSocket 連接的狀態。它通過發送和接收消息來與客戶端進行通信,並使用非請求的消息和控制流來管理連接的狀態。 ::: :::info #### `process_message` 函數是用於處理接收到的 WebSocket 消息的輔助函數。以下是該函數的詳細操作: 1. 檢查消息的類型:根據接收到的消息的類型進行不同的處理。 2. 如果是 Text 消息:如果接收到的消息是 Text 消息,則輸出 `>>> {who} sent str: {t}`,其中 `{who}` 是客戶端的 Socket 地址,`{t}` 是消息的內容。 3. 如果是 Binary 消息:如果接收到的消息是 Binary 消息,則輸出 `>>> {who} sent {d.len()} bytes: {d}`,其中 `{who}` 是客戶端的 Socket 地址,`{d.len()}` 是消息的字節數,`{d}` 是消息的內容。 4. 如果是 Close 消息:如果接收到的消息是 Close 消息,則進行特殊處理。它檢查是否存在 CloseFrame(關閉消息的元數據),如果存在,則輸出 `>>> {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`。 5. 如果是 Pong 消息:如果接收到的消息是 Pong 消息,則輸出 `>>> {who} sent pong with {v}`,其中 `{who}` 是客戶端的 Socket 地址,`{v}` 是 Pong 消息的內容。 6. 如果是 Ping 消息:如果接收到的消息是 Ping 消息,則輸出 `>>> {who} sent ping with {v}`,其中 `{who}` 是客戶端的 Socket 地址,`{v}` 是 Ping 消息的內容。 7. 返回 `ControlFlow::Continue`:函數返回 `ControlFlow::Continue`,以指示狀態機繼續運行。 總結來說,`process_message` 函數根據接收到的 WebSocket 消息的類型,對消息進行適當的處理並輸出相關信息。它用於在 `handle_socket` 函數中顯示接收到的消息的內容和元數據,以及進行相應的處理。 ::: # 未整理筆記 ## [HashMap](https://doc.rust-lang.org/std/collections/struct.HashMap.html) * 哈希表最核心的特點就是: **巨量的可能輸入和有限的哈希表容量。** 這就會引發哈希衝突,也就是兩個或者多個輸入的哈希被映射到了同一個位置,所以我們要==能夠處理哈希衝突==。 * 兩個主要的解決衝突方式 * chaining : 鏈地址法 * 將落在同一 index 的 value 連接起來 * search 時,找到 index 後一個一個遍歷該 index 下所有 value * ![](https://hackmd.io/_uploads/HJNbSUmS2.png) * open addressing : 開放尋址法 * 衝突發生時依照特定的規則把 value 插入其他空位 * ![](https://hackmd.io/_uploads/S1R4U8QH2.png) * [源碼](https://doc.rust-lang.org/src/std/collections/hash/map.rs.html#214-216) * ![](https://hackmd.io/_uploads/Hkm8uUOSh.png) ## opencc * [似乎是原作者的文章](https://magiclen.org/opencc-rust/) ## build 很慢時可試試的方法 * [~/.cargo/config.toml 的設定](https://docs.shipyard.rs/configuration/git-fetch-with-cli.html) * 在 .gitconfig 加入以下設定 ```shell [user] email = joelai1988@gmail.com name = kawagami [core] editor = vim [url "git@github.com:"] insteadOf = https://github.com/ [url "https://github.com/rust-lang/crates.io-index"] insteadOf = https://github.com/rust-lang/crates.io-index ``` # docker build 相關 ## 在 Multi-stage builds 中想使用 apline 或是 scratch 做為最後基底的兩個方法 * ==有些 crate 像是 ring 會導致 build 失敗== * 此時我目前還沒找到解法,只有換回其他能 build 通過的 final image 基底,像是 ubuntu 或是 debian slim 版本 * 在 build 的時候使用 rust:alpine 版本 * 可以直接用一般的流程 * COPY --from=builder /app/target/release/==APPNAME== /final/==APPNAME== * 就好 * 以下為簡單範例 :::info ```dockerfile FROM rust:alpine as builder WORKDIR /app COPY Cargo.toml . COPY /src ./src RUN cargo build --release # FROM alpine as final FROM scratch as final WORKDIR /final COPY --from=builder /app/target/release/PROJECTNAME /final/PROJECTNAME CMD ["/final/PROJECTNAME"] ``` ::: * 在 build 的時候使用 rust:1.75 之類的一般版本 ```dockerfile # 在 build 前安裝此環境 RUN rustup target add x86_64-unknown-linux-musl # 使用這個編譯 RUN cargo build --target x86_64-unknown-linux-musl ...... # 再到這位置取得編譯後的檔案 COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/APPNAME /final/APPNAME ``` * 以下為簡單範例 :::info ```dockerfile FROM rust:1.75-slim-bookworm as builder WORKDIR /app COPY Cargo.toml . COPY /src ./src RUN rustup target add x86_64-unknown-linux-musl RUN cargo build --release --target x86_64-unknown-linux-musl # FROM alpine as final FROM scratch as final WORKDIR /final COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/PROJECTNAME /final/PROJECTNAME CMD ["/final/PROJECTNAME"] ``` ::: # 演講影片 axum 的設計概念 * [影片](https://youtu.be/w1atdqNsA80) # 等待閱讀清單 * [x] [Rust 爲什麼需要 Pin、Unpin?](https://www.readfog.com/a/1727216433488826368)