refactor: 异步 RPC
This commit is contained in:
Generated
+1
@@ -847,6 +847,7 @@ dependencies = [
|
||||
"postcard",
|
||||
"serde",
|
||||
"symphonia",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
]
|
||||
|
||||
@@ -20,6 +20,7 @@ serde = { version = "1.0", features = ["derive"] }
|
||||
postcard = { version = "1.0", features = ["alloc", "use-std"] }
|
||||
parking_lot = "0.12.5"
|
||||
dashmap = "6.1.0"
|
||||
thiserror = "2.0.17"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
alsa = "0.11"
|
||||
|
||||
@@ -31,14 +31,12 @@ mod session;
|
||||
pub use pipeline::{PipelineHandle, PlaybackPipeline, RecordPipeline};
|
||||
pub use session::Session;
|
||||
|
||||
use crate::net::command::{
|
||||
AudioState, Command, CommandError, CommandResult, DeviceInfo, SetVolumeResponse, ShellRequest,
|
||||
ShellResponse,
|
||||
};
|
||||
use crate::net::command::CommandResult;
|
||||
use crate::net::discovery::Discovery;
|
||||
use crate::net::event::{EventBus, EventBusSubscription, EventData};
|
||||
use crate::net::network::{AudioSocket, Connection};
|
||||
use crate::net::protocol::ControlPacket;
|
||||
use crate::net::rpc::RpcBuilder;
|
||||
use crate::net::sync::now_us;
|
||||
use anyhow::{Result, anyhow};
|
||||
use session::handshake;
|
||||
@@ -254,11 +252,22 @@ impl Client {
|
||||
ControlPacket::RpcResponse { id, result } => {
|
||||
session.resolve_rpc(id, result);
|
||||
}
|
||||
// todo 耗时任务,异步响应
|
||||
ControlPacket::RpcRequest { id, command } => {
|
||||
let result = self.handle_command(session, command).await;
|
||||
// 处理 RPC 请求(支持超时和异步控制)
|
||||
ControlPacket::RpcRequest {
|
||||
id,
|
||||
run_async,
|
||||
timeout,
|
||||
command,
|
||||
} => {
|
||||
let session_clone = session.clone();
|
||||
session
|
||||
.send(&ControlPacket::RpcResponse { id, result })
|
||||
.rpc_manager
|
||||
.handle_rpc_request(id, run_async, timeout, command, move |pck| {
|
||||
let s = session_clone.clone();
|
||||
async move {
|
||||
return s.send(&pck).await;
|
||||
}
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
ControlPacket::StartRecording { config } => {
|
||||
@@ -294,72 +303,6 @@ impl Client {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 处理 RPC 命令
|
||||
async fn handle_command(&self, session: &Arc<Session>, command: Command) -> CommandResult {
|
||||
match command {
|
||||
Command::Shell(req) => self.handle_shell(req),
|
||||
Command::GetInfo => self.handle_get_info(session),
|
||||
Command::Ping { timestamp } => {
|
||||
let now = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis() as u64;
|
||||
CommandResult::Pong {
|
||||
timestamp,
|
||||
server_time: now,
|
||||
}
|
||||
}
|
||||
Command::SetVolume(req) => {
|
||||
let prev = session.set_volume(req.volume);
|
||||
CommandResult::Volume(SetVolumeResponse {
|
||||
previous: prev,
|
||||
current: session.volume(),
|
||||
})
|
||||
}
|
||||
_ => CommandResult::Error(CommandError::not_implemented()),
|
||||
}
|
||||
}
|
||||
|
||||
/// 处理 Shell 命令
|
||||
fn handle_shell(&self, req: ShellRequest) -> CommandResult {
|
||||
let mut cmd = std::process::Command::new("sh");
|
||||
cmd.arg("-c").arg(&req.command);
|
||||
|
||||
if let Some(cwd) = &req.cwd {
|
||||
cmd.current_dir(cwd);
|
||||
}
|
||||
|
||||
if let Some(env) = &req.env {
|
||||
for (k, v) in env {
|
||||
cmd.env(k, v);
|
||||
}
|
||||
}
|
||||
|
||||
match cmd.output() {
|
||||
Ok(output) => CommandResult::Shell(ShellResponse {
|
||||
stdout: String::from_utf8_lossy(&output.stdout).to_string(),
|
||||
stderr: String::from_utf8_lossy(&output.stderr).to_string(),
|
||||
exit_code: output.status.code().unwrap_or(-1),
|
||||
}),
|
||||
Err(e) => CommandResult::Error(CommandError::internal(e.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
/// 处理获取设备信息
|
||||
fn handle_get_info(&self, session: &Arc<Session>) -> CommandResult {
|
||||
CommandResult::Info(DeviceInfo {
|
||||
model: self.config.model.clone(),
|
||||
serial_number: self.config.serial_number.clone(),
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
uptime_secs: session.uptime_secs(),
|
||||
audio_state: AudioState {
|
||||
is_recording: session.is_recording(),
|
||||
is_playing: session.is_playing(),
|
||||
volume: session.volume(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/// 清理资源
|
||||
async fn cleanup(&self) {
|
||||
let mut session_guard = self.session.write().await;
|
||||
@@ -372,25 +315,15 @@ impl Client {
|
||||
// ==================== 公开 API ====================
|
||||
|
||||
/// 执行 RPC 命令
|
||||
pub async fn execute(&self, command: Command) -> Result<CommandResult> {
|
||||
pub async fn rpc(&self, builder: &RpcBuilder) -> Result<CommandResult> {
|
||||
let session_guard = self.session.read().await;
|
||||
if let Some(session) = session_guard.as_ref() {
|
||||
session.execute(command).await
|
||||
session.rpc(builder).await
|
||||
} else {
|
||||
Err(anyhow!("Not connected"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 执行 Shell 命令
|
||||
pub async fn shell(&self, cmd: &str) -> Result<ShellResponse> {
|
||||
let result = self.execute(Command::shell(cmd)).await?;
|
||||
match result {
|
||||
CommandResult::Shell(resp) => Ok(resp),
|
||||
CommandResult::Error(e) => Err(anyhow!("{}", e)),
|
||||
_ => Err(anyhow!("Unexpected response type")),
|
||||
}
|
||||
}
|
||||
|
||||
/// 发送事件
|
||||
pub async fn send_event(&self, event: EventData) -> Result<()> {
|
||||
let session_guard = self.session.read().await;
|
||||
|
||||
@@ -28,7 +28,6 @@ use crate::net::protocol::AudioPacket;
|
||||
use crate::net::sync::now_us;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
//! - 会话生命周期
|
||||
//! - 活动音频流追踪
|
||||
|
||||
use crate::net::command::{Command, CommandResult};
|
||||
use crate::net::command::CommandResult;
|
||||
use crate::net::network::{AudioSocket, Connection};
|
||||
use crate::net::protocol::{ClientInfo, ControlPacket};
|
||||
use crate::net::rpc::RpcManager;
|
||||
use crate::net::rpc::{RpcBuilder, RpcManager};
|
||||
use crate::net::sync::ClockSync;
|
||||
use anyhow::{Context, Result, anyhow};
|
||||
use std::net::SocketAddr;
|
||||
@@ -90,7 +90,7 @@ pub struct Session {
|
||||
pub server_audio_addr: SocketAddr,
|
||||
|
||||
/// RPC 管理器
|
||||
pub rpc: Arc<RpcManager>,
|
||||
pub rpc_manager: Arc<RpcManager>,
|
||||
|
||||
/// 会话取消令牌
|
||||
pub cancel: CancellationToken,
|
||||
@@ -120,7 +120,7 @@ impl Session {
|
||||
conn,
|
||||
audio_socket,
|
||||
server_audio_addr,
|
||||
rpc: Arc::new(RpcManager::new()),
|
||||
rpc_manager: Arc::new(RpcManager::new()),
|
||||
cancel,
|
||||
clock: Arc::new(parking_lot::Mutex::new(ClockSync::new(100))),
|
||||
pipelines: parking_lot::Mutex::new(ActivePipelines::default()),
|
||||
@@ -161,18 +161,20 @@ impl Session {
|
||||
.update(client_send_ts, server_ts, client_recv_ts);
|
||||
}
|
||||
|
||||
/// 发起 RPC 调用(新版)
|
||||
pub async fn execute(&self, command: Command) -> Result<CommandResult> {
|
||||
let (id, rx) = self.rpc.register();
|
||||
self.conn
|
||||
.send(&ControlPacket::RpcRequest { id, command })
|
||||
/// 发起 RPC 调用(支持超时和异步控制)
|
||||
pub async fn rpc(&self, request: &RpcBuilder) -> Result<CommandResult> {
|
||||
let result = self
|
||||
.rpc_manager
|
||||
.call(request, |pck| async move {
|
||||
return self.conn.send(&pck).await;
|
||||
})
|
||||
.await?;
|
||||
rx.await.context("RPC channel closed")
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// 处理 RPC 响应
|
||||
pub fn resolve_rpc(&self, id: u32, result: CommandResult) {
|
||||
self.rpc.resolve(id, result);
|
||||
self.rpc_manager.resolve(id, result);
|
||||
}
|
||||
|
||||
/// 开始录音管道
|
||||
|
||||
@@ -229,22 +229,3 @@ impl AudioBus {
|
||||
self.subscribers.contains_key(addr)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_audio_bus_basic() {
|
||||
let bus = AudioBus::new().await.unwrap();
|
||||
let addr: SocketAddr = "127.0.0.1:12345".parse().unwrap();
|
||||
|
||||
let id = bus.register(addr, true);
|
||||
assert!(bus.is_registered(&addr));
|
||||
assert_eq!(bus.subscriber_count(), 1);
|
||||
|
||||
bus.unregister(&addr);
|
||||
assert!(!bus.is_registered(&addr));
|
||||
assert_eq!(bus.subscriber_count(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,13 +44,12 @@ pub use stream::{FilePlaybackStream, RecorderStream, StreamHandle};
|
||||
|
||||
use crate::audio::config::AudioConfig;
|
||||
use crate::audio::wav::WavReader;
|
||||
use crate::net::command::{
|
||||
AudioState, Command, CommandError, CommandResult, DeviceInfo, ShellResponse,
|
||||
};
|
||||
use crate::net::command::CommandResult;
|
||||
use crate::net::discovery::Discovery;
|
||||
use crate::net::event::{EventBus, EventBusSubscription, EventData};
|
||||
use crate::net::network::Connection;
|
||||
use crate::net::protocol::ControlPacket;
|
||||
use crate::net::rpc::RpcBuilder;
|
||||
use crate::net::sync::now_us;
|
||||
use anyhow::{Context, Result, anyhow};
|
||||
use std::net::SocketAddr;
|
||||
@@ -289,64 +288,28 @@ impl Server {
|
||||
ControlPacket::RpcResponse { id, result } => {
|
||||
session.resolve_rpc(id, result);
|
||||
}
|
||||
// todo 耗时任务,异步响应
|
||||
ControlPacket::RpcRequest { id, command } => {
|
||||
let result = self.handle_command(session, command).await;
|
||||
ControlPacket::RpcRequest {
|
||||
id,
|
||||
run_async,
|
||||
timeout,
|
||||
command,
|
||||
} => {
|
||||
let session_clone = session.clone();
|
||||
session
|
||||
.send(&ControlPacket::RpcResponse { id, result })
|
||||
.rpc_manager
|
||||
.handle_rpc_request(id, run_async, timeout, command, move |pck| {
|
||||
let s = session_clone.clone();
|
||||
async move {
|
||||
return s.send(&pck).await;
|
||||
}
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 处理 RPC 命令
|
||||
async fn handle_command(&self, session: &Arc<Session>, command: Command) -> CommandResult {
|
||||
match command {
|
||||
Command::Shell(req) => {
|
||||
// 转发给客户端执行
|
||||
match session.execute(Command::Shell(req)).await {
|
||||
Ok(result) => result,
|
||||
Err(e) => CommandResult::Error(CommandError::internal(e.to_string())),
|
||||
}
|
||||
}
|
||||
Command::GetInfo => {
|
||||
// 返回服务器信息
|
||||
CommandResult::Info(DeviceInfo {
|
||||
model: "XiaoAi-Server".to_string(),
|
||||
serial_number: "SERVER-001".to_string(),
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
uptime_secs: self.started_at.elapsed().as_secs(),
|
||||
audio_state: AudioState {
|
||||
is_recording: session.is_recording(),
|
||||
is_playing: session.is_playing(),
|
||||
volume: 100,
|
||||
},
|
||||
})
|
||||
}
|
||||
Command::Ping { timestamp } => {
|
||||
let now = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis() as u64;
|
||||
CommandResult::Pong {
|
||||
timestamp,
|
||||
server_time: now,
|
||||
}
|
||||
}
|
||||
Command::SetVolume(_) => {
|
||||
// 转发给客户端
|
||||
match session.execute(command).await {
|
||||
Ok(result) => result,
|
||||
Err(e) => CommandResult::Error(CommandError::internal(e.to_string())),
|
||||
}
|
||||
}
|
||||
_ => CommandResult::Error(CommandError::not_implemented()),
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 公开 API ====================
|
||||
|
||||
/// 获取所有已连接的客户端地址
|
||||
@@ -364,20 +327,10 @@ impl Server {
|
||||
self.started_at.elapsed().as_secs()
|
||||
}
|
||||
|
||||
/// 向客户端发起 RPC 调用(新版)
|
||||
pub async fn execute(&self, addr: SocketAddr, command: Command) -> Result<CommandResult> {
|
||||
/// 向客户端发起 RPC
|
||||
pub async fn rpc(&self, addr: SocketAddr, builder: &RpcBuilder) -> Result<CommandResult> {
|
||||
let session = self.sessions.get(&addr).context("Session not found")?;
|
||||
session.execute(command).await
|
||||
}
|
||||
|
||||
/// 执行 Shell 命令
|
||||
pub async fn shell(&self, addr: SocketAddr, cmd: &str) -> Result<ShellResponse> {
|
||||
let result = self.execute(addr, Command::shell(cmd)).await?;
|
||||
match result {
|
||||
CommandResult::Shell(resp) => Ok(resp),
|
||||
CommandResult::Error(e) => Err(anyhow!("{}", e)),
|
||||
_ => Err(anyhow!("Unexpected response type")),
|
||||
}
|
||||
session.rpc(builder).await
|
||||
}
|
||||
|
||||
/// 发送事件
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
//! 音频流的实际处理由 AudioBus 和 Stream 模块负责。
|
||||
|
||||
use crate::audio::config::AudioConfig;
|
||||
use crate::net::command::{Command, CommandResult};
|
||||
use crate::net::command::CommandResult;
|
||||
use crate::net::network::Connection;
|
||||
use crate::net::protocol::{ClientInfo, ControlPacket};
|
||||
use crate::net::rpc::RpcManager;
|
||||
use crate::net::rpc::{RpcBuilder, RpcManager};
|
||||
use anyhow::{Context, Result};
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
@@ -98,7 +98,7 @@ pub struct Session {
|
||||
pub conn: Arc<Connection>,
|
||||
|
||||
/// RPC 管理器
|
||||
pub rpc: Arc<RpcManager>,
|
||||
pub rpc_manager: Arc<RpcManager>,
|
||||
|
||||
/// TCP 地址(用作会话 ID)
|
||||
pub tcp_addr: SocketAddr,
|
||||
@@ -131,7 +131,7 @@ impl Session {
|
||||
Self {
|
||||
info,
|
||||
conn,
|
||||
rpc: Arc::new(RpcManager::new()),
|
||||
rpc_manager: Arc::new(RpcManager::new()),
|
||||
tcp_addr,
|
||||
audio_addr,
|
||||
cancel,
|
||||
@@ -166,18 +166,20 @@ impl Session {
|
||||
self.conn.recv().await
|
||||
}
|
||||
|
||||
/// 执行 RPC 调用(新版,使用 Command)
|
||||
pub async fn execute(&self, command: Command) -> Result<CommandResult> {
|
||||
let (id, rx) = self.rpc.register();
|
||||
self.conn
|
||||
.send(&ControlPacket::RpcRequest { id, command })
|
||||
/// 发起 RPC 调用(支持超时和异步控制)
|
||||
pub async fn rpc(&self, request: &RpcBuilder) -> Result<CommandResult> {
|
||||
let result = self
|
||||
.rpc_manager
|
||||
.call(request, |pck| async move {
|
||||
return self.conn.send(&pck).await;
|
||||
})
|
||||
.await?;
|
||||
rx.await.context("RPC channel closed")
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// 处理 RPC 响应
|
||||
pub fn resolve_rpc(&self, id: u32, result: CommandResult) {
|
||||
self.rpc.resolve(id, result);
|
||||
self.rpc_manager.resolve(id, result);
|
||||
}
|
||||
|
||||
/// 开始录音流
|
||||
|
||||
@@ -10,6 +10,7 @@ use std::sync::Arc;
|
||||
use xiao::app::client::{Client, ClientConfig};
|
||||
use xiao::net::command::Command;
|
||||
use xiao::net::event::EventData;
|
||||
use xiao::net::rpc::RpcBuilder;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
@@ -61,22 +62,20 @@ async fn main() -> anyhow::Result<()> {
|
||||
println!("Connected to server!");
|
||||
println!("Running client-side tests...\n");
|
||||
|
||||
// 1. 测试向服务器发送 Ping
|
||||
println!("1️⃣ Testing Ping to server...");
|
||||
match client.execute(Command::ping()).await {
|
||||
Ok(result) => println!(" ✅ Pong received: {:?}", result),
|
||||
Err(e) => println!(" ❌ Ping failed: {}", e),
|
||||
// 1. 测试简单的 Shell 命令
|
||||
println!("1️⃣ Testing Shell command...");
|
||||
match client
|
||||
.rpc(&RpcBuilder::default(Command::Shell(
|
||||
"echo 'Hello from client'".to_string(),
|
||||
)))
|
||||
.await
|
||||
{
|
||||
Ok(resp) => println!(" ✅ Command executed: {:?}", resp),
|
||||
Err(e) => println!(" ❌ Command failed: {}", e),
|
||||
}
|
||||
|
||||
// 2. 测试获取服务器信息
|
||||
println!("\n2️⃣ Testing GetInfo from server...");
|
||||
match client.execute(Command::GetInfo).await {
|
||||
Ok(result) => println!(" ✅ Server info: {:?}", result),
|
||||
Err(e) => println!(" ❌ GetInfo failed: {}", e),
|
||||
}
|
||||
|
||||
// 3. 发送客户端事件
|
||||
println!("\n3️⃣ Sending event to server...");
|
||||
// 2. 发送客户端事件
|
||||
println!("\n2️⃣ Sending event to server...");
|
||||
match client
|
||||
.send_event(EventData::Hello {
|
||||
message: "from client!".to_string(),
|
||||
|
||||
@@ -11,6 +11,7 @@ use xiao::app::server::{Server, ServerConfig};
|
||||
use xiao::audio::config::AudioConfig;
|
||||
use xiao::net::command::Command;
|
||||
use xiao::net::event::EventData;
|
||||
use xiao::net::rpc::RpcBuilder;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
@@ -65,32 +66,34 @@ async fn main() -> anyhow::Result<()> {
|
||||
println!("Client connected: {}", addr);
|
||||
println!("Running demo tests...\n");
|
||||
|
||||
// 1. 测试 Ping
|
||||
println!("1️⃣ Testing Ping...");
|
||||
match server.execute(addr, Command::ping()).await {
|
||||
Ok(result) => println!(" ✅ Ping result: {:?}", result),
|
||||
Err(e) => println!(" ❌ Ping failed: {}", e),
|
||||
// 1. 测试 Shell 命令(同步)
|
||||
println!("1️⃣ Testing Shell RPC (sync)...");
|
||||
match server
|
||||
.rpc(
|
||||
addr,
|
||||
&RpcBuilder::default(Command::Shell("echo 'Hello from server'".to_string())),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(resp) => println!(" ✅ Command executed: {:?}", resp),
|
||||
Err(e) => println!(" ❌ Command failed: {}", e),
|
||||
}
|
||||
|
||||
// 2. 测试获取设备信息
|
||||
println!("\n2️⃣ Testing GetInfo...");
|
||||
match server.execute(addr, Command::GetInfo).await {
|
||||
Ok(result) => println!(" ✅ Device info: {:?}", result),
|
||||
Err(e) => println!(" ❌ GetInfo failed: {}", e),
|
||||
// 2. 测试 Shell 命令(带超时)
|
||||
println!("\n2️⃣ Testing Shell RPC with timeout (1s)...");
|
||||
match server
|
||||
.rpc(
|
||||
addr,
|
||||
&RpcBuilder::default(Command::Shell("sleep 2".to_string())).set_timeout(1000),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(resp) => println!(" ✅ Command executed: {:?}", resp),
|
||||
Err(e) => println!(" ❌ Command failed: {}", e),
|
||||
}
|
||||
|
||||
// 3. 测试 Shell 命令
|
||||
println!("\n3️⃣ Testing Shell RPC...");
|
||||
match server.shell(addr, "echo 'Hello from XiaoAi!'").await {
|
||||
Ok(resp) => {
|
||||
println!(" ✅ stdout: {}", resp.stdout.trim());
|
||||
println!(" ✅ exit_code: {}", resp.exit_code);
|
||||
}
|
||||
Err(e) => println!(" ❌ Shell failed: {}", e),
|
||||
}
|
||||
|
||||
// 4. 测试事件
|
||||
println!("\n4️⃣ Broadcasting notification event...");
|
||||
// 3. 测试事件
|
||||
println!("\n3️⃣ Broadcasting notification event...");
|
||||
match server
|
||||
.send_event(
|
||||
addr,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod app;
|
||||
pub mod audio;
|
||||
pub mod net;
|
||||
pub mod utils;
|
||||
|
||||
@@ -28,147 +28,10 @@
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::utils::shell::{ShellRequest, ShellResponse};
|
||||
|
||||
// ==================== 命令请求类型 ====================
|
||||
|
||||
/// Shell 命令请求
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct ShellRequest {
|
||||
/// 要执行的命令
|
||||
pub command: String,
|
||||
/// 工作目录(可选)
|
||||
pub cwd: Option<String>,
|
||||
/// 环境变量(可选)
|
||||
pub env: Option<Vec<(String, String)>>,
|
||||
/// 超时秒数(可选)
|
||||
pub timeout_secs: Option<u32>,
|
||||
}
|
||||
|
||||
/// Shell 命令响应
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
pub struct ShellResponse {
|
||||
pub stdout: String,
|
||||
pub stderr: String,
|
||||
pub exit_code: i32,
|
||||
}
|
||||
|
||||
/// 获取设备信息请求
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
pub struct GetInfoRequest;
|
||||
|
||||
/// 设备信息响应
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct DeviceInfo {
|
||||
pub model: String,
|
||||
pub serial_number: String,
|
||||
pub version: String,
|
||||
pub uptime_secs: u64,
|
||||
pub audio_state: AudioState,
|
||||
}
|
||||
|
||||
/// 音频状态
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
pub struct AudioState {
|
||||
pub is_recording: bool,
|
||||
pub is_playing: bool,
|
||||
pub volume: u8,
|
||||
}
|
||||
|
||||
/// 设置音量请求
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct SetVolumeRequest {
|
||||
pub volume: u8, // 0-100
|
||||
}
|
||||
|
||||
/// 设置音量响应
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct SetVolumeResponse {
|
||||
pub previous: u8,
|
||||
pub current: u8,
|
||||
}
|
||||
|
||||
/// 文件操作请求
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub enum FileRequest {
|
||||
/// 读取文件
|
||||
Read { path: String },
|
||||
/// 写入文件
|
||||
Write { path: String, data: Vec<u8> },
|
||||
/// 删除文件
|
||||
Delete { path: String },
|
||||
/// 列出目录
|
||||
List { path: String },
|
||||
/// 获取文件信息
|
||||
Stat { path: String },
|
||||
}
|
||||
|
||||
/// 文件操作响应
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub enum FileResponse {
|
||||
/// 读取结果
|
||||
Data(Vec<u8>),
|
||||
/// 写入成功
|
||||
Written { bytes: usize },
|
||||
/// 删除成功
|
||||
Deleted,
|
||||
/// 目录列表
|
||||
Entries(Vec<FileEntry>),
|
||||
/// 文件信息
|
||||
Stat(FileStat),
|
||||
}
|
||||
|
||||
/// 文件条目
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct FileEntry {
|
||||
pub name: String,
|
||||
pub is_dir: bool,
|
||||
pub size: u64,
|
||||
}
|
||||
|
||||
/// 文件状态
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct FileStat {
|
||||
pub size: u64,
|
||||
pub is_dir: bool,
|
||||
pub modified: u64,
|
||||
}
|
||||
|
||||
/// 系统控制请求
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub enum SystemRequest {
|
||||
/// 重启
|
||||
Reboot,
|
||||
/// 关机
|
||||
Shutdown,
|
||||
/// 获取系统负载
|
||||
GetLoad,
|
||||
/// 获取内存使用
|
||||
GetMemory,
|
||||
}
|
||||
|
||||
/// 系统控制响应
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub enum SystemResponse {
|
||||
/// 操作已接受
|
||||
Accepted,
|
||||
/// 系统负载
|
||||
Load { one: f32, five: f32, fifteen: f32 },
|
||||
/// 内存使用
|
||||
Memory { total: u64, used: u64, free: u64 },
|
||||
}
|
||||
|
||||
/// 自定义命令请求(用于扩展)
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct CustomRequest {
|
||||
pub name: String,
|
||||
pub payload: Vec<u8>,
|
||||
}
|
||||
|
||||
/// 自定义命令响应
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct CustomResponse {
|
||||
pub payload: Vec<u8>,
|
||||
}
|
||||
|
||||
// ==================== 统一命令枚举 ====================
|
||||
|
||||
/// RPC 命令 - 统一的请求类型
|
||||
@@ -176,39 +39,15 @@ pub struct CustomResponse {
|
||||
pub enum Command {
|
||||
/// 执行 Shell 命令
|
||||
Shell(ShellRequest),
|
||||
/// 获取设备信息
|
||||
GetInfo,
|
||||
/// 设置音量
|
||||
SetVolume(SetVolumeRequest),
|
||||
/// 文件操作
|
||||
File(FileRequest),
|
||||
/// 系统控制
|
||||
System(SystemRequest),
|
||||
/// 自定义命令
|
||||
Custom(CustomRequest),
|
||||
/// Ping(用于测量延迟)
|
||||
Ping { timestamp: u64 },
|
||||
}
|
||||
|
||||
/// RPC 结果 - 统一的响应类型
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub enum CommandResult {
|
||||
/// Shell 命令结果
|
||||
Shell(ShellResponse),
|
||||
/// 设备信息
|
||||
Info(DeviceInfo),
|
||||
/// 音量设置结果
|
||||
Volume(SetVolumeResponse),
|
||||
/// 文件操作结果
|
||||
File(FileResponse),
|
||||
/// 系统控制结果
|
||||
System(SystemResponse),
|
||||
/// 自定义命令结果
|
||||
Custom(CustomResponse),
|
||||
/// Pong 响应
|
||||
Pong { timestamp: u64, server_time: u64 },
|
||||
/// 错误
|
||||
Error(CommandError),
|
||||
/// Shell 命令结果
|
||||
Shell(ShellResponse),
|
||||
}
|
||||
|
||||
/// 命令错误
|
||||
@@ -261,48 +100,7 @@ impl std::error::Error for CommandError {}
|
||||
|
||||
// ==================== 便捷构造方法 ====================
|
||||
|
||||
impl Command {
|
||||
/// 创建 Shell 命令
|
||||
pub fn shell(cmd: impl Into<String>) -> Self {
|
||||
Self::Shell(ShellRequest {
|
||||
command: cmd.into(),
|
||||
cwd: None,
|
||||
env: None,
|
||||
timeout_secs: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// 创建带超时的 Shell 命令
|
||||
pub fn shell_with_timeout(cmd: impl Into<String>, timeout: u32) -> Self {
|
||||
Self::Shell(ShellRequest {
|
||||
command: cmd.into(),
|
||||
cwd: None,
|
||||
env: None,
|
||||
timeout_secs: Some(timeout),
|
||||
})
|
||||
}
|
||||
|
||||
/// 创建 Ping 命令
|
||||
pub fn ping() -> Self {
|
||||
Self::Ping {
|
||||
timestamp: std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis() as u64,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CommandResult {
|
||||
/// 创建成功的 Shell 响应
|
||||
pub fn shell_ok(stdout: String) -> Self {
|
||||
Self::Shell(ShellResponse {
|
||||
stdout,
|
||||
stderr: String::new(),
|
||||
exit_code: 0,
|
||||
})
|
||||
}
|
||||
|
||||
/// 创建错误响应
|
||||
pub fn error(err: CommandError) -> Self {
|
||||
Self::Error(err)
|
||||
|
||||
@@ -65,6 +65,11 @@ pub enum ControlPacket {
|
||||
// ========== RPC ==========
|
||||
RpcRequest {
|
||||
id: u32,
|
||||
/// 是否异步运行
|
||||
run_async: bool,
|
||||
/// 超时(毫秒)
|
||||
timeout: Option<u64>,
|
||||
/// 请求命令
|
||||
command: Command,
|
||||
},
|
||||
RpcResponse {
|
||||
|
||||
@@ -4,138 +4,226 @@
|
||||
//! - 请求 ID 生成
|
||||
//! - 请求/响应匹配
|
||||
//! - 超时处理
|
||||
//! - 异步任务管理
|
||||
|
||||
use crate::net::command::CommandResult;
|
||||
use parking_lot::Mutex;
|
||||
use std::collections::HashMap;
|
||||
use crate::net::command::{Command, CommandError, CommandResult};
|
||||
use crate::net::protocol::ControlPacket;
|
||||
use crate::utils::shell::Shell;
|
||||
use anyhow::Result;
|
||||
use dashmap::DashMap;
|
||||
use std::future::Future;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
use std::time::Duration;
|
||||
use thiserror::Error;
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
/// RPC 调用错误
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum RpcError {
|
||||
/// 超时
|
||||
#[error("RPC timeout")]
|
||||
Timeout,
|
||||
/// 通道关闭
|
||||
#[error("RPC cancelled")]
|
||||
Cancelled,
|
||||
/// 未连接
|
||||
NotConnected,
|
||||
#[error("RPC internal error: {0}")]
|
||||
Internal(String),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for RpcError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
RpcError::Timeout => write!(f, "RPC timeout"),
|
||||
RpcError::Cancelled => write!(f, "RPC cancelled"),
|
||||
RpcError::NotConnected => write!(f, "Not connected"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for RpcError {}
|
||||
|
||||
/// 等待中的 RPC 请求
|
||||
struct PendingRequest {
|
||||
tx: oneshot::Sender<CommandResult>,
|
||||
created_at: std::time::Instant,
|
||||
}
|
||||
|
||||
/// RPC 管理器
|
||||
pub struct RpcManager {
|
||||
handler: RpcHandler,
|
||||
next_id: AtomicU32,
|
||||
pending: Mutex<HashMap<u32, PendingRequest>>,
|
||||
default_timeout: Duration,
|
||||
pending: Arc<DashMap<u32, oneshot::Sender<CommandResult>>>,
|
||||
}
|
||||
|
||||
impl RpcManager {
|
||||
/// 创建新的 RPC 管理器
|
||||
pub fn new() -> Self {
|
||||
Self::with_timeout(Duration::from_secs(30))
|
||||
}
|
||||
|
||||
/// 创建带自定义超时的 RPC 管理器
|
||||
pub fn with_timeout(timeout: Duration) -> Self {
|
||||
Self {
|
||||
handler: RpcHandler::new(),
|
||||
next_id: AtomicU32::new(1),
|
||||
pending: Mutex::new(HashMap::new()),
|
||||
default_timeout: timeout,
|
||||
pending: Arc::new(DashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// 注册一个新的 RPC 请求
|
||||
///
|
||||
/// 返回 (请求ID, 响应接收器)
|
||||
pub fn register(&self) -> (u32, oneshot::Receiver<CommandResult>) {
|
||||
let id = self.next_id.fetch_add(1, Ordering::SeqCst);
|
||||
let (tx, rx) = oneshot::channel();
|
||||
self.pending.lock().insert(
|
||||
id,
|
||||
PendingRequest {
|
||||
tx,
|
||||
created_at: std::time::Instant::now(),
|
||||
},
|
||||
);
|
||||
(id, rx)
|
||||
}
|
||||
|
||||
/// 解析 RPC 响应
|
||||
pub fn resolve(&self, id: u32, result: CommandResult) {
|
||||
if let Some(req) = self.pending.lock().remove(&id) {
|
||||
let _ = req.tx.send(result);
|
||||
if let Some((_, tx)) = self.pending.remove(&id) {
|
||||
let _ = tx.send(result);
|
||||
}
|
||||
}
|
||||
|
||||
/// 取消指定的 RPC 请求
|
||||
pub fn cancel(&self, id: u32) {
|
||||
self.pending.lock().remove(&id);
|
||||
self.pending.remove(&id);
|
||||
}
|
||||
|
||||
/// 获取待处理请求数量
|
||||
pub fn pending_count(&self) -> usize {
|
||||
self.pending.lock().len()
|
||||
/// 发起异步 RPC 调用
|
||||
///
|
||||
/// F: 异步闭包,输入 ID,返回 Future
|
||||
pub async fn call<F, Fut>(
|
||||
&self,
|
||||
builder: &RpcBuilder,
|
||||
send_fn: F,
|
||||
) -> Result<CommandResult, RpcError>
|
||||
where
|
||||
F: FnOnce(ControlPacket) -> Fut,
|
||||
Fut: Future<Output = Result<()>>,
|
||||
{
|
||||
let command = builder.command.clone();
|
||||
let timeout = builder.timeout;
|
||||
let run_async = builder.run_async;
|
||||
let id = self.next_id.fetch_add(1, Ordering::Relaxed);
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
// 1. 注册请求
|
||||
self.pending.insert(id, tx);
|
||||
|
||||
// 2. RAII 守卫:确保无论因超时、报错还是外部 Drop,ID 都会被清理
|
||||
let _guard = DropGuard {
|
||||
id,
|
||||
pending: Arc::clone(&self.pending),
|
||||
};
|
||||
|
||||
// 3. 执行异步发送逻辑
|
||||
if let Err(e) = send_fn(ControlPacket::RpcRequest {
|
||||
id,
|
||||
command,
|
||||
timeout,
|
||||
run_async,
|
||||
})
|
||||
.await
|
||||
{
|
||||
return Err(RpcError::Internal(e.to_string()));
|
||||
}
|
||||
|
||||
// 4. 等待响应或超时
|
||||
match timeout {
|
||||
Some(ms) => match tokio::time::timeout(Duration::from_millis(ms), rx).await {
|
||||
Ok(Ok(res)) => Ok(res),
|
||||
Ok(Err(_)) => Err(RpcError::Cancelled), // tx 关闭,任务被取消
|
||||
Err(_) => Err(RpcError::Timeout),
|
||||
},
|
||||
None => rx.await.map_err(|_| RpcError::Cancelled), // tx 关闭,任务被取消
|
||||
}
|
||||
}
|
||||
|
||||
/// 清理超时的请求
|
||||
pub fn cleanup_expired(&self) {
|
||||
let mut pending = self.pending.lock();
|
||||
let now = std::time::Instant::now();
|
||||
/// 处理 RPC 调用
|
||||
///
|
||||
/// F: 异步闭包,输入 ID,返回 Future
|
||||
pub async fn handle_rpc_request<F, Fut>(
|
||||
&self,
|
||||
id: u32,
|
||||
run_async: bool,
|
||||
timeout_ms: Option<u64>,
|
||||
command: Command,
|
||||
send_fn: F,
|
||||
) -> Result<()>
|
||||
where
|
||||
F: Fn(ControlPacket) -> Fut + Send + Sync + 'static, // 异步执行需要 'static
|
||||
Fut: Future<Output = Result<()>> + Send,
|
||||
{
|
||||
let sender = Arc::new(send_fn);
|
||||
let handler = self.handler.clone();
|
||||
|
||||
pending.retain(|_, req| {
|
||||
if now.duration_since(req.created_at) > self.default_timeout {
|
||||
false
|
||||
} else {
|
||||
true
|
||||
let task = async move {
|
||||
let execute_future = handler.handle(command, timeout_ms);
|
||||
|
||||
match timeout_ms {
|
||||
None => {
|
||||
// 无超时逻辑
|
||||
let result = execute_future.await;
|
||||
sender(ControlPacket::RpcResponse { id, result }).await
|
||||
}
|
||||
Some(ms) => {
|
||||
let d = Duration::from_millis(ms);
|
||||
match tokio::time::timeout(d, execute_future).await {
|
||||
Ok(result) => {
|
||||
// 1. 正常完成
|
||||
sender(ControlPacket::RpcResponse { id, result }).await
|
||||
}
|
||||
Err(_) => {
|
||||
// 2. 触发超时:发送超时错误消息
|
||||
let err_result = CommandResult::error(CommandError::timeout(format!(
|
||||
"Task {} timed out after {}ms",
|
||||
id, ms
|
||||
)));
|
||||
sender(ControlPacket::RpcResponse {
|
||||
id,
|
||||
result: err_result,
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/// 获取默认超时时间
|
||||
pub fn default_timeout(&self) -> Duration {
|
||||
self.default_timeout
|
||||
if run_async {
|
||||
tokio::spawn(task);
|
||||
Ok(())
|
||||
} else {
|
||||
task.await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RpcManager {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RpcBuilder {
|
||||
pub command: Command,
|
||||
pub run_async: bool,
|
||||
pub timeout: Option<u64>,
|
||||
}
|
||||
|
||||
impl RpcBuilder {
|
||||
/// 基础构造器
|
||||
pub fn new(command: Command) -> Self {
|
||||
Self {
|
||||
command,
|
||||
run_async: false,
|
||||
timeout: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// 便捷方法:默认异步配置
|
||||
pub fn default(command: Command) -> Self {
|
||||
Self::new(command).set_async(true).set_timeout(10_000)
|
||||
}
|
||||
|
||||
/// 设置为异步运行
|
||||
pub fn set_async(mut self, is_async: bool) -> Self {
|
||||
self.run_async = is_async;
|
||||
self
|
||||
}
|
||||
|
||||
/// 设置超时时长(毫秒)
|
||||
pub fn set_timeout(mut self, ms: u64) -> Self {
|
||||
self.timeout = Some(ms);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// RPC 调用辅助函数
|
||||
pub async fn call_with_timeout<F, Fut>(
|
||||
timeout: Duration,
|
||||
register_fn: F,
|
||||
) -> Result<CommandResult, RpcError>
|
||||
where
|
||||
F: FnOnce() -> Fut,
|
||||
Fut: std::future::Future<Output = Result<oneshot::Receiver<CommandResult>, RpcError>>,
|
||||
{
|
||||
let rx = register_fn().await?;
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RpcHandler;
|
||||
|
||||
match tokio::time::timeout(timeout, rx).await {
|
||||
Ok(Ok(result)) => Ok(result),
|
||||
Ok(Err(_)) => Err(RpcError::Cancelled),
|
||||
Err(_) => Err(RpcError::Timeout),
|
||||
impl RpcHandler {
|
||||
fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
|
||||
async fn handle(&self, command: Command, timeout_ms: Option<u64>) -> CommandResult {
|
||||
match command {
|
||||
Command::Shell(req) => match Shell::run(req, timeout_ms).await {
|
||||
Ok(resp) => CommandResult::Shell(resp),
|
||||
Err(e) => CommandResult::Error(CommandError::internal(e.to_string())),
|
||||
},
|
||||
_ => CommandResult::Error(CommandError::not_implemented()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DropGuard {
|
||||
id: u32,
|
||||
pending: Arc<DashMap<u32, oneshot::Sender<CommandResult>>>,
|
||||
}
|
||||
|
||||
impl Drop for DropGuard {
|
||||
fn drop(&mut self) {
|
||||
self.pending.remove(&self.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
pub mod shell;
|
||||
@@ -0,0 +1,68 @@
|
||||
use anyhow::{Context, Result, anyhow};
|
||||
use core::str;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::process::Stdio;
|
||||
use std::time::Duration;
|
||||
use tokio::io::AsyncReadExt;
|
||||
use tokio::process::Command;
|
||||
|
||||
pub type ShellRequest = String;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
pub struct ShellResponse {
|
||||
pub stdout: String,
|
||||
pub stderr: String,
|
||||
pub exit_code: i32,
|
||||
}
|
||||
|
||||
pub struct Shell;
|
||||
|
||||
impl Shell {
|
||||
pub async fn run(command: String, timeout_ms: Option<u64>) -> Result<ShellResponse> {
|
||||
// 1. 配置 Command
|
||||
let mut child = Command::new("sh")
|
||||
.arg("-c")
|
||||
.arg(&command)
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
// 当 Child 结构体被 Drop 时,自动杀死子进程(SIGKILL)
|
||||
.kill_on_drop(true)
|
||||
.spawn()
|
||||
.context("Failed to spawn shell command")?;
|
||||
|
||||
let mut stdout_reader = child.stdout.take().context("Failed to open stdout")?;
|
||||
let mut stderr_reader = child.stderr.take().context("Failed to open stderr")?;
|
||||
|
||||
// 2. 定义执行逻辑
|
||||
let run_logic = async move {
|
||||
let mut stdout_buf = Vec::new();
|
||||
let mut stderr_buf = Vec::new();
|
||||
|
||||
// 使用 join! 并发读取流并等待进程结束
|
||||
let (res_out, res_err, res_status) = tokio::join!(
|
||||
stdout_reader.read_to_end(&mut stdout_buf),
|
||||
stderr_reader.read_to_end(&mut stderr_buf),
|
||||
child.wait()
|
||||
);
|
||||
|
||||
let status = res_status.context("Wait for child failed")?;
|
||||
res_out.context("Read stdout failed")?;
|
||||
res_err.context("Read stderr failed")?;
|
||||
|
||||
Ok(ShellResponse {
|
||||
stdout: String::from_utf8_lossy(&stdout_buf).to_string(),
|
||||
stderr: String::from_utf8_lossy(&stderr_buf).to_string(),
|
||||
exit_code: status.code().unwrap_or(-1),
|
||||
})
|
||||
};
|
||||
|
||||
// 3. 应用超时
|
||||
if let Some(ms) = timeout_ms {
|
||||
tokio::time::timeout(Duration::from_millis(ms), run_logic)
|
||||
.await
|
||||
.map_err(|_| anyhow!("Shell execution timed out"))?
|
||||
} else {
|
||||
run_logic.await
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user