前两天晚上七点多,我坐在家里用 SSH 连回公司内网服务器,敲一条 ls 命令等了差不多三秒才回显。切到远程桌面更绝望,画面像被马赛克糊过一样,拖一下窗口要半天才刷新。白天一切正常,一过六点就开始卡,每天如此,已经被折磨了一个星期。我用的就是 FRP 内网穿透,服务器端在云上,客户端在公司内网,链路本身并不复杂,问题出在晚高峰的公网链路上。
后来我把 FRP 的传输层协议从默认的 TCP 切到了 KCP,卡顿问题基本解决,SSH 和远程桌面都恢复了可用状态。加上新版本 FRP 全面转向 TOML 格式配置,网上很多教程还停留在旧版 ini 写法,照着做直接启动失败,踩了不少坑。这篇文章把整个排查过程、KCP 协议的原理、完整的 TOML 配置流程以及开启 KCP 后的新坑都梳理一遍,希望能帮到同样被内网穿透卡顿折磨的人。
1. 晚高峰卡顿到底卡在哪:TCP 在拥塞链路里有多吃亏
1.1 白天正常晚上卡,这个时间规律本身就是答案
排查网络问题,第一件事是看现象的时间规律。我的 FRP 链路搭建好之后,白天几乎感觉不到延迟,SSH 敲命令跟本机操作差不多,RDP 远程桌面也能正常办公。但一到晚上 6 点到 10 点这个时间段,延迟明显上升,丢包开始出现,远程桌面基本不可用。
这个规律直接指向公网链路拥塞。晚高峰是家庭宽带和移动网络使用量最大的时段,视频、游戏、直播都在抢占带宽,运营商骨干网和跨网互联的链路会出现明显的拥塞。FRP 服务端部署在云服务器上,家里的网络和服务器的网络往往不是同一个运营商,跨网互访在晚高峰更是重灾区。
还有一个容易忽略的因素是云服务器本身的带宽。我用的服务器是 5M 带宽,白天够用,晚上一旦出现丢包,TCP 的有效吞吐会急剧下降,5M 变成不到 1M 都有可能。
1.2 TCP 面对拥塞时的表现:老实排队,越堵越慢
理解这个问题需要回到 TCP 协议本身的特性。TCP 是面向连接的可靠传输协议,为了保证数据不丢失、不乱序,它设计了一套拥塞控制机制。当网络出现丢包时,TCP 会认为网络发生了拥塞,然后主动降低发送速率,进入拥塞避免阶段,并且每次超时重传都会让超时时间加倍,从 1 秒到 2 秒、4 秒、8 秒指数增长。
用大白话说,TCP 就像一辆在高速上遇到堵车就自动减速的大巴,它非常守规矩,为了不加剧拥堵宁愿把自己降到很慢的速度。在晚高峰这种本来就拥堵的链路上,TCP 的守规矩反而成了致命伤——丢包率稍微上升,它就把自己的发送窗口缩得很小,然后花了大量时间在等待超时重传上。
FRP 默认走的就是 TCP 协议,所以晚高峰卡顿的根源就是传输层协议在拥塞链路上的天然劣势。我做了个简单的丢包测试,使用 ping 命令连续丢 100 个包,白天的丢包率在 1% 以内,晚高峰直接到了 5% 到 8%。对于 TCP 来说,这个丢包率已经非常致命,有效吞吐量可能只剩原来的三分之一到四分之一。
1.3 远程桌面和 SSH 为什么感知最明显
不同应用对延迟和丢包的敏感度不同。SSH 是交互式协议,每一次按键都要经过完整的往返确认,网络延迟直接从按键到屏幕的反应时间体现出来。RDP 远程桌面更是吃带宽和延迟的怪兽,它需要持续传输屏幕变化的数据,一旦传输速率跟不上,画面就会降级、模糊、刷新缓慢。
这两个场景恰恰是我使用 FRP 最频繁的两个。如果你只是在内网穿透基础上跑一些文件传输或者 Web 服务,晚高峰卡顿的感知可能没有那么强,因为 HTTP 请求没有严格的实时性要求,文件传输慢一点顶多多等一会儿。但交互式应用不行,卡顿就是没法用。
1.4 排查思路:先确认是不是加密和压缩导致的计算瓶颈
在把锅甩给 TCP 之前,我还排查过另一个可能的原因——FRP 自身的加密和压缩功能。FRP 支持在传输层配置 useEncryption 和 useCompression,这两个功能会带来额外的 CPU 开销,如果两端设备性能不足,也可能造成延迟。
我检查了服务端和客户端的 CPU 使用率,发现占用率都在 5% 以下,排除了这个可能。真正的瓶颈还是链路上的丢包与延迟。判断方法也很简单:看延迟曲线是否和晚高峰的时间段完全吻合,以及丢包率是否同步上升。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. KCP 协议为什么管用:拿带宽换延迟的低层逻辑
2.1 KCP 是什么:跑在 UDP 上面的可靠传输协议
KCP 是一个基于 UDP 实现的快速可靠传输协议,它的核心思路是在 UDP 之上自己实现可靠传输机制,包括确认、重传、窗口管理,但做得比 TCP 激进得多。它的设计目标很明确——在网络状况比较差、丢包率较高的场景下,降低延迟就是一切。
FRP 从很早的版本开始就内置了 KCP 支持,这不是一个单独的插件,而是在配置里可以直接切换的选项。服务端开启 kcpBindPort 之后,客户端把 transport.protocol 设置为 kcp,FRP 就会把所有传输数据切换到基于 KCP 的 UDP 通道上。
