<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>izum</title><description>个人博客</description><link>https://izumkineno.github.io/my-blog/</link><language>zh_CN</language><item><title>给 AI 足够上下文，能把 Slint 搬到 ESP32 上吗？</title><link>https://izumkineno.github.io/my-blog/posts/ai-context-esp32-slint/</link><guid isPermaLink="true">https://izumkineno.github.io/my-blog/posts/ai-context-esp32-slint/</guid><description>从微雪官方 LVGL Demo 提取硬件信息，先用 embedded-graphics 完成 Rust 亮屏实验，再沿 Slint 源码找到 no_std 软件渲染接口，将声明式 GUI 跑上 ESP32-S3 圆屏。</description><pubDate>Sun, 26 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;我一开始有一个很直接的想法：&lt;strong&gt;只要给 AI 足够多的上下文，它应该就能完成大部分项目。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这次我拿一块微雪 &lt;code&gt;ESP32-S3-Touch-LCD-1.85C&lt;/code&gt; 做了实验。官方主要提供 Arduino、ESP-IDF 和 LVGL 示例，而我想走一条并不在官方 Demo 里的路线：使用 Rust 驱动整块开发板，再把 Slint 的 GUI 跑到这块 360 × 360 的圆形触摸屏上。&lt;/p&gt;
&lt;p&gt;最后，屏幕成功点亮，Slint 界面能够正常渲染，触摸也可以转换为界面事件。项目已经公开在 GitHub：&lt;a href=&quot;https://github.com/izumkineno/esp32_slint_waveshare&quot;&gt;izumkineno/esp32_slint_waveshare&lt;/a&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
本文先记录从官方 Demo 到 Rust、再到 Slint 的移植过程。开发板实拍和运行界面截图将在后续更新中补充。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;我真正想验证的是什么&lt;/h2&gt;
&lt;p&gt;如果只把一句“帮我给 ESP32 写个 Slint GUI”交给 AI，得到的结果很可能只是能够解释概念、却无法在真实硬件上工作的代码。&lt;/p&gt;
&lt;p&gt;嵌入式开发的问题不只在语言和框架。屏幕型号、总线协议、GPIO 映射、复位顺序、初始化命令、像素格式、内存位置和工具链版本，任何一项错误都可能让设备停在黑屏。AI 并不知道我手里这块板子的实际连接方式，也无法凭空判断厂商 Demo 中哪些步骤不能省略。&lt;/p&gt;
&lt;p&gt;所以这次实验里的“足够上下文”，并不是简单地把大量文件塞进提示词，而是给出一条可以追溯的证据链：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.waveshare.net/ESP32-S3-Touch-LCD-1.85C/&quot;&gt;微雪官方硬件文档&lt;/a&gt;负责说明板载器件和基础规格。&lt;/li&gt;
&lt;li&gt;官方 Arduino 与 ESP-IDF 的 LVGL Demo 负责说明这块板子实际上如何初始化和通信。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;esp-hal&lt;/code&gt; 与 Rust 工程负责约束目标平台、所有权模型和 &lt;code&gt;no_std&lt;/code&gt; 环境。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/embedded-graphics/embedded-graphics&quot;&gt;embedded-graphics&lt;/a&gt;先验证底层显示与触摸链路。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/slint-ui/slint&quot;&gt;Slint 源码&lt;/a&gt;负责回答框架在没有桌面窗口系统时如何接入自定义平台和显示设备。&lt;/li&gt;
&lt;li&gt;编译、烧录和真实触摸操作负责验证结果，而不是让“代码看起来合理”成为终点。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这条链路把一个模糊目标拆成了可以逐层验证的问题。&lt;/p&gt;
&lt;h2&gt;第一步：从 LVGL Demo 中提取硬件使用方式&lt;/h2&gt;
&lt;p&gt;目标开发板的核心硬件是：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;模块&lt;/th&gt;
&lt;th&gt;型号与配置&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;主控&lt;/td&gt;
&lt;td&gt;ESP32-S3，最高 240 MHz&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;存储&lt;/td&gt;
&lt;td&gt;16 MB Flash、8 MB Octal PSRAM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LCD&lt;/td&gt;
&lt;td&gt;ST77916，1.85 英寸，360 × 360，RGB565&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LCD 总线&lt;/td&gt;
&lt;td&gt;QSPI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;触摸&lt;/td&gt;
&lt;td&gt;CST816S，I2C 单点电容触摸&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IO 扩展&lt;/td&gt;
&lt;td&gt;TCA9554PWR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RTC&lt;/td&gt;
&lt;td&gt;PCF85063&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;硬件介绍只能告诉我“有什么”，真正决定驱动能否工作的，是 Demo 告诉我的“怎么用”。我让 AI 同时阅读官方 Arduino 和 ESP-IDF 示例，把信息整理成独立的硬件笔记，而不是直接照抄某一套 C/C++ API。&lt;/p&gt;
&lt;p&gt;提取出的关键连接包括：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;信号&lt;/th&gt;
&lt;th&gt;配置&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;I2C SDA / SCL&lt;/td&gt;
&lt;td&gt;GPIO11 / GPIO10，400 kHz&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TCA9554PWR&lt;/td&gt;
&lt;td&gt;I2C 地址 &lt;code&gt;0x20&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LCD reset&lt;/td&gt;
&lt;td&gt;TCA9554PWR EXIO2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Touch reset&lt;/td&gt;
&lt;td&gt;TCA9554PWR EXIO1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LCD QSPI SCK&lt;/td&gt;
&lt;td&gt;GPIO40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LCD QSPI D0 / D1 / D2 / D3&lt;/td&gt;
&lt;td&gt;GPIO46 / GPIO45 / GPIO42 / GPIO41&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LCD CS&lt;/td&gt;
&lt;td&gt;GPIO21&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LCD backlight&lt;/td&gt;
&lt;td&gt;GPIO5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CST816S&lt;/td&gt;
&lt;td&gt;I2C 地址 &lt;code&gt;0x15&lt;/code&gt;，中断 GPIO4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;更重要的是初始化顺序：先建立 I2C，再配置 TCA9554PWR；通过扩展 IO 释放 LCD 复位后，才能初始化 ST77916；触摸控制器也依赖同一条 I2C 和另一根扩展复位线。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;I2C
  ↓
TCA9554PWR
  ├─ EXIO2 → LCD reset
  └─ EXIO1 → Touch reset
       ↓
ST77916 QSPI + CST816S I2C
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ST77916 也不是“把像素通过 SPI 发出去”这么简单。Rust 驱动最终保留了 Demo 中能够被验证的面板命令流：低速读取面板信息、软件复位并等待、切换到 40 MHz QSPI、设置 RGB565 像素格式、发送厂商初始化表，最后打开显示。&lt;/p&gt;
&lt;p&gt;这一步让我确认了一件事：&lt;strong&gt;厂商 Demo 不只是示例代码，它还是硬件行为的可执行文档。&lt;/strong&gt; AI 的价值在于跨 Arduino、ESP-IDF 和芯片资料提取不变量，再把它们映射到新的语言与 HAL，而不是逐行翻译 API。&lt;/p&gt;
&lt;h2&gt;第二步：先做 &lt;code&gt;esp_learn&lt;/code&gt; 亮屏实验&lt;/h2&gt;
&lt;p&gt;我没有直接把 Slint 接上去，而是先建立了 &lt;code&gt;esp_learn&lt;/code&gt;。这个工程只解决三个问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Rust 能否通过 &lt;code&gt;esp-hal&lt;/code&gt; 正确初始化 ST77916；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;embedded-graphics&lt;/code&gt; 能否在圆屏上绘图；&lt;/li&gt;
&lt;li&gt;CST816S 的触摸坐标能否进入 Rust 主循环。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;St77916Display&lt;/code&gt; 实现 &lt;code&gt;embedded_graphics::DrawTarget&lt;/code&gt;，颜色格式使用 &lt;code&gt;Rgb565&lt;/code&gt;。360 × 360 × 2 字节的完整 framebuffer 大约是 253 KiB，因此它被放在静态存储区，而不是入口任务的栈上。绘制完成后，再通过 QSPI 把 framebuffer 刷到 LCD。&lt;/p&gt;
&lt;p&gt;圆形面板的控制器仍暴露 360 × 360 的方形地址空间。实验程序先把整个区域填黑，再绘制直径 356 像素的白色圆形画布；触摸命中判断也使用相同的圆形边界，避免在物理屏幕之外的四角继续绘图。&lt;/p&gt;
&lt;p&gt;最终的实验很简单：在屏幕上拖动可以画线，点击 &lt;code&gt;CLEAR&lt;/code&gt; 可以清空画板。它没有复杂 UI，却验证了完整的最小闭环：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;触摸坐标 → Rust 输入逻辑 → embedded-graphics → RGB565 framebuffer → ST77916 QSPI
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这一步非常关键。假如我一开始就引入 Slint，那么黑屏可能来自面板初始化、像素字节序、渲染器、内存分配或事件循环中的任何一层。先让 &lt;code&gt;embedded-graphics&lt;/code&gt; 亮屏，相当于把后续问题收敛成“如何把 Slint 的输出和输入接到已经工作的驱动上”。&lt;/p&gt;
&lt;h2&gt;第三步：从 Slint 源码里找移植接口&lt;/h2&gt;
&lt;p&gt;确认硬件链路后，我把 Slint 的源码拉到本地，让 AI 不再猜测框架应该怎样移植，而是直接查找已有的 MCU、软件渲染器和自定义 Platform 实现。&lt;/p&gt;
&lt;p&gt;最终找到的关键接口是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Platform&lt;/code&gt;：为没有桌面窗口系统的目标提供窗口和时间来源；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MinimalSoftwareWindow&lt;/code&gt;：在裸机或 RTOS 环境中承载 Slint 窗口；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SoftwareRenderer&lt;/code&gt;：把 Slint 场景渲染为软件像素；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;LineBufferProvider&lt;/code&gt;：逐行接收渲染结果并交给显示驱动；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WindowEvent&lt;/code&gt;：把触摸坐标转换为按下、移动、释放和离开事件。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;工程关闭了 Slint 的默认功能，只保留 MCU 移植需要的部分：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;slint = { version = &quot;=1.16.0&quot;, default-features = false, features = [
    &quot;compat-1-2&quot;,
    &quot;unsafe-single-threaded&quot;,
    &quot;libm&quot;,
    &quot;renderer-software&quot;,
] }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;构建脚本使用 &lt;code&gt;EmbedForSoftwareRenderer&lt;/code&gt; 编译 &lt;code&gt;.slint&lt;/code&gt; 文件并把所需资源嵌入固件。&lt;code&gt;no_std&lt;/code&gt; 环境下 Slint 仍需要动态分配，因此工程在创建任何 Slint 对象之前初始化板载 Octal PSRAM allocator。&lt;/p&gt;
&lt;p&gt;渲染侧则使用 &lt;code&gt;RepaintBufferType::ReusedBuffer&lt;/code&gt; 和一条 360 像素的 RGB565 行缓冲：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let window = MinimalSoftwareWindow::new(RepaintBufferType::ReusedBuffer);
window.set_size(PhysicalSize::new(360, 360));

let mut line_buffer = [Rgb565Pixel(0); 360];
window.draw_if_needed(|renderer| {
    renderer.render_by_line(DisplayLineBuffer {
        display: &amp;amp;mut display,
        buffer: &amp;amp;mut line_buffer,
    });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;DisplayLineBuffer::process_line()&lt;/code&gt; 让 Slint 先填充需要重绘的行片段，再调用显示驱动的 &lt;code&gt;write_line()&lt;/code&gt;。驱动把行号和像素范围转换成 ST77916 的写入窗口，随后用 QSPI 发送 RGB565 数据。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Slint scene
    ↓
MinimalSoftwareWindow + SoftwareRenderer
    ↓
LineBufferProvider
    ↓
360 像素 RGB565 行缓冲
    ↓
ST77916 局部窗口 + QSPI
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;与 &lt;code&gt;esp_learn&lt;/code&gt; 的完整 framebuffer 相比，这条路径不需要为 Slint 再准备一张 360 × 360 的整屏缓冲。单行 RGB565 像素本身只有 720 字节，渲染完成后就可以直接写入屏幕。&lt;/p&gt;
&lt;p&gt;输入方向正好相反：轮询 CST816S 得到坐标后，第一次接触发送 &lt;code&gt;PointerPressed&lt;/code&gt;，坐标变化发送 &lt;code&gt;PointerMoved&lt;/code&gt;，触摸结束发送 &lt;code&gt;PointerReleased&lt;/code&gt; 和 &lt;code&gt;PointerExited&lt;/code&gt;。这样 Slint 控件并不需要知道底层使用的是哪一款触摸芯片。&lt;/p&gt;
&lt;h2&gt;从亮屏到真正的 GUI&lt;/h2&gt;
&lt;p&gt;完成 Platform、渲染和输入适配后，我先做了一个 360 × 360 的圆形控制界面：触摸按钮可以更新计数，&lt;code&gt;CLEAR&lt;/code&gt; 可以清零，动画页面用于确认定时器和连续重绘能够运行。&lt;/p&gt;
&lt;p&gt;最初的成功版本证明了下面这条链路可以成立：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;微雪 LVGL Demo
    ↓ 提取硬件事实
Rust + esp-hal 驱动
    ↓ 验证显示和触摸
embedded-graphics 亮屏实验
    ↓ 替换图形层
Slint no_std software renderer
    ↓
ESP32-S3 圆屏声明式 GUI
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;项目后来继续加入了时钟、RTC、WiFi、Bluetooth LE 和更多圆屏页面，但这些都建立在最初的显示、触摸和 Slint 平台适配之上。本文记录的重点仍是第一次把 Slint 真正跑上这块开发板的过程。&lt;/p&gt;
&lt;p&gt;实际工程与构建说明：&lt;a href=&quot;https://github.com/izumkineno/esp32_slint_waveshare&quot;&gt;https://github.com/izumkineno/esp32_slint_waveshare&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;这次实验后，我重新理解了“足够的上下文”&lt;/h2&gt;
&lt;p&gt;实验结果基本支持我最初的判断，但也给它加上了几个前提。&lt;/p&gt;
&lt;h3&gt;1. 上下文的质量比数量重要&lt;/h3&gt;
&lt;p&gt;官方文档、厂商 Demo、目标框架源码、确定的工具链版本和真实编译错误，都是高价值上下文。论坛里缺少版本和硬件条件的零散代码，即使很多，也可能把 AI 带到错误方向。&lt;/p&gt;
&lt;h3&gt;2. 先建立可验证的中间结果&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;esp_learn&lt;/code&gt; 不是多余的临时工程。它把“移植 Slint”拆成了“先让 Rust 可靠控制硬件”和“再替换图形层”两个可验证阶段。AI 可以快速生成和修改代码，但物理世界仍需要亮屏、触摸和烧录结果来裁决对错。&lt;/p&gt;
&lt;h3&gt;3. 源码可以成为最精确的提示词&lt;/h3&gt;
&lt;p&gt;当高层文档没有覆盖我的硬件组合时，Slint 源码中的 MCU 适配方式比继续追问“应该怎么做”更有效。让 AI 在源码里查找 &lt;code&gt;MinimalSoftwareWindow&lt;/code&gt;、&lt;code&gt;LineBufferProvider&lt;/code&gt; 和软件渲染流程，得到的是框架已经支持的扩展点，而不是一套平行实现。&lt;/p&gt;
&lt;h3&gt;4. 人仍然要定义边界和验收条件&lt;/h3&gt;
&lt;p&gt;AI 可以帮助阅读、归纳、移植和排错，但“先验证哪一层”“什么结果算成功”“哪些资料可信”仍需要人来决定。这次的验收条件一直很具体：能编译、能烧录、能亮屏、能接收触摸、能让 Slint 控件响应。&lt;/p&gt;
&lt;p&gt;所以我现在会把最初的想法改写成：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;对于边界能够描述、结果能够验证的项目，只要向 AI 提供足够准确且结构化的上下文，它确实可以完成很大一部分工程工作。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这不是让 AI 凭空替代嵌入式开发，而是把硬件文档、厂商实现、框架源码和真实设备组织成一个能够持续验证的工作环境。对我来说，这次从 LVGL Demo 到 Rust，再到 Slint 的移植，就是这套方法第一次完整跑通。&lt;/p&gt;
</content:encoded></item><item><title>68 元捡来一台阿里云 200 Mbps 小服务器：搭建 EasyTier 与 RustDesk 中继</title><link>https://izumkineno.github.io/my-blog/posts/45%E6%8D%A1%E6%9D%A5%E4%B8%80%E4%B8%AA%E9%98%BF%E9%87%8C%E7%9A%84200mbs%E5%B8%A6%E5%AE%BD%E7%9A%84%E8%B6%85%E5%B0%8F%E6%9C%8D%E5%8A%A1%E5%99%A8/</link><guid isPermaLink="true">https://izumkineno.github.io/my-blog/posts/45%E6%8D%A1%E6%9D%A5%E4%B8%80%E4%B8%AA%E9%98%BF%E9%87%8C%E7%9A%84200mbs%E5%B8%A6%E5%AE%BD%E7%9A%84%E8%B6%85%E5%B0%8F%E6%9C%8D%E5%8A%A1%E5%99%A8/</guid><description>从 Sakura Frp 中转 EasyTier 的临时方案，折腾到用一台带独立公网 IP 的小服务器自建 EasyTier 组网节点和 RustDesk 中继。</description><pubDate>Wed, 17 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;最近用向日葵远程电脑时，我越来越受不了它的延迟：画面更新慢、操作有明显的黏滞感，网络稍有波动就更难受。单纯换一个远程桌面客户端并不能解决所有问题，真正影响体验的还有连接路径——只要流量绕得足够远，再好的客户端也救不回来。&lt;/p&gt;
&lt;p&gt;我由此想起了以前开《方舟：生存进化》和 Minecraft 服务器时留下的两套工具：EasyTier 和 Sakura Frp。当时为了让不同网络下的朋友加入服务器，我没少折腾 P2P 打洞、端口映射和中转节点。这次干脆把旧方案重新组合，看看能不能搭一套延迟更低、路径更可控的远程桌面环境。&lt;/p&gt;
&lt;h2&gt;从游戏联机方案想到远程桌面&lt;/h2&gt;
&lt;p&gt;国内家庭宽带普遍没有可直接入站的独立公网 IPv4，不少线路还处于运营商 CGNAT 后面。EasyTier 会优先尝试 NAT 穿透，但两端 NAT 类型、运营商策略或防火墙不合适时，P2P 连接仍然可能失败。&lt;/p&gt;
&lt;p&gt;我最早采用的办法是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在一台没有公网 IP 的设备上运行 EasyTier，让它监听本地端口。&lt;/li&gt;
&lt;li&gt;使用 Sakura Frp 把 EasyTier 的监听端口映射到一个公网节点。&lt;/li&gt;
&lt;li&gt;其他设备通过 Sakura Frp 节点提供的域名和端口加入 EasyTier 网络。&lt;/li&gt;
&lt;li&gt;EasyTier 能打洞时尽量走 P2P；打洞失败时，通过指定的中转路径保持可用。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这套方案的关键不是“消灭延迟”，而是把不可控的绕路收敛到一个位置合适的中转节点。最终延迟仍取决于中转机房、双方运营商线路、网络拥塞和丢包率，但通常会比随机分配、路径不可控的公共远控服务稳定。&lt;/p&gt;
&lt;p&gt;后来我准备把同一思路用到 RustDesk 上：先用 EasyTier 建立虚拟局域网，再把自建的 RustDesk ID 服务和中继服务放进这个网络。结果看 Sakura Frp 套餐时，发现当时最低的青铜 VIP 也要 10 元/月。价格可能随时调整，但这已经足够让我继续逛一圈云服务器。&lt;/p&gt;
&lt;p&gt;然后我碰到了这台小机器：2 核 CPU、2 GiB 内存、40 GiB 系统盘，峰值公网带宽 200 Mbps，还带独立公网 IP。价格只要 68 元，于是直接开干。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./pasted-image-1786279521986.png&quot; alt=&quot;服务器监控中显示 2 核 CPU、2 GiB 内存、40 GiB 系统盘和 200 Mbps 峰值公网带宽&quot; /&gt;&lt;/p&gt;
&lt;p&gt;需要注意，页面标注的是&lt;strong&gt;峰值公网带宽&lt;/strong&gt;，不等于任何时刻都能稳定跑满 200 Mbps。实际吞吐还会受实例限速、共享出口、线路质量和流量配额影响。对远程桌面来说，稳定延迟和低丢包通常比峰值数字更重要。&lt;/p&gt;
&lt;h2&gt;最终方案&lt;/h2&gt;
&lt;p&gt;这台服务器同时承担两个职责：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;EasyTier 固定节点&lt;/strong&gt;：所有设备先加入同一个加密虚拟网络；能直连时走 P2P，不能直连时由服务器协助转发。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RustDesk 服务端&lt;/strong&gt;：运行 &lt;code&gt;hbbs&lt;/code&gt;（ID/信令服务）和 &lt;code&gt;hbbr&lt;/code&gt;（中继服务），远控双方通过 EasyTier 虚拟 IP 访问它们。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;远控端 ─┐
        ├─ EasyTier 虚拟网络 ─ 阿里云服务器（10.144.144.1）
被控端 ─┘                         ├─ hbbs：ID/信令
                                 └─ hbbr：远程桌面中继
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样做有两个好处：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;公网侧原则上只需要开放 EasyTier 的入口，RustDesk 端口可以只允许虚拟网段访问。&lt;/li&gt;
&lt;li&gt;如果将来更换云服务器，只要保留 EasyTier 网段、RustDesk 数据目录和公钥，客户端配置基本不需要重做。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;下面以 Debian/Ubuntu 系统和 Docker Compose 为例。命令中的网络名称、密码、公网 IP 都必须替换为自己的值，&lt;strong&gt;不要把真实密码、Sakura Frp 访问密钥或 RustDesk 私钥写进公开文章&lt;/strong&gt;。&lt;/p&gt;
&lt;h2&gt;一、准备服务器&lt;/h2&gt;
&lt;p&gt;先更新系统并安装 Docker。Docker 的安装方式会随发行版变化，建议直接参考 &lt;a href=&quot;https://docs.docker.com/engine/install/&quot;&gt;Docker 官方安装文档&lt;/a&gt;，不要长期依赖来历不明的一键脚本。&lt;/p&gt;
&lt;p&gt;确认 Docker 和 Compose 可用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker --version
docker compose version
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在阿里云安全组中开放：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;端口&lt;/th&gt;
&lt;th&gt;协议&lt;/th&gt;
&lt;th&gt;用途&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;11010&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;td&gt;EasyTier 节点连接&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;11010&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;UDP&lt;/td&gt;
&lt;td&gt;EasyTier UDP 连接和更低延迟的直连尝试&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;如果只准备通过 EasyTier 虚拟网络使用 RustDesk，就不需要在云安全组中公开 &lt;code&gt;21115&lt;/code&gt;—&lt;code&gt;21117&lt;/code&gt;。若服务器启用了 UFW，可以先放行 EasyTier：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo ufw allow 11010/tcp
sudo ufw allow 11010/udp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安全组对外开放时无法限制动态家庭宽带的来源地址，因此一定要使用足够长且随机的 EasyTier 网络密码。&lt;/p&gt;
&lt;h2&gt;二、部署 EasyTier&lt;/h2&gt;
&lt;p&gt;创建工作目录：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo mkdir -p /opt/easytier
cd /opt/easytier
sudo nano compose.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;写入以下配置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;services:
  easytier:
    image: easytier/easytier:latest
    hostname: aliyun-relay
    container_name: easytier
    restart: unless-stopped
    network_mode: host
    cap_add:
      - NET_ADMIN
      - NET_RAW
    devices:
      - /dev/net/tun:/dev/net/tun
    volumes:
      - /etc/machine-id:/etc/machine-id:ro
    command: &amp;gt;
      -i 10.144.144.1
      --network-name YOUR_NETWORK_NAME
      --network-secret YOUR_STRONG_SECRET
      --private-mode true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里的参数含义：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-i 10.144.144.1&lt;/code&gt;：给服务器分配固定的 EasyTier 虚拟 IP。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--network-name&lt;/code&gt;：虚拟网络名称，所有设备必须一致。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--network-secret&lt;/code&gt;：虚拟网络密码，所有设备必须一致。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--private-mode true&lt;/code&gt;：只允许网络名称和密码匹配的节点加入。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;network_mode: host&lt;/code&gt;：让容器直接使用宿主机网络。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/dev/net/tun&lt;/code&gt;、&lt;code&gt;NET_ADMIN&lt;/code&gt; 和 &lt;code&gt;NET_RAW&lt;/code&gt;：创建虚拟网卡所需的权限。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;启动服务：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo docker compose up -d
sudo docker compose logs -f easytier
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;首次验证完成后，建议把 &lt;code&gt;latest&lt;/code&gt; 换成当时确认可用的具体版本，避免未来自动拉取到不兼容更新。&lt;/p&gt;
&lt;h2&gt;三、让电脑加入 EasyTier 网络&lt;/h2&gt;
&lt;p&gt;在远控端和被控端安装 EasyTier。官方提供 Windows、Linux、macOS 等平台的 GUI 和命令行版本，本文只展示命令行参数。&lt;/p&gt;
&lt;p&gt;被控端可以使用固定虚拟 IP：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo easytier-core \
  -i 10.144.144.2 \
  --network-name YOUR_NETWORK_NAME \
  --network-secret YOUR_STRONG_SECRET \
  -p tcp://SERVER_PUBLIC_IP:11010 \
  -p udp://SERVER_PUBLIC_IP:11010
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;远控端使用另一个地址：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo easytier-core \
  -i 10.144.144.3 \
  --network-name YOUR_NETWORK_NAME \
  --network-secret YOUR_STRONG_SECRET \
  -p tcp://SERVER_PUBLIC_IP:11010 \
  -p udp://SERVER_PUBLIC_IP:11010
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Windows 下使用 &lt;code&gt;easytier-core.exe&lt;/code&gt;，并以管理员身份启动终端。GUI 用户填写相同的网络名称、网络密码、虚拟 IP 和对等节点地址即可。&lt;/p&gt;
&lt;p&gt;检查组网状态：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;easytier-cli peer
easytier-cli route
ping 10.144.144.1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;peer&lt;/code&gt; 输出中的 &lt;code&gt;tunnel_proto&lt;/code&gt;、&lt;code&gt;lat_ms&lt;/code&gt; 和 &lt;code&gt;loss_rate&lt;/code&gt; 很重要：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;p2p&lt;/code&gt; 或直连路径通常延迟最低。&lt;/li&gt;
&lt;li&gt;通过服务器转发时，延迟大致由“本地到服务器”和“服务器到对端”两段路径共同决定。&lt;/li&gt;
&lt;li&gt;丢包明显时，不要只盯着平均延迟；远程桌面的卡顿往往先由抖动和丢包触发。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;四、部署 RustDesk Server OSS&lt;/h2&gt;
&lt;p&gt;在服务器上创建 RustDesk 工作目录：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo mkdir -p /opt/rustdesk/data
cd /opt/rustdesk
sudo nano compose.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;写入：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;services:
  hbbs:
    image: rustdesk/rustdesk-server:latest
    container_name: hbbs
    command: hbbs -r 10.144.144.1:21117
    volumes:
      - ./data:/root
    network_mode: host
    depends_on:
      - hbbr
    restart: unless-stopped

  hbbr:
    image: rustdesk/rustdesk-server:latest
    container_name: hbbr
    command: hbbr
    volumes:
      - ./data:/root
    network_mode: host
    restart: unless-stopped
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;hbbs&lt;/code&gt; 默认使用 &lt;code&gt;21116&lt;/code&gt; 提供 ID 注册和连接服务，并使用 &lt;code&gt;21115/TCP&lt;/code&gt; 进行 NAT 类型测试。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hbbr&lt;/code&gt; 默认使用 &lt;code&gt;21117/TCP&lt;/code&gt; 提供中继服务。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-r 10.144.144.1:21117&lt;/code&gt; 明确告诉客户端中继服务位于服务器的 EasyTier 虚拟 IP。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;./data:/root&lt;/code&gt; 用于持久化服务器密钥和数据库，迁移或备份时不能漏掉。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;启动：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo docker compose up -d
sudo docker compose ps
sudo docker compose logs --tail=100 hbbs hbbr
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;读取 RustDesk 公钥：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo cat /opt/rustdesk/data/id_ed25519.pub
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;id_ed25519.pub&lt;/code&gt; 可以填写到客户端；同目录下的私钥绝不能公开。&lt;/p&gt;
&lt;p&gt;如果使用 UFW，并且只允许 EasyTier 网段访问 RustDesk：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo ufw allow from 10.144.144.0/24 to any port 21115:21117 proto tcp
sudo ufw allow from 10.144.144.0/24 to any port 21116 proto udp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;云安全组仍然只开放 &lt;code&gt;11010/TCP&lt;/code&gt; 和 &lt;code&gt;11010/UDP&lt;/code&gt;。RustDesk 官方还列出了 &lt;code&gt;21118&lt;/code&gt;、&lt;code&gt;21119&lt;/code&gt; 两个 Web 客户端端口；本文不使用 Web 客户端，因此保持关闭。&lt;/p&gt;
&lt;h2&gt;五、配置 RustDesk 客户端&lt;/h2&gt;
&lt;p&gt;远控端和被控端都要先接入 EasyTier，并确认能够访问 &lt;code&gt;10.144.144.1&lt;/code&gt;。然后打开 RustDesk：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;进入“设置”→“网络”。&lt;/li&gt;
&lt;li&gt;解锁网络设置。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ID Server&lt;/code&gt; 填写 &lt;code&gt;10.144.144.1:21116&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Relay Server&lt;/code&gt; 填写 &lt;code&gt;10.144.144.1:21117&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Key&lt;/code&gt; 填写 &lt;code&gt;id_ed25519.pub&lt;/code&gt; 的完整内容。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;API Server&lt;/code&gt; 留空；开源版不需要它。&lt;/li&gt;
&lt;li&gt;保存后检查客户端底部是否显示“就绪”。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;RustDesk 官方说明中继地址通常可以由客户端推断，但这里显式填写，方便确认所有流量都使用预期的 EasyTier 地址。&lt;/p&gt;
&lt;p&gt;接下来使用另一台设备的 RustDesk ID 发起连接。测试时同时观察：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;easytier-cli peer
sudo docker compose -f /opt/rustdesk/compose.yml logs -f hbbs hbbr
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果 RustDesk 日志显示使用了 &lt;code&gt;hbbr&lt;/code&gt;，说明本次连接经过 RustDesk 中继；如果双方建立了直连，则服务器主要负责 ID 和连接协调。&lt;/p&gt;
&lt;h2&gt;六、没有公网 IP 时，用 Sakura Frp 暴露 EasyTier&lt;/h2&gt;
&lt;p&gt;如果没有这台带独立公网 IP 的服务器，原来的 Sakura Frp 方案仍然能用，而且没有必要把 RustDesk 的每个端口都单独映射出去。只暴露 EasyTier 入口，再让 RustDesk 走虚拟网络即可。&lt;/p&gt;
&lt;p&gt;在 Sakura Frp 面板中创建 TCP 隧道：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;配置项&lt;/th&gt;
&lt;th&gt;值&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;隧道类型&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;本地 IP&lt;/td&gt;
&lt;td&gt;&lt;code&gt;127.0.0.1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;本地端口&lt;/td&gt;
&lt;td&gt;&lt;code&gt;11010&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;节点&lt;/td&gt;
&lt;td&gt;优先选择距离双方近、跨网表现稳定的节点&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;远程端口&lt;/td&gt;
&lt;td&gt;使用平台分配或允许设置的端口&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;在运行 EasyTier 固定节点的设备上启动 Sakura Frp 客户端。可以使用官方启动器，也可以按面板给出的隧道 ID 启动：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./frpc -f &apos;YOUR_ACCESS_TOKEN:YOUR_TUNNEL_ID&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其他设备把 EasyTier 的对等节点地址改为：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;easytier-core \
  -i 10.144.144.2 \
  --network-name YOUR_NETWORK_NAME \
  --network-secret YOUR_STRONG_SECRET \
  -p tcp://SAKURA_NODE_HOST:REMOTE_PORT
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;需要尝试 UDP 时，再单独创建一条指向本地 &lt;code&gt;11010/UDP&lt;/code&gt; 的 UDP 隧道，并增加一个 &lt;code&gt;udp://&lt;/code&gt; 对等节点地址。TCP 路径通常更容易建立，但遇到丢包时可能产生队头阻塞；UDP 的实时性可能更好，实际效果必须按线路测试。&lt;/p&gt;
&lt;p&gt;Sakura Frp 的“端口导出”功能还会尝试 NAT 打洞，但官方文档也明确提示：NAT 类型不合适时很可能不可用。它可以作为优化项，不能代替稳定的中转路径。&lt;/p&gt;
&lt;h2&gt;七、延迟不理想时怎么排查&lt;/h2&gt;
&lt;h3&gt;1. 先测基础网络&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;ping SERVER_PUBLIC_IP
ping 10.144.144.1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;比较公网 IP 和 EasyTier 虚拟 IP 的延迟与丢包。如果虚拟网络明显更差，先检查 EasyTier 实际选择的协议和路径。&lt;/p&gt;
&lt;h3&gt;2. 确认是否发生了双重中转&lt;/h3&gt;
&lt;p&gt;最差的情况可能是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;远控端 → EasyTier 中转 → 被控端 → RustDesk 中继 → 远控端
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;EasyTier 和 RustDesk 都可能在直连失败时启用中继。先通过 &lt;code&gt;easytier-cli peer&lt;/code&gt; 判断 EasyTier 是否直连，再看 &lt;code&gt;hbbr&lt;/code&gt; 日志确认 RustDesk 是否使用中继。能直连时不要强制所有流量经过 &lt;code&gt;hbbr&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;3. 选择更合适的节点地域&lt;/h3&gt;
&lt;p&gt;中转服务器不一定离其中一方越近越好。双方跨运营商时，应分别测试到服务器的延迟、抖动和晚高峰丢包，选择总路径更均衡的地域。&lt;/p&gt;
&lt;h3&gt;4. 调整 RustDesk 画质&lt;/h3&gt;
&lt;p&gt;网络路径优化后，编码参数仍会影响体验。弱网下适当降低分辨率、帧率和图像质量，往往比盲目追求高码率更稳定。&lt;/p&gt;
&lt;h3&gt;5. 检查服务器资源和流量&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;docker stats
ip -s link
df -h
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2 核 2 GiB 对少量个人连接通常够用，但同时中继多路高分辨率画面时，带宽、CPU 和流量配额都可能成为瓶颈。&lt;/p&gt;
&lt;h2&gt;八、安全和维护&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;EasyTier 网络名称不要使用常见词，网络密码使用随机长字符串。&lt;/li&gt;
&lt;li&gt;不要公开 Sakura Frp 访问密钥、隧道 ID、RustDesk 私钥或真实服务器管理端口。&lt;/li&gt;
&lt;li&gt;RustDesk 的 &lt;code&gt;id_ed25519.pub&lt;/code&gt; 是公钥，可以分发；&lt;code&gt;id_ed25519&lt;/code&gt; 是私钥，必须妥善备份并限制权限。&lt;/li&gt;
&lt;li&gt;不使用 RustDesk Web 客户端时，不开放 &lt;code&gt;21118&lt;/code&gt; 和 &lt;code&gt;21119&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;定期备份 &lt;code&gt;/opt/rustdesk/data&lt;/code&gt; 和两份 Compose 配置。&lt;/li&gt;
&lt;li&gt;升级前记录当前镜像版本，先拉取镜像并查看变更，再执行 &lt;code&gt;docker compose up -d&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;云服务器有独立公网 IP 不代表所有端口都应该暴露；能只通过 EasyTier 访问的服务，就不要直接开放到公网。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;最后&lt;/h2&gt;
&lt;p&gt;这次折腾的起点只是“向日葵太卡了”，最后却把以前为游戏联机准备的 EasyTier、Sakura Frp 和现在的 RustDesk 串成了一套完整方案。&lt;/p&gt;
&lt;p&gt;没有公网 IP 时，Sakura Frp 可以帮助暴露 EasyTier 入口；有独立公网 IP 后，云服务器可以直接成为固定组网节点，同时承载 RustDesk 的 ID 和中继服务。整个方案并不能保证任何网络下都获得最低延迟，但它把连接路径、服务器位置和带宽控制权拿回了自己手里，后续出现问题也有明确的排查方向。&lt;/p&gt;
&lt;h2&gt;参考资料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://easytier.cn/guide/installation.html&quot;&gt;EasyTier：安装命令行程序&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://easytier.cn/guide/network/quick-networking.html&quot;&gt;EasyTier：快速组网&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://easytier.cn/guide/network/host-public-server.html&quot;&gt;EasyTier：搭建共享节点&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://rustdesk.com/docs/en/self-host/rustdesk-server-oss/docker/&quot;&gt;RustDesk Server OSS：Docker 部署&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://rustdesk.com/docs/en/self-host/client-configuration/&quot;&gt;RustDesk：客户端配置&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://doc.natfrp.com/frpc/export-port.html&quot;&gt;Sakura Frp：端口导出&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://doc.natfrp.com/frpc/manual.html&quot;&gt;Sakura Frp：frpc 用户手册&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item></channel></rss>