» Rust:使用Socket.IO创建Web Chat App在线聊天应用 » 2. 开发 » 2.2 联系人列表

联系人列表

使用 type UserStore = HashMap<String, User>User 结构体的 map)在内存中保存所有用户信息。

在大规模项目中,你需要使用数据库来存储信息。

添加 serde 以序列化、反序列化数据:

cargo add serde

更新 src/main.rs:

@@ -1,20 +1,66 @@
-use socketioxide::{extract::SocketRef, SocketIo};
+use std::collections::HashMap;
+
+use serde::{Deserialize, Serialize};
+use socketioxide::{
+    extract::{Data, SocketRef, State},
+    SocketIo,
+};
+use tokio::{net::TcpListener, sync::RwLock};
 use tower::ServiceBuilder;
 use tower_http::cors::CorsLayer;
 
 const PORT: i32 = 4000;
 
+#[derive(Serialize, Deserialize, Debug, Clone)]
+struct User {
+    name: String,
+    emoji: String,
+    sid: Option<String>,
+}
+
+type UserStore = HashMap<String, User>;
+
+async fn on_connect(s: SocketRef) {
+    println!("A user connected {}", s.id);
+
+    s.on(
+        "user-join",
+        |s: SocketRef, Data::<User>(user), users: State<RwLock<UserStore>>| async move {
+            if user.name == "" {
+                return;
+            }
+            println!("User {} => {} {} joined", s.id, user.emoji, user.name);
+            let new_user = User {
+                name: user.name,
+                emoji: user.emoji,
+                sid: Some(s.id.to_string()),
+            };
+            let mut binding = users.write().await;
+            binding.insert(s.id.to_string(), new_user);
+            let items: Vec<(String, User)> = binding
+                .iter()
+                .map(|(k, v)| (k.clone(), v.clone()))
+                .collect();
+            // If you provide array-like data (tuple, vec, arrays), it will be considered as multiple arguments.
+            // Therefore if you want to send an array as the first argument of the payload, you need to wrap it in an array or a tuple.
+            s.broadcast().emit("contacts", (items.clone(),)).ok();
+            s.emit("contacts", (items,)).ok(); // Emit to itself as well
+        },
+    )
+}
+
 #[tokio::main]
 async fn main() -> Result<(), Box<dyn std::error::Error>> {
-    let (layer, io) = SocketIo::builder().build_layer();
-    io.ns("/", |s: SocketRef| println!("A user connected {}", s.id));
+    let start_users: RwLock<UserStore> = HashMap::new().into();
+    let (layer, io) = SocketIo::builder().with_state(start_users).build_layer();
+    io.ns("/", on_connect);
 
     let app = axum::Router::new().layer(
         ServiceBuilder::new()
             .layer(CorsLayer::permissive())
             .layer(layer),
     );
-    let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", PORT))
+    let listener = TcpListener::bind(format!("0.0.0.0:{}", PORT))
         .await
         .unwrap();
     println!("Chat server serving at localhost:{}", PORT);
  • .with_state(...)HashMap<String, User> 用作所有 socket 处理函数都可以访问的全局状态。
  • items: Vec<(String, User)> 是一个 tuple 的 vector。它使得 js 客户端将数据解析成 Map 的时候更容易。
  • s.broadcast().emit(...) 将最新的联系人列表广播给所有用户,除了发送者自身。故,后面通过 s.emit("contacts", ...) 补发给自身。