# AeroSync 协议设计与功能规划
本文档覆盖协议实现细节,以及当前实现状态与后续开发规划。
---
## 目录
1. [功能实现现状](#1-功能实现现状)
2. [开发路线图](#2-开发路线图)
3. [P0 已实现设计参考](#3-p0-已实现设计参考)
4. [P1 已实现设计参考](#4-p1-已实现设计参考)
5. [P2 已实现设计参考](#5-p2-已实现设计参考)
6. [协议交互时序](#6-协议交互时序)
7. [多机连接实战指南](#7-多机连接实战指南)
---
## 1. 功能实现现状
| 1 | SHA-256 文件完整性校验 | P0 | ✅ 已实现 | 发送方预计算、接收方校验,HTTP 单文件 + 分片均覆盖 |
| 2 | 审计日志 | P0 | ✅ 已实现 | JSONL 格式追加写入磁盘;含 IP、时间、文件名、SHA-256、协议;HTTP + QUIC 均集成 |
| 3 | TLS 证书管理 | P0 | ✅ 已实现 | 支持从 PEM 文件加载外部证书;无证书时自动生成自签名兜底;`--tls-cert`/`--tls-key` CLI 参数 |
| 4 | Auth 与传输流程集成 | P0 | ✅ 已实现 | `/upload`、`/upload/chunk`、`/upload/complete`、QUIC 均覆盖 |
| 5 | QUIC 服务端实现 | P0 | ✅ 已实现 | `start_quic_server` 完整,Quinn + rustls,含认证 |
| 6 | CLI send 命令 | P0 | ✅ 已实现 | 协议协商、SHA-256 预计算、MultiProgress、限速、预检验均完整 |
| 7 | CLI receive 命令 | P0 | ✅ 已实现 | 支持 one-shot 和持续模式;支持外部 TLS 证书 |
| 8 | 协议自动协商 | P1 | ✅ 已实现 | `/health` 探测 + `X-AeroSync` header 触发 QUIC 升级 |
| 9 | 分片上传 | P1 | ✅ 已实现 | 32MB 分片、断点续传、服务端合并校验 |
| 10 | 批量传输流水线 | P1 | ✅ 已实现 | Semaphore + FuturesUnordered,自适应并发 |
| 11 | 带宽限速 | P1 | ✅ 已实现 | Token Bucket RateLimiter;集成到分片上传;`--limit 512KB/10MB/1MB/s` |
| 12 | 传输历史持久化 | P1 | ✅ 已实现 | JSONL 追加写入 `~/.config/aerosync/history.jsonl`;`aerosync history` 支持过滤查询 |
| 13 | 预检验 | P1 | ✅ 已实现 | `/health` 返回 `free_bytes/total_bytes/version`;发送前自动检查磁盘空间;`--no-preflight` 跳过 |
| 14 | /health 端点 | P2 | ✅ 已实现 | 返回 `free_bytes/total_bytes/version`;含 `X-AeroSync: true` 响应头 |
| 15 | Token 持久化 | P1 | ✅ 已实现 | TOML 格式存储至 `~/.config/aerosync/tokens.toml`;`token generate --save/list/revoke` |
| 16 | Prometheus /metrics 端点 | P1 | ✅ 已实现 | `AtomicU64` 计数器 + 磁盘 gauge;Prometheus exposition format;`enable_metrics` 可关闭 |
| 17 | WebSocket 实时进度推送 | P1 | ✅ 已实现 | `broadcast::Sender<WsEvent>`;GET /ws;多客户端扇出;上传完成/失败自动广播 |
| 18 | 多接收目录路由 | P2 | ✅ 已实现 | 按 sender_ip / X-AeroSync-Tag / 扩展名路由;第一条匹配规则胜出;TOML 配置 |
| 19 | 配置热重载(SIGHUP) | P2 | ✅ 已实现 | `watch_config_reload()`;可热重载 auth/routing/limits;端口变更 warn + 忽略 |
| 20 | S3 Multipart Upload | P2 | ✅ 已实现 | 三步 API:initiate/upload_part/complete;超阈值自动切换;abort on failure |
| 21 | `aerosync watch` WebSocket 订阅命令 | P2 | ✅ 已实现 | CLI 原生订阅 `/ws`;pretty/json 双格式;`--filter` 事件过滤;agent 友好 |
| 22 | mDNS 局域网服务发现 | P2 | ✅ 已实现 | receiver 启动时自动广播 `_aerosync._tcp.local.`;`aerosync discover` 扫描局域网 |
**已实现 22 项 / 部分实现 0 项 / 未实现 0 项 — Phase 4 + Phase 5 + 补丁功能全部完成 🎉**
---
## 2. 开发路线图
### Phase 4 — 可靠性与可观测性 ✅ 已完成
| 审计日志写入磁盘 | P0 | ✅ | `aerosync-core/src/audit.rs` |
| 外部 TLS 证书加载 | P0 | ✅ | `aerosync-core/src/server.rs` `TlsConfig` |
| Token 持久化到磁盘 | P1 | ✅ | `aerosync-core/src/auth/store.rs` |
| 传输历史持久化 | P1 | ✅ | `aerosync-core/src/history.rs` |
| 预检验(磁盘空间) | P1 | ✅ | `aerosync-core/src/preflight.rs` |
| 带宽限速(Token Bucket) | P1 | ✅ | `aerosync-protocols/src/ratelimit.rs` |
### Phase 5 — 生产增强 ✅ 已完成
详细设计见 [docs/phase5-plan.md](phase5-plan.md)。
| 5.1 | Prometheus 指标端点 `/metrics` | P1 | ✅ | `aerosync-core/src/metrics.rs`(新增) |
| 5.2 | WebSocket 实时传输进度推送 | P1 | ✅ | `aerosync-core/src/server.rs` |
| 5.3 | 多接收目录路由(按 sender IP/tag) | P2 | ✅ | `aerosync-core/src/routing.rs`(新增) |
| 5.4 | S3 Multipart Upload(大文件分片) | P2 | ✅ | `aerosync-protocols/src/s3.rs` |
| 5.5 | 配置热重载(SIGHUP) | P2 | ✅ | `aerosync-core/src/server.rs` |
---
## 3. P0 已实现设计参考
### 3.1 审计日志持久化 ✅
**实现**:`aerosync-core/src/audit.rs`,JSONL 格式追加写入,`Arc<Mutex<File>>` 线程安全,支持并发写入。
**核心接口**:
```rust
pub struct AuditLogger { file: Arc<Mutex<tokio::fs::File>>, path: PathBuf }
impl AuditLogger {
pub async fn new(path: &Path) -> Result<Self>;
pub async fn log(&self, entry: AuditEntry) -> Result<()>;
pub async fn log_completed(&self, direction, protocol, filename, size, sha256, remote_ip);
pub async fn log_failed(&self, direction, protocol, filename, size, remote_ip, error);
pub async fn log_auth_failed(&self, protocol, remote_ip, reason);
pub async fn read_all(&self) -> Result<Vec<AuditRecord>>;
pub async fn read_recent(&self, limit: usize) -> Result<Vec<AuditRecord>>;
}
```
**日志文件位置**:`ServerConfig.audit_log: Option<PathBuf>`(由调用方指定)
**日志格式(JSONL)**:
```jsonl
{"timestamp":1775903419,"event":{"type":"transfer_completed"},"filename":"data.bin","size":157286400,"sha256":"204be31...","remote_ip":"192.168.1.5","direction":"Receive","result":"Ok","protocol":"http"}
{"timestamp":1775903500,"event":{"type":"auth_failed"},"filename":"","size":0,"remote_ip":"10.0.0.3","direction":"Receive","result":{"Err":"Invalid token"},"protocol":"http"}
```
**集成点**:
- `server.rs handle_file_upload` / `handle_chunk_complete` — 记录接收事件
- `transfer.rs transfer_worker` — 记录发送事件
- 所有 auth 失败路径 — 记录 `AuthFailed`
---
### 3.2 外部 TLS 证书加载 ✅
**实现**:`ServerConfig.tls: Option<TlsConfig>`,`rustls-pemfile` 加载 PEM 文件;`--tls-cert`/`--tls-key` CLI 参数;无证书时自动生成自签名兜底。
**核心接口**:
```rust
// aerosync-core/src/server.rs
pub struct TlsConfig {
pub cert_path: PathBuf, // PEM 证书文件路径
pub key_path: PathBuf, // PEM 私钥文件路径
}
// configure_quic_server(tls: Option<&TlsConfig>) — 有证书时加载,无则自签名
// load_tls_from_pem(cert_path, key_path) — 支持 PKCS8 和 RSA 两种私钥格式
```
**CLI 用法**:
```bash
aerosync receive --tls-cert /path/to/cert.pem --tls-key /path/to/key.pem
```
---
## 4. P1 已实现设计参考
### 4.1 Token 持久化 ✅
**实现**:`aerosync-core/src/auth/store.rs`,TOML 格式存储到 `~/.config/aerosync/tokens.toml`;`token generate --save/list/revoke` 子命令。
**核心接口**:
```rust
pub struct TokenStore { path: PathBuf }
impl TokenStore {
pub fn new(path: &Path) -> Self;
pub fn default_path() -> PathBuf; // ~/.config/aerosync/tokens.toml
pub fn save(&self, token: &str, label: Option<&str>, expires_at: u64) -> Result<()>;
pub fn list_all(&self) -> Result<Vec<StoredToken>>;
pub fn list_valid(&self) -> Result<Vec<StoredToken>>;
pub fn find_by_prefix(&self, prefix: &str) -> Result<Option<StoredToken>>;
pub fn revoke(&self, token: &str) -> Result<bool>;
pub fn prune(&self) -> Result<usize>; // 清理过期/已撤销
}
```
**CLI 用法**:
```bash
aerosync token generate --save # 生成并保存到 tokens.toml
aerosync token generate --save --label "prod-server-1"
aerosync token list # 列出所有已保存 token
aerosync token revoke <token-prefix> # 按前缀撤销
```
---
### 4.2 带宽限速 ✅
**实现**:`aerosync-protocols/src/ratelimit.rs`,Token Bucket 算法;集成到 `HttpTransfer.upload_chunked` 分片循环;`--limit 512KB/10MB/1MB/s` CLI 参数;`parse_limit()` 支持多种单位格式。
**核心接口**:
```rust
pub struct RateLimiter { inner: Arc<Mutex<RateLimiterInner>> }
impl RateLimiter {
pub fn new(rate_bytes_per_sec: u64) -> Self; // 0 = 不限速
pub fn unlimited() -> Self;
pub async fn consume(&self, bytes: u64); // 令牌不足时异步等待
}
/// 解析限速字符串:"512KB" / "10MB" / "1MB/s" / "100"(默认 KB/s)
pub fn parse_limit(s: &str) -> Option<u64>;
```
**集成**:`HttpConfig.upload_limit_bps: u64`(0 = 不限速),每个分片上传前调用 `rate_limiter.consume(chunk_size).await`
**CLI 用法**:
```bash
aerosync send ./large-file.bin 192.168.1.10:7788 --limit 10MB
aerosync send ./dir/ host:7788 --limit 512KB -r
```
---
### 4.3 传输历史持久化 ✅
**实现**:`aerosync-core/src/history.rs`,JSONL 格式追加到 `~/.config/aerosync/history.jsonl`;`HistoryQuery` 支持按方向/协议/成功状态过滤;`aerosync history --sent/--received/--success-only/--limit` 子命令。
**核心接口**:
```rust
pub struct HistoryStore { path: PathBuf, file: Arc<Mutex<tokio::fs::File>> }
impl HistoryStore {
pub async fn new(path: &Path) -> Result<Self>;
pub fn default_path() -> PathBuf; // ~/.config/aerosync/history.jsonl
pub async fn append(&self, entry: HistoryEntry) -> Result<()>;
pub async fn append_silent(&self, entry: HistoryEntry); // fire-and-forget
pub async fn read_all(&self) -> Result<Vec<HistoryEntry>>;
pub async fn query(&self, q: &HistoryQuery) -> Result<Vec<HistoryEntry>>;
pub async fn recent(&self, limit: usize) -> Result<Vec<HistoryEntry>>;
}
pub struct HistoryQuery {
pub direction: Option<String>, // "send" / "receive"
pub protocol: Option<String>, // "http" / "quic" / ...
pub success_only: bool,
pub limit: usize, // 0 = 不限
}
```
**CLI 用法**:
```bash
aerosync history # 最近 20 条(默认)
aerosync history --limit 50
aerosync history --sent # 只看发送记录
aerosync history --success-only # 只看成功记录
```
---
### 4.4 预检验(Preflight)✅
**实现**:`aerosync-core/src/preflight.rs`,`preflight_check()` 探测 `/health` 并验证磁盘空间;`free_bytes == 0` 时跳过检查(兼容旧版);`--no-preflight` 标志完全跳过;`/health` 扩展返回 `free_bytes/total_bytes/version`(`libc::statvfs` 跨平台实现)。
**`/health` 响应格式**:
```json
{
"status": "ok",
"received_files": 12,
"free_bytes": 107374182400,
"total_bytes": 536870912000,
"version": "0.2.0"
}
```
**核心接口**:
```rust
/// 探测接收端健康状态(返回磁盘信息和版本号)
pub async fn probe_receiver(http_base: &str) -> Result<PreflightResult>;
/// 验证磁盘空间是否足够;free_bytes==0 时视为跳过
pub async fn preflight_check(
http_base: &str,
total_bytes: u64,
) -> Result<PreflightResult, PreflightError>;
```
**CLI 用法**:`aerosync send` 默认自动执行预检验,`--no-preflight` 跳过。
---
## 5. P2 已实现设计参考
### 5.1 Prometheus /metrics 端点 ✅
**实现**:`aerosync-core/src/metrics.rs`,`Arc<Metrics>` 全局单例,`AtomicU64` 零锁计数器,`render()` 手动生成 exposition format(无外部 crate 依赖)。
**核心接口**:
```rust
pub struct Metrics {
pub files_received_total: AtomicU64,
pub bytes_received_total: AtomicU64,
pub upload_errors_total: AtomicU64,
pub ws_connections_total: AtomicU64,
// active_ws_connections: AtomicU64 (gauge)
}
impl Metrics {
pub fn new() -> Arc<Self>;
pub fn inc_files_received(&self);
pub fn add_bytes_received(&self, n: u64);
pub fn inc_upload_errors(&self);
pub fn inc_ws_connections(&self);
pub fn dec_ws_connections(&self);
pub fn render(&self, free_bytes: u64, total_bytes: u64) -> String;
}
```
**端点**:`GET /metrics`,`Content-Type: text/plain; version=0.0.4`,通过 `ServerConfig.enable_metrics: bool` 控制开关。
---
### 5.2 WebSocket 实时进度推送 ✅
**实现**:`WsEvent` JSON 枚举通过 `tokio::sync::broadcast` 广播给所有连接的客户端;客户端断连 fire-and-forget;`Lagged` 错误打印 warn 后继续。
**核心接口**:
```rust
#[derive(Serialize)]
#[serde(tag = "event", rename_all = "snake_case")]
pub enum WsEvent {
TransferStarted { filename: String, size: u64, sender_ip: String },
Progress { filename: String, bytes: u64, total: u64 },
Completed { filename: String, size: u64, sha256: String },
Failed { filename: String, reason: String },
}
pub type WsBroadcast = broadcast::Sender<WsEvent>;
```
**端点**:`GET /ws`(WebSocket 升级),通过 `ServerConfig.enable_ws` 控制开关,`ws_event_buffer` 配置广播缓冲区。
---
### 5.3 多接收目录路由 ✅
**实现**:`aerosync-core/src/routing.rs`,`Router::resolve()` 按优先级迭代规则列表,第一条匹配胜出,无匹配回退 `receive_directory`。
**核心接口**:
```rust
pub struct RoutingRule {
pub name: String,
pub destination: PathBuf,
pub tag: Option<String>, // X-AeroSync-Tag header
pub sender_ip: Option<String>, // exact IP match
pub extension: Option<String>, // case-insensitive, without dot
}
pub struct Router { config: RouterConfig, default_dir: PathBuf }
impl Router {
pub fn resolve(&self, sender_ip: &str, tag: Option<&str>, filename: &str) -> PathBuf;
}
```
**配置**:`ServerConfig.routing: Option<RouterConfig>`(TOML 可序列化)。
---
### 5.4 S3 Multipart Upload ✅
**实现**:`aerosync-protocols/src/s3.rs`,三步 API;`upload_auto()` 按 `multipart_threshold` 自动选择路径;part 失败时自动调用 `abort_multipart()` 清理。
**核心接口**:
```rust
impl S3Transfer {
pub async fn initiate_multipart(&self, bucket, key) -> Result<String>; // → UploadId
pub async fn upload_part(&self, bucket, key, upload_id, part_number, data) -> Result<String>; // → ETag
pub async fn complete_multipart(&self, bucket, key, upload_id, parts) -> Result<()>;
pub async fn abort_multipart(&self, bucket, key, upload_id) -> Result<()>;
pub async fn upload_auto(&self, file_path, url, progress_tx) -> Result<()>; // 自动选路
}
```
**配置**:`S3Config.multipart_threshold`(默认 100MB)、`S3Config.part_size`(默认 16MB)。
---
### 5.5 配置热重载(SIGHUP)✅
**实现**:`FileReceiver::watch_config_reload(config_path)` 启动后台 task,监听 `SIGHUP`(Unix only),重新读取 TOML 配置文件并只更新可热重载字段。
**可热重载字段**:`max_file_size`、`allow_overwrite`、`auth`、`routing`、`audit_log`
**不可热重载字段**:`http_port`、`quic_port`、`bind_address`(变更时打印 warn 并忽略,需重启生效)
**用法**:
```bash
# 修改配置文件后发送 SIGHUP,无需重启服务
kill -HUP $(pidof aerosync)
```
---
### 5.6 `aerosync watch` WebSocket 订阅命令 ✅
**实现**:`src/main.rs`,`tokio-tungstenite` 客户端,`build_ws_url()` 归一化输入地址,连接 `ws://host:port/ws` 后持续接收 `WsEvent` JSON 消息。`watch_once()` 封装单次连接逻辑,`cmd_watch()` 外层循环实现指数退避自动重连。
**CLI 参数**:
| `<host>` | `localhost:7788` | 接收端地址(`host:port` 或完整 `ws://` URL) |
| `--format` | `pretty` | `pretty`(人类可读)或 `json`(机器可读原始 JSON) |
| `--filter <event>` | — | 只输出含指定事件类型的消息 |
| `--reconnect` | false | 断连后自动重连(默认关闭) |
| `--max-retries <n>` | `0` | 最大重连次数,`0` 表示无限重连(需配合 `--reconnect`) |
| `--retry-delay <s>` | `2` | 初始等待秒数,每次失败后翻倍,上限 60 秒 |
**重连退避策略**:
| 1 | 2s |
| 2 | 4s |
| 3 | 8s |
| 4 | 16s |
| 5 | 32s |
| 6+ | 60s(上限) |
服务端主动发送 Close frame 时**不触发重连**(视为正常关闭)。
**输出规则(pretty 模式)**:
| `completed` | **stdout** | agent 只需捕获 stdout 即可感知文件到达 |
| `transfer_started` / `failed` | stderr | 状态信息,不干扰管道 |
| `progress` | 静默 | 默认不输出,避免刷屏 |
**使用示例**:
```bash
# 1. 基本用法 — 连接本地接收端,pretty 格式输出
aerosync watch
# 等同于:
aerosync watch localhost:7788 --format pretty
# 2. 连接远端接收端
aerosync watch 10.0.0.5:7788
# 3. 使用完整 ws:// URL(自定义路径)
aerosync watch ws://10.0.0.5:7788/ws
# 4. 只显示 completed 事件(过滤掉 started/progress/failed)
aerosync watch --filter completed
# 5. agent 模式 — 机器可读 JSON,stderr 丢弃(不刷屏)
# stdout 每行输出一个完整 JSON 对象
aerosync watch --format json 2>/dev/null
# 输出示例:
# {"event":"completed","filename":"report.pdf","size":2097152,"sha256":"a1b2c3d4..."}
# 6. agent 脚本:管道解析文件到达事件
echo "[AGENT] File arrived: $filename"
done
# 7. agent 脚本(jq 版):提取 completed 事件中的 sha256
aerosync watch --format json 2>/dev/null \
# 8. 只监听失败事件(用于告警)
aerosync watch --filter failed --format json 2>/dev/null \
| jq -r '"ALERT: upload failed for \(.filename): \(.reason)"'
# 9. 自动重连(断网后无限重试,初始等待 2s,最多等 60s)
aerosync watch --reconnect
# 10. 限制重连次数(最多重试 5 次,初始等待 3s)
aerosync watch --reconnect --max-retries 5 --retry-delay 3
# 11. agent 场景:重连 + json 模式(服务重启后自动恢复订阅)
aerosync watch --reconnect --format json 2>/dev/null \
**与 Prometheus 指标的组合使用**:
```bash
# 终端 1:实时监听文件事件
aerosync watch --filter completed
# 终端 2:定期采集指标
**注意事项**:
- `aerosync watch` 会持续运行直到 `Ctrl+C` 或服务端关闭连接
- 若接收端未启用 WebSocket(`[ws] enabled = false`),连接会被拒绝并返回 404
- 事件缓冲区满(默认 256 条)时,滞后的客户端会收到 `WebSocket error: lagged` 警告,不影响接收端正常运行
---
## 6. 协议交互时序
### 6.1 QUIC 自动协商流程
```
Client Server (port 7788)
│ │
│ GET http://{host}:7788/health │
│ ─────────────────────────────────► │
│ │
│ 200 OK │
│ X-AeroSync: true │
│ ◄───────────────────────────────── │
│ │
│ (升级到 QUIC, port = 7788+1) │
│ │
│ QUIC connect → {host}:7789 │
│ ─────────────────────────────────► │
│ │
│ UPLOAD:{filename}:{size}:{token} │
│ ─────────────────────────────────► │
│ │
│ stream bytes (流式传输) │
│ ─────────────────────────────────► │
│ │
│ OK:{sha256} │
│ ◄───────────────────────────────── │
```
降级条件:
- `/health` 超时(2s)
- 响应头无 `X-AeroSync: true`
- 目标 URL 已有显式协议前缀(`http://`、`s3://`、`ftp://`)
### 6.2 分片上传流程
```
Client Server
│ │
│ POST /upload/chunk │
│ ?task_id=UUID&chunk_index=0 │
│ &total_chunks=5&filename=file.bin │
│ body: [32MB bytes] │
│ ───────────────────────────────────────► │
│ 200 OK {"received":0} │ 写入 .aerosync/tmp/{uuid}/00000000
│ ◄─────────────────────────────────────── │
│ │
│ POST /upload/chunk?chunk_index=1 ... │
│ ───────────────────────────────────────► │
│ 200 OK {"received":1} │ 写入 .aerosync/tmp/{uuid}/00000001
│ ◄─────────────────────────────────────── │
│ │
│ ... (中途可中断,状态持久化) ... │
│ │
│ POST /upload/complete │
│ ?task_id=UUID&filename=file.bin │
│ &total_chunks=5&sha256=204be31... │
│ ───────────────────────────────────────► │
│ │ 顺序拼接 00000000~00000004
│ │ 校验 SHA-256
│ 200 OK {"saved":"file.bin","size":...} │ 删除 tmp/{uuid}/
│ ◄─────────────────────────────────────── │
```
断点续传恢复:
- 客户端读取 `.aerosync/{task_id}.json`
- `pending_chunks()` 跳过 `completed_chunks` 中的序号
- 直接从剩余分片继续,不重传已完成分片
### 6.3 Auth 验证流程(HTTP)
```
Client Server
│ │
│ POST /upload │
│ Authorization: Bearer <token> │
│ X-File-Hash: sha256hex │
│ ───────────────────────────────────────► │
│ │ AuthMiddleware::authenticate_http_request()
│ │ ├─ 解析 Bearer token
│ │ ├─ HMAC-SHA256 验签
│ │ ├─ 检查过期时间
│ │ └─ 验证通过 → 处理上传
│ 401 Unauthorized(验证失败) │
│ or 200 OK(验证通过) │
│ ◄─────────────────────────────────────── │
```
### 6.4 S3 上传流程
```
Client S3 / MinIO
│ │
│ PUT /{bucket}/{key} │
│ Authorization: AWS4-HMAC-SHA256 │ (AWS S3)
│ or Authorization: Bearer token │ (MinIO)
│ x-amz-date: 20260411T000000Z │
│ Content-Length: {size} │
│ body: file bytes │
│ ─────────────────────────────────► │
│ 200 OK │
│ ◄───────────────────────────────── │
```
URL 格式:`s3://bucket-name/path/to/key`
不支持分片续传(S3 Multipart Upload 已实现,见 §5.4),大文件超过 `multipart_threshold`(默认 100MB)自动切换为分片上传。
---
*最后更新:2026-04-12(mDNS 服务发现 + 多机连接指南完成,累计 231 个测试,0 失败)*
---
## 7. 多机连接实战指南
本章描述两台或多台机器之间真实使用 AeroSync 的完整流程:**服务发现 → 能力协商 → 连接建立 → 传输 → 事件订阅**。
---
### 7.1 服务发现
#### 方式一:mDNS 自动发现(局域网推荐)
receiver 启动后自动在局域网广播 `_aerosync._tcp.local.`,sender 无需手动输入 IP。
**实现**:`aerosync-core/src/discovery.rs`,`AeroSyncMdns::register()` 在 `FileReceiver::start()` 中自动调用。
**mDNS TXT 记录**:
| `version` | `0.2.0` | AeroSync 版本号 |
| `ws` | `true/false` | WebSocket 是否启用 |
| `auth` | `true/false` | 是否需要认证 token |
**发现 receiver:**
```bash
# 扫描局域网,等待 3 秒(默认)
aerosync discover
# 扫描 5 秒,JSON 格式(适合脚本)
aerosync discover --timeout 5 --json
```
**示例输出:**
```
Scanning for AeroSync receivers on local network (3s)…
Found 2 receiver(s):
NAME ADDRESS VERSION WS AUTH
--------------------------------------------------------------------
machine-b.local 192.168.1.20:7788 0.2.0 yes no
gpu-server.local 10.0.0.5:7788 0.2.0 yes yes
```
**JSON 模式(脚本):**
```bash
# 取第一个 receiver 的地址
```
#### 方式二:手动指定 IP(跨网络 / 无 mDNS 场景)
```bash
# 直接用 host:port,自动协商协议
aerosync send ./file.bin 192.168.1.20:7788
# 强制 HTTP(NAT/防火墙限制 UDP 时)
aerosync send ./file.bin http://192.168.1.20:7788/upload
```
---
### 7.2 能力协商
连接建立前有两步自动协商:
#### 第一步:协议协商(`src/main.rs: negotiate_protocol()`)
```
sender 输入 host:port(无协议前缀)
│
├─► GET http://host:port/health (2s timeout)
│ Response Header: X-AeroSync: true ?
│
├─ 是 AeroSync ──► 升级 QUIC: quic://host:(port+1)/upload
└─ 非 AeroSync / 超时 ──► 降级 HTTP: http://host:port/upload
```
输入已有协议前缀(`http://`、`quic://`、`s3://`、`ftp://`)时**跳过协商**,直接使用指定协议。
#### 第二步:Preflight 磁盘检查(`aerosync-core/src/preflight.rs`)
```
GET http://host:port/health
← { version, free_bytes, total_bytes, received_files }
if free_bytes < total_transfer_size:
abort("Insufficient disk space on receiver")
else:
proceed with transfer
```
`--no-preflight` 跳过此检查。`free_bytes == 0` 时(旧版 receiver)视为空间充足,不中断。
---
### 7.3 连接建立全流程时序
```
Machine A (sender) Machine B (receiver, IP=192.168.1.20)
│ │
│ [mDNS] _aerosync._tcp.local broadcast │ ← receiver 启动时自动广播
│ ◄────────────────────────────────────── │
│ │
│ aerosync discover │
│ → 发现 192.168.1.20:7788 │
│ │
│ GET /health (协议探测) │
│ ────────────────────────────────────────►│
│ 200 OK X-AeroSync: true │
│ ◄────────────────────────────────────── │
│ │
│ (升级到 QUIC, port 7789) │
│ │
│ GET /health (preflight 磁盘检查) │
│ ────────────────────────────────────────►│
│ { free_bytes: 500GB, version: "0.2.0" } │
│ ◄────────────────────────────────────── │
│ │
│ QUIC connect → 192.168.1.20:7789 │
│ ────────────────────────────────────────►│
│ TLS 1.3 handshake (自签名证书) │
│ ◄──────────────────────────────────────► │
│ │
│ UPLOAD:file.bin:1073741824:Bearer_token │
│ ────────────────────────────────────────►│
│ │
│ stream bytes (多并发流) │
│ ════════════════════════════════════════►│
│ │
│ OK:sha256hex │
│ ◄────────────────────────────────────── │
│ │
│ (可选) GET /ws (WebSocket 订阅) │
│ ────────────────────────────────────────►│
│ { event: "completed", filename: ... } │
│ ◄────────────────────────────────────── │
```
---
### 7.4 认证
#### 配置认证
```bash
# receiver 端
aerosync receive --auth-token my_secret_token
# sender 端
aerosync send ./file.bin 192.168.1.20:7788 --token my_secret_token
```
#### 认证流程(HTTP)
```
HTTP Header: Authorization: Bearer my_secret_token
→ AuthMiddleware::extract_token_from_header()
→ AuthManager::authenticate(token, client_ip)
→ 失败 → 401 Unauthorized,记录审计日志
→ 成功 → 继续处理上传
```
支持格式:
- `Authorization: Bearer <token>`
- `X-Auth-Token: <token>`(直接 token)
#### Token 管理
```bash
# 生成长期 token 并保存
aerosync token generate --save --label "machine-b-prod"
# 列出所有 token
aerosync token list
# 撤销 token
aerosync token revoke <token-prefix>
```
---
### 7.5 跨网络场景(NAT / 防火墙)
AeroSync **不内置 NAT 穿透**,跨网络需借助外部手段:
| 同局域网 | mDNS 自动发现 | 零配置,`aerosync discover` 直接找到 |
| receiver 有公网 IP | 直接连接 | 防火墙放行 TCP 7788(HTTP)和 UDP 7789(QUIC)|
| 双方都在 NAT 后 | Tailscale / WireGuard | 建立 VPN 后用虚拟 IP 连接 |
| 企业内网 | SSH 反向隧道 | `ssh -R 7788:localhost:7788 jump-server` |
| UDP 被封锁(QUIC 不通) | 强制 HTTP | 用 `http://host:7788/upload` 显式 URL |
| Docker / 容器 | 映射端口 | `-p 7788:7788 -p 7789:7789/udp` |
#### QUIC 端口说明
| HTTP | 7788(默认) | TCP | 文件上传、/health、/ws、/metrics |
| QUIC | 7789(默认) | UDP | 高性能文件传输 |
防火墙规则(以 iptables 为例):
```bash
# receiver 机器上
iptables -A INPUT -p tcp --dport 7788 -j ACCEPT
iptables -A INPUT -p udp --dport 7789 -j ACCEPT
```
#### 仅 HTTP 模式(禁用 QUIC)
```bash
# receiver 端禁用 QUIC
aerosync receive --http-only
# sender 端强制 HTTP
aerosync send ./file.bin http://host:7788/upload
```
---
### 7.6 多机 Agent 协作
两个 agent 通过 AeroSync 通信的完整操作流程:
**机器 B(receiver agent):**
```bash
# 方式一:Python agent(自动管理 aerosync receive 子进程)
python examples/receiver_agent.py \
--save-dir /data/inbox \
--host 0.0.0.0:7788
# 方式二:直接启动 aerosync receive
aerosync receive --save-to /data/inbox --port 7788
# 另一终端订阅事件
aerosync watch --reconnect --format json 2>/dev/null \
**机器 A(sender agent):**
```bash
# 自动发现局域网内的 receiver
aerosync discover
# Python agent(自动监控目录 + 订阅 WS 回传)
python examples/sender_agent.py \
--watch-dir /data/outbox \
--host 192.168.1.20:7788 \
--results-dir /data/results
# 或直接发送
aerosync send ./payload.bin 192.168.1.20:7788
```
**跨机 WS 订阅(sender 监听 receiver 事件):**
```bash
# 机器 A 订阅机器 B 的事件流
aerosync watch 192.168.1.20:7788 --reconnect --format json 2>/dev/null \
---
### 7.7 `aerosync discover` 命令参考
**实现**:`src/main.rs: cmd_discover()`,调用 `AeroSyncMdns::discover()` 扫描局域网。
| `--timeout <s>` | `3` | 扫描等待时间(秒) |
| `--json` | false | JSON 格式输出(每行一个 receiver) |
**JSON 输出字段**:
```json
{
"name": "machine-b.local",
"host": "192.168.1.20",
"port": 7788,
"addr": "192.168.1.20:7788",
"version": "0.2.0",
"ws_enabled": true,
"auth_required": false
}
```
**脚本集成示例**:
```bash
# 自动发现并发送到第一个无认证的 receiver
ADDR=$(aerosync discover --json \
| jq -r 'select(.auth_required == false) | .addr' \
| head -1)
[ -n "$ADDR" ] && aerosync send ./file.bin "$ADDR"
# 发现所有支持 WS 的 receiver 并订阅
aerosync discover --json \
| jq -r 'select(.ws_enabled == true) | .addr' \
| while read addr; do
aerosync watch "$addr" --reconnect &
done
```
---
### 7.8 mDNS 服务发现实现参考
**实现**:`aerosync-core/src/discovery.rs`
**核心接口**:
```rust
/// 广播句柄 — 保持存活则持续广播,drop 时自动注销
pub struct MdnsHandle { ... }
pub struct AeroSyncMdns;
impl AeroSyncMdns {
/// receiver 端:在局域网广播自身(FileReceiver::start() 自动调用)
pub fn register(
instance_name: &str,
port: u16,
version: &str,
ws_enabled: bool,
auth_required: bool,
) -> Result<MdnsHandle, mdns_sd::Error>;
/// sender 端 / discover 命令:扫描局域网内所有 AeroSync receiver
pub async fn discover(timeout: Duration) -> Vec<AeroSyncPeer>;
}
pub struct AeroSyncPeer {
pub name: String,
pub host: String, // IPv4 优先
pub port: u16,
pub version: Option<String>,
pub ws_enabled: bool,
pub auth_required: bool,
}
impl AeroSyncPeer {
pub fn addr(&self) -> String; // "host:port"
}
```
**集成点**:
- `FileReceiver::start()` → 自动调用 `AeroSyncMdns::register()`
- `FileReceiver::stop()` → 自动 drop `MdnsHandle`,注销广播
- `cmd_discover()` → 调用 `AeroSyncMdns::discover()`
**mDNS 不可用时的行为**:广播失败只打印 `warn` 日志,不影响 receiver 正常工作(non-fatal)。