# YAMA 文件传输速度调节设计 > **状态**:已实现(Windows 端,含多语言)。其他平台(Linux/macOS 客户端)暂不实施。 > **读者**:研发/评审人员。 > **关联文档**:[FILE_TRANSFER_V2.md](./FILE_TRANSFER_V2.md)(V2 协议)、[Mcp_FileTransfer_Design.md](./Mcp_FileTransfer_Design.md)(MCP 文件传输)。 --- ## 1. 背景与目标 文件传输速度目前是**写死的**:`SimplePlugins` 库的 `InitFileUpload(key, msg, hmac, chunkSizeKb, sendDurationMs, ...)` 被 YAMA 所有调用点传 `64, 50`,折算 ≈ 1.25 MB/s。用户无法调节。 目标:在 YAMA 主菜单「高级」下新增一个**面向小白**的「文件传输速度」对话框——拖动滑块即完成设置,实时显示预估速度;**不修改 SimplePlugins 库**(库已编译,只通过其已暴露的 `chunkSizeKb`/`sendDurationMs` 两个入参控制)。 --- ## 2. 速度原理(现状) 库内部把两个入参转成全局量(`SimplePlugins/file_upload.cpp:221-222, 849-850`): ```cpp static int chunk_size = 64 * 1024; // 默认 64KB static int send_duration = 50; // 默认 50ms // InitFileUpload: chunk_size = chunkSizeKb * 1024; send_duration = sendDurationMs; ``` 发送循环每发一块就 `Sleep(send_duration)`(V1 `FileBatchTransferWorker` 与 V2 `FileBatchTransferWorkerV2` 共用这对全局量): ``` 预估速度 = chunkSizeKb * 1024 / (sendDurationMs / 1000) 字节/秒 默认 64KB / 50ms ≈ 1.25 MB/s ``` 两个关键约束: 1. **速度只在 `InitFileUpload` 时设定一次**,库无运行时 setter(`g_threadCount > 1` 时提前返回,不会重设)。 2. **V1 / V2 共用同一对全局量**,一次设置同时覆盖 `COMMAND_SEND_FILE(68)` 与 `COMMAND_SEND_FILE_V2(85)` 两种协议。 --- ## 3. 档位表(权威唯一来源,主控端对话框维护) | 档位 | 名称 | chunkKb | durationMs | 预估速度 | |---|---|---|---|---| | 0 | 最慢 | 64 | 200 | ≈ 0.3 MB/s | | 1 | 慢 | 64 | 100 | ≈ 0.6 MB/s | | 2 | 默认 | 64 | 50 | ≈ 1.25 MB/s | | 3 | 快 | 128 | 50 | ≈ 2.5 MB/s | | 4 | 更快 | 256 | 50 | ≈ 5 MB/s | | 5 | 很快 | 512 | 50 | ≈ 10 MB/s | | 6 | 不限速 | 1024 | 0 | 网络上限 | - 滑块 `CSliderCtrl` 范围 `0..6`,`SetPageSize(1)`,默认落在 2。 - `durationMs = 0` 表示不人为限速(`Sleep(0)` 仅让出时间片,实际受带宽约束)。 - **注意 Windows `Sleep` 粒度 ≈ 15.6ms**:`durationMs` 低于约 15ms 基本等同不限速。故档位要么 ≥ 20ms,要么直接 0ms,不设 5ms 之类的“名不副实”中间值。 预估文案:`当前约 1.25 MB/s`;`durationMs==0` 显示 `不限速(受网络带宽约束)`。 - **多语言**:档位名(最慢/慢/默认/快/更快/很快/不限速)与预估格式串(`%s · 约 %.2f MB/s`、`%s · 不受限速(受网络带宽约束)`)经 `_TR`/`_LF` 走 `LangManager` 翻译表;语言文件以中文原文为 key。刻度标签拆成 最慢/默认/不限速 三个独立 `LTEXT` 以便各自翻译(英文宽度不同,不能靠空格对齐)。 --- ## 4. 两个方向的生效链路 文件传输有两条方向,`InitFileUpload` 各自在不同进程/时机调用: | 方向 | 发送方 | `InitFileUpload` 调用点 | 生效方式 | |---|---|---|---| | 上传(主控端 → 被控端) | 主控端 | `server/2015Remote/2015RemoteDlg.cpp:2223`(启动一次) | 读 `THIS_CFG`,重启主控端生效 | | 下载(被控端 → 主控端) | 被控端 | `client/FileManager.cpp:40`(CFileManager 构造,每会话一次) | 读客户端配置(`MasterSettings` 登录下发写入),下次打开文件管理会话生效 | ### 4.1 上传方向(主控端本地,无协议改动) `2015RemoteDlg.cpp:2223` 现为 `InitFileUpload({},{},GetHMAC(),64,50,Logf,GetUpperHash())`,改为读 `THIS_CFG`: ```cpp int chunkKb = THIS_CFG.GetInt("transfer", "ChunkKb", 64); int durMs = THIS_CFG.GetInt("transfer", "DurationMs", 50); InitFileUpload({}, {}, GetHMAC(), chunkKb, durMs, Logf, GetUpperHash()); ``` ### 4.2 下载方向(随 `MasterSettings` 登录下发到被控端) 下载速度**不再用独立命令下发**,而是折叠进 `MasterSettings` 结构体,随**客户端每次登录成功**第一时间下发(主连接建立即 `SendMasterSettings`),客户端持久化到 `[transfer]` 节后本地生效: 1. **`common/commands.h`**:在 `MasterSettings` 结构体 `Reserved` 之前新增两个字段(`sizeof` 仍为 1000,`Signature` 偏移不变,向下兼容老客户端): ```c uint16_t TransferChunkKb; // 文件传输速度 chunkKb(随登录配置下发) uint16_t TransferDurationMs; // 文件传输速度 durationMs(0=不限速) ``` 2. **主控端登录即下发**:`OnInitDialog()` 读 `THIS_CFG` 的 `[transfer]`(`GetTransferSpeed`,默认 64/50),填入 `m_settings.TransferChunkKb/TransferDurationMs`。登录链路本就把 `m_settings` 经 `SendMasterSettings` 下发给每个新连接客户端,**无需任何额外命令**。 3. **主控端改速即时广播**:`OnFileTransferSpeed()` 保存后同步更新 `m_settings` 两个字段并 `SendMasterSettings(nullptr, m_settings)`,在线客户端即时收到新速度;新连接主机也自动获取。 4. **被控端接收**:客户端 `CMD_MASTERSETTING` 处理器(`client/KernelManager.cpp:1116` 附近)中,`TransferChunkKb` 非 0(老主控未携带时为 0,保留本地默认值)则把两数写入 `iniFile(CLIENT_PATH)` 的 `[transfer]` 节。 5. **被控端调用点读配置**:`client/FileManager.cpp:40`(CFileManager 构造)等 4 个 `InitFileUpload` 调用点均读 `cfg.GetInt("transfer", ...)`,替换硬编码 `64, 50`。CFileManager 每会话重建(构造 Init、析构 Uninit),故下次打开文件管理即生效。 > **为何不用独立命令**:v1.4.0 曾用独立 `CMD_FILE_SPEED(252)` 在 `OnOnlineFileManager()`/`OnOnlineDesktopManager()` 发 `COMMAND_LIST_DRIVE`/`COMMAND_SCREEN_SPY` **之前**追加下发。主控端 IOCP 对两次背靠背 `Send2Client` 会合并为两次整包 `WSASend`(`OutCompressedBuffer` 累积),导致客户端收到重复的 `[FILE_SPEED][SCREEN_SPY]` 命令流——这正是“点击远程桌面弹出两个对话框”的根因(commit 76decec)。改为随 `MasterSettings` 登录下发后,下载方向不再有任何背靠背发送。 > **向后兼容**:`CMD_FILE_SPEED = 252` 命令号与客户端 `case CMD_FILE_SPEED` 处理器仍保留,供老主控(仍按旧机制下发)与新客户端互通;新主控不再发送该命令。 > **关键**:库用进程级引用计数,速度只在**首次** `InitFileUpload` 时设定(`g_threadCount > 1` 时提前返回,不重设)。客户端共 4 个 `InitFileUpload` 调用点(`FileManager.cpp:40`、`ScreenManager.cpp:109`、`ScreenManager.cpp:873`、`KernelManager.cpp:1399`),谁先初始化谁锁定速度。故**这 4 处必须全部改为读同一配置**,否则下载速度会被先初始化的管理器(如远程桌面会话)锁成旧值。 --- ## 5. 配置存储 - 主控端:`THIS_CFG`(ini)`[transfer]` 节 `ChunkKb` / `DurationMs`(int)。 - 被控端:`iniFile(CLIENT_PATH)` 的 `[transfer]` 节 `ChunkKb` / `DurationMs`(int),由 `MasterSettings` 登录下发写入。 存 `ChunkKb`/`DurationMs` 两个 int 而非档位索引:自解释、直接对应 `InitFileUpload` 入参、不易错位。 --- ## 6. 改动文件清单(Windows 端) | 文件 | 改动 | |---|---| | `server/2015Remote/resource.h` | 加 `IDD_FILE_SPEED`、`ID_TOOL_FILE_SPEED`、`IDC_FSPEED_SLIDER`、`IDC_FSPEED_LABEL`、`IDC_FSPEED_RESET` | | `server/2015Remote/2015Remote.rc` | 「高级」菜单加一项;新增对话框资源(滑块+预估标签+按钮)。**文件为 UTF-16LE 编码,须按 Unicode 读写** | | `server/2015Remote/CFileSpeedDlg.h` | 新建:档位表 + `OnHScroll` 实时预估 + 恢复默认(照 `CPreviewLevelsDlg.h` 内联实现) | | `server/2015Remote/2015RemoteDlg.h` | 加 `OnFileTransferSpeed()` 声明 | | `server/2015Remote/2015RemoteDlg.cpp` | 消息映射 `ON_COMMAND(ID_TOOL_FILE_SPEED, ...)`;`2223` 读配置(上传);`OnInitDialog()` 把 `[transfer]` 速度填入 `m_settings`;`OnFileTransferSpeed()` 保存后更新 `m_settings` 并经 `SendMasterSettings(nullptr, ...)` 广播 | | `common/commands.h` | `MasterSettings` 新增 `TransferChunkKb`/`TransferDurationMs`(`sizeof` 不变,`CMD_FILE_SPEED=252` 保留向后兼容) | | `client/KernelManager.cpp` | `CMD_MASTERSETTING` 处理器写 `[transfer]`(`TransferChunkKb` 非 0 才写) | | `client/FileManager.cpp`、`client/ScreenManager.cpp`、`client/KernelManager.cpp` | 4 个 `InitFileUpload` 调用点读配置替换硬编码 `64, 50`(引用计数要求全部一致) | | `server/2015Remote/lang/en_US.ini`、`zh_TW.ini`、`de_DE.ini` | 追加「文件传输速度」相关 13 条中文原文→译文(GBK 编码,`RCDATA "lang\*.ini"` 由 rc.exe 嵌入) | --- ## 7. 生效时机与提示文案 - 上传速度:重启主控端生效。 - 下载速度:已推送到在线客户端即时生效;新连接的主机自动获取。 - 对话框保存后弹提示:`文件传输速度已保存。上传速度重启主控端后生效;下载速度已推送到在线客户端,新连接的主机也会自动获取。` --- ## 8. 风险与备注 1. **`Sleep` 精度**:Windows 计时器粒度约 15.6ms,`durationMs` 过小则失真(见 §3)。 2. **实际速度 < 预估**:预估为纯 `chunk/delay` 理论值,未计入 `f()` 网络发送耗时;对话框文案用“约/大概”表述。 3. **结构体字段布局**:`TransferChunkKb`/`TransferDurationMs` 加在 `Reserved` 之前(`sizeof(MasterSettings)` 仍为 1000,`Signature` 偏移不变),保证与 Go 侧/老客户端二进制兼容;`TransferChunkKb == 0` 作为“未下发”哨兵(老主控未填充 → 客户端保留本地默认值)。 4. **`.rc` 编码**:`2015Remote.rc` 为 UTF-16LE(带 BOM),编辑必须按 Unicode 处理,避免破坏资源脚本。 5. **跨平台**:Linux/macOS 客户端下载路径暂不改;`MasterSettings` 新增字段位于原 `Reserved` 区(老/其他平台客户端不识别即忽略),向后兼容。 6. **多语言 key 精确匹配**:语言文件为 GBK 编码、key 不 trim(`CIniParser` 保留原始空格);译文 key 必须与源码逐字一致——源码为 UTF-8、经 `/execution-charset:.936` 编译为 GBK,`·`(U+00B7)、全角括号()等需一致,否则查不到翻译。 --- ## 9. 实施顺序 1. `common/commands.h` 扩 `MasterSettings` → `resource.h` 加 ID → `.rc` 加菜单项与对话框资源。 2. 新建 `CFileSpeedDlg.h` → `2015RemoteDlg` 接线(上传方向,可先验证)。 3. 主控端经 `MasterSettings` 下发速度 → 客户端 `CMD_MASTERSETTING` 持久化 → 客户端调用点读配置(下载方向)。 4. 编译验证(MSBuild 须传 `-p:SolutionDir`,见项目记忆)。