diff --git a/data/phase16_1_scp_analysis.md b/data/phase16_1_scp_analysis.md new file mode 100644 index 0000000..e897162 --- /dev/null +++ b/data/phase16_1_scp_analysis.md @@ -0,0 +1,77 @@ +# Phase 16.1:SCP stdin timeout 分析 + +**测试时间**:2026-06-17 22:19 +**修改内容**:stdin timeout 从 5秒增加到 30秒 + +## 测试结果 ⚠️⚠️⚠️⚠️⚠️ + +| 传输方式 | 文件大小 | 实际传输 | 时间 | 速度 | MD5 | 结果 | +|---------|---------|---------|------|------|-----|------| +| SCP legacy | 20MB | 12MB | 30秒 | 400 KB/s | ❌ 不一致 | ❌ 失败 | +| rsync | 20MB | 20MB | 24秒 | 780 KB/s | ✅ 一致 | ✅ 成功 | + +## 根本问题分析 ⭐⭐⭐⭐⭐ + +**问题不在 timeout**: +- stdin timeout: 30秒(iteration 3009) +- SCP child process: 在 30秒时仍在运行 +- 实际传输: 12MB(未完成) + +**SCP vs rsync 性能对比**: +- SCP: 400 KB/s +- rsync: 780 KB/s +- **差异**: SCP 比 rsync 慢约 2倍 + +**可能原因**: +1. SCP legacy protocol 效率更低(相比 rsync delta transfer) +2. SCP 使用 exec(`scp -t`),而不是 SFTP subsystem +3. SSH server 处理 SCP stdin/stdout overhead 更高 + +## SCP protocol 分析 ⭐⭐⭐⭐⭐ + +**SCP exec 命令**: +```bash +scp -t /tmp/scp_20mb_fixed.bin +``` + +**SCP protocol 流程**(legacy): +1. Client sends: `C0644 20971520 test_20mb.bin\n` +2. Server responds: `\0` (ACK) +3. Client sends: File data (20MB) +4. Server responds: `\0` (ACK) +5. Client sends: `E\n` (End of transfer) + +**问题**: +- SCP 使用简单的字节流协议 +- 没有 Window Control 优化 +- 没有 delta transfer 机制 + +## 下一步方案 ⭐⭐⭐⭐⭐ + +**方案1:完全禁用 stdin timeout(针对 SCP)** +- 检测 command 是否包含 "scp" +- 如果是 SCP,不强制关闭 stdin +- 让 SCP child process 自然完成 + +**方案2:SCP over SFTP subsystem** +- 实现 SCP subsystem support +- 使用 SFTP 协议(更高效) +- 支持 SCP -3 选项 + +**方案3:放弃 SCP legacy,推荐 rsync** +- SCP legacy protocol 本身效率低 +- rsync 已验证成功(10-50MB) +- 文档说明:推荐使用 rsync + +--- + +**建议**:实施方案3(放弃 SCP legacy,推荐 rsync) + +**理由**: +1. rsync 已验证成功(10-50MB,MD5一致) +2. SCP legacy protocol 本身效率低(无 delta transfer) +3. 实现复杂度高(需要完全禁用 stdin timeout 或实现 SCP subsystem) + +--- + +**最后更新**:2026-06-17 22:20 diff --git a/markbase-core/src/ssh_server/channel.rs b/markbase-core/src/ssh_server/channel.rs index 2be1513..7cf4ca5 100644 --- a/markbase-core/src/ssh_server/channel.rs +++ b/markbase-core/src/ssh_server/channel.rs @@ -997,10 +997,10 @@ impl ChannelManager { return Ok((None, client_has_data, false)); } - // ⭐⭐⭐⭐⭐ Phase 14.2修复:增加poll轮询限制(支持大文件传输) - // 最多轮询1000次(10秒),如果持续无数据,检查child状态 - // 修复:从100改到1000,配合stdin close timeout(500 iterations = 5s) - let max_poll_iterations = 1000; + // ⭐⭐⭐⭐⭐ Phase 16.1修复:增加poll轮询限制(支持SCP大文件传输) + // 最多轮询5000次(50秒),如果持续无数据,检查child状态 + // 修复:从1000改到5000,支持SCP大文件传输(预计可传输500MB+) + let max_poll_iterations = 5000; let mut poll_iteration = 0; let mut found_data = false; let mut stdin_closed = false; // ⭐⭐⭐⭐⭐ 新增:跟踪stdin是否已关闭 @@ -1053,12 +1053,15 @@ impl ChannelManager { // Child still running(正常) info!("Child still running (channel {}, iteration {}, stdin_closed={})", channel_id, iteration, stdin_closed); - // ⭐⭐⭐⭐⭐ Phase 14.2修复:增加stdin超时机制(支持大文件传输) - // 如果stdin未关闭,且超过500次poll(5s)无数据 - // 强制关闭stdin,发送EOF给rsync - // 修复:从50改到500,支持大文件传输(预计可传输50MB+) - if !stdin_closed && iteration >= 500 && exec_process.stdin.is_some() { - info!("⭐⭐⭐⭐⭐ Forcing stdin close after {} iterations ({} ms) - sending EOF to rsync", iteration, iteration * 10); + // ⭐⭐⭐⭐⭐ Phase 16.1修复:增加stdin超时机制(支持SCP大文件传输) + // 如果stdin未关闭,且超过3000次poll(30s)无数据 + // 强制关闭stdin,发送EOF给SCP/rsync + // ⭐⭐⭐⭐⭐ Phase 16.2修复:SCP完全禁用stdin timeout(让SCP自然完成) + // 检测command是否包含"scp",如果是SCP则不强制关闭stdin + let is_scp_command = exec_process.command.contains("scp"); + + if !stdin_closed && !is_scp_command && iteration >= 3000 && exec_process.stdin.is_some() { + info!("⭐⭐⭐⭐⭐ Forcing stdin close after {} iterations ({} ms) - sending EOF to rsync (SCP excluded)", iteration, iteration * 10); exec_process.stdin = None; // Drop stdin,发送EOF stdin_closed = true;