203 lines
6.3 KiB
Rust
203 lines
6.3 KiB
Rust
//! df-relay 连接注册表
|
|
//!
|
|
//! 设计依据:设计文档「Layer2」—— 按 device_id 配对路由(非全局广播)。
|
|
//!
|
|
//! 注册表结构:`HashMap<conn_id, ConnHandle>` + `device_id → conn_id` 索引。
|
|
//! 配对语义:小程序 device_id ↔ 桌面端绑定该 device_id 的 token(同 device_id
|
|
//! 的小程序与桌面端互为对端)。relay 按 device_id 找到对端连接转发消息。
|
|
//!
|
|
//! 并发:tokio::sync::Mutex 单锁(注册表操作高频但临界区小,先简单后优化)。
|
|
|
|
use std::collections::HashMap;
|
|
use std::sync::atomic::{AtomicU64, Ordering};
|
|
use std::sync::Arc;
|
|
|
|
use tokio::sync::{mpsc, Mutex};
|
|
|
|
use crate::broadcast::{BroadcastMessage, ClientKind};
|
|
|
|
/// 连接唯一标识(u64 自增,进程内唯一)
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
|
|
pub struct ConnId(pub u64);
|
|
|
|
impl ConnId {
|
|
/// 伪值(用于无来源场景,如服务端构造的控制消息)
|
|
pub const NIL: ConnId = ConnId(0);
|
|
}
|
|
|
|
static NEXT_CONN_ID: AtomicU64 = AtomicU64::new(1);
|
|
|
|
/// 分配下一个 conn_id(进程内递增,从 1 起,0 留作 NIL)
|
|
pub fn next_conn_id() -> ConnId {
|
|
ConnId(NEXT_CONN_ID.fetch_add(1, Ordering::Relaxed))
|
|
}
|
|
|
|
/// 连接句柄(注册表条目)
|
|
///
|
|
/// 每个连接持一个 mpsc::UnboundedSender,广播时向其投递 BroadcastMessage,
|
|
/// 由连接自己的读取任务转发给对端 socket。
|
|
#[derive(Debug)]
|
|
pub struct ConnHandle {
|
|
/// 连接 ID
|
|
pub id: ConnId,
|
|
/// 客户端类型
|
|
pub kind: ClientKind,
|
|
/// 配对绑定的设备 ID(桌面端首次配置生成,小程序携带)
|
|
pub device_id: String,
|
|
/// 广播投递通道(连接读取任务消费 → 写入 socket)
|
|
pub sender: mpsc::UnboundedSender<BroadcastMessage>,
|
|
}
|
|
|
|
impl ConnHandle {
|
|
pub fn new(
|
|
id: ConnId,
|
|
kind: ClientKind,
|
|
device_id: String,
|
|
sender: mpsc::UnboundedSender<BroadcastMessage>,
|
|
) -> Self {
|
|
Self {
|
|
id,
|
|
kind,
|
|
device_id,
|
|
sender,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 连接注册表(RelayState 内部状态)
|
|
#[derive(Default)]
|
|
pub struct ConnRegistry {
|
|
/// conn_id → 连接句柄
|
|
by_id: HashMap<ConnId, ConnHandle>,
|
|
/// device_id → 绑定的连接集合(同 device_id 的小程序 + 桌面端)
|
|
/// 设计为 Vec:兼容同 device_id 多连接(多端登录),MVP 一般 1+1。
|
|
by_device: HashMap<String, Vec<ConnId>>,
|
|
}
|
|
|
|
impl ConnRegistry {
|
|
pub fn new() -> Self {
|
|
Self::default()
|
|
}
|
|
|
|
/// 注册连接
|
|
pub fn add(&mut self, handle: ConnHandle) {
|
|
let id = handle.id;
|
|
let device_id = handle.device_id.clone();
|
|
self.by_device.entry(device_id).or_default().push(id);
|
|
self.by_id.insert(id, handle);
|
|
}
|
|
|
|
/// 注销连接(返回其 device_id 用于清理索引)
|
|
pub fn remove(&mut self, id: ConnId) -> Option<String> {
|
|
let handle = self.by_id.remove(&id)?;
|
|
let device_id = handle.device_id.clone();
|
|
if let Some(ids) = self.by_device.get_mut(&device_id) {
|
|
ids.retain(|c| *c != id);
|
|
if ids.is_empty() {
|
|
self.by_device.remove(&device_id);
|
|
}
|
|
}
|
|
Some(device_id)
|
|
}
|
|
|
|
/// 查询 device_id 是否有在线连接(离线降级判断用)
|
|
pub fn is_device_online(&self, device_id: &str) -> bool {
|
|
self.by_device
|
|
.get(device_id)
|
|
.map(|ids| !ids.is_empty())
|
|
.unwrap_or(false)
|
|
}
|
|
|
|
/// 投递消息给指定 device_id 配对的对端连接(排除来源 source)
|
|
///
|
|
/// 路由规则:
|
|
/// - Event(Device 发):转发给同 device_id 的所有 Miniapp 连接
|
|
/// - Command(Miniapp 发):转发给同 device_id 的所有 Device 连接
|
|
/// - Control:转发给同 device_id 的所有连接(除来源)
|
|
///
|
|
/// 返回成功投递的连接数(用于日志/降级判断)。
|
|
pub fn route(&self, msg: &BroadcastMessage) -> usize {
|
|
let Some(ids) = self.by_device.get(&msg.device_id) else {
|
|
return 0;
|
|
};
|
|
let mut delivered = 0usize;
|
|
for &id in ids {
|
|
if id == msg.source {
|
|
// 不回环给来源
|
|
continue;
|
|
}
|
|
let Some(handle) = self.by_id.get(&id) else {
|
|
continue;
|
|
};
|
|
match (msg.kind, msg.from, handle.kind) {
|
|
// Event:仅投递给 Miniapp
|
|
(crate::broadcast::MessageKind::Event, _, ClientKind::Miniapp) => {}
|
|
// Command:仅投递给 Device
|
|
(crate::broadcast::MessageKind::Command, _, ClientKind::Device) => {}
|
|
// Control:全投递(除来源)
|
|
(crate::broadcast::MessageKind::Control, _, _) => {}
|
|
// 其余方向不匹配(Event 不投递给 Device,Command 不投递给 Miniapp)
|
|
_ => continue,
|
|
}
|
|
if handle.sender.send(msg.clone()).is_ok() {
|
|
delivered += 1;
|
|
}
|
|
}
|
|
delivered
|
|
}
|
|
|
|
/// 当前总连接数(诊断用)
|
|
pub fn len(&self) -> usize {
|
|
self.by_id.len()
|
|
}
|
|
|
|
/// 是否为空(诊断用)
|
|
pub fn is_empty(&self) -> bool {
|
|
self.by_id.is_empty()
|
|
}
|
|
}
|
|
|
|
/// 共享中继状态(axum State 传递)
|
|
///
|
|
/// 注册表用 tokio Mutex 包裹:广播路径在锁内调用 route,锁粒度小。
|
|
#[derive(Clone, Default)]
|
|
pub struct RelayState {
|
|
inner: Arc<Mutex<ConnRegistry>>,
|
|
}
|
|
|
|
impl RelayState {
|
|
pub fn new() -> Self {
|
|
Self::default()
|
|
}
|
|
|
|
/// 从 Arc 直接构造(用于 axum state)
|
|
pub fn from_arc(inner: Arc<Mutex<ConnRegistry>>) -> Self {
|
|
Self { inner }
|
|
}
|
|
|
|
/// 暴露内部 Arc(测试/外部 start 使用)
|
|
pub fn inner(&self) -> Arc<Mutex<ConnRegistry>> {
|
|
self.inner.clone()
|
|
}
|
|
|
|
/// 注册连接
|
|
pub async fn add_conn(&self, handle: ConnHandle) {
|
|
self.inner.lock().await.add(handle);
|
|
}
|
|
|
|
/// 注销连接
|
|
pub async fn remove_conn(&self, id: ConnId) -> Option<String> {
|
|
self.inner.lock().await.remove(id)
|
|
}
|
|
|
|
/// 查询 device 在线状态
|
|
pub async fn is_device_online(&self, device_id: &str) -> bool {
|
|
self.inner.lock().await.is_device_online(device_id)
|
|
}
|
|
|
|
/// 投递消息给对端连接(返回成功投递数)
|
|
pub async fn route(&self, msg: &BroadcastMessage) -> usize {
|
|
self.inner.lock().await.route(msg)
|
|
}
|
|
}
|