rathole 是 Rust 写的内网穿透工具,一个 4051456 字节的 exe。它的活是把本地机器上的某个端口,通过一台有公网 IP 的服务器转出去,让外面的人能连进来。

在本地把服务端和客户端都跑起来做了一次完整实测:服务端和客户端各自起来到隧道可用 1.02 秒;通过隧道下载一个 9896267 字节的文件,耗时 3.103 秒,和直连的 3.103 秒一模一样;但每次请求的首包延迟从 1.12 毫秒涨到 3.39 毫秒。

两个 toml,一个可执行文件

# server.toml(跑在有公网 IP 的机器上)
[server]
bind_addr = "0.0.0.0:2333"
default_token = "test-token"

[server.services.web]
token = "test-token"
bind_addr = "0.0.0.0:5202"

# client.toml(跑在要暴露服务的机器上)
[client]
remote_addr = "198.2.209.42:2333"
default_token = "test-token"

[client.services.web]
token = "test-token"
local_addr = "127.0.0.1:8098"

两边的 services.web 名字必须对上(这里是 web),token 也要一致。服务端在 5202 上监听,收到连接就通过 2333 这条控制通道转给客户端,客户端再去连本地的 8098。

两边配置加起来 16 行,没有证书、没有域名、不依赖额外组件。这点比带面板的穿透方案轻很多。

从启动到隧道可用,1.02 秒

阶段耗时依据
服务端 + 客户端起到隧道可用1.02 秒端口 5202 开始接受连接
控制通道握手毫秒级日志里 handshake 与 established 同一秒
直连下载 9896267 字节3.103 秒3192269 B/s
经隧道下载同一文件3.103 秒3189399 B/s
首包延迟(直连)1.12 毫秒15 次平均
首包延迟(隧道)3.39 毫秒15 次平均
延迟差(隧道 - 直连)2.27 毫秒两跳 TCP
握手到 5202 开始监听0.3 毫秒日志时间戳相减
两份配置合计16 行server.toml + client.toml
exe 体积4051456 字节单文件,无依赖
连接被拒错误码10061客户端未起来时

1.02 秒里绝大部分是进程启动和 TCP 连接的时间。日志里从握手到 5202 开始监听只隔了 0.3 毫秒,几乎可以忽略。

吞吐不受影响,延迟多两跳

同一个文件、同一台机器,直连和走隧道都是 3.103 秒。原因很简单:瓶颈在文件服务那一端(限速 3 MB/s 左右),隧道不是瓶颈,加一层转发不会让它变慢。

真正变的是延迟:1.12 毫秒变成 3.39 毫秒,多了 2.27 毫秒。这 2.27 毫秒就是多出来的两跳 TCP——请求先进服务端的 5202,再转给客户端,客户端连本地那个服务,回来还要走一遍。

这个开销在局域网里看不出来,跨公网时会被 RTT 放大。如果应用对延迟敏感(比如数据库连接、远程桌面),穿透方案的延迟要先算清楚:隧道本身不创造带宽,它只是把两段网络接起来。

握手过程长什么样

INFO rathole::server: Listening at 127.0.0.1:2333
INFO connection{addr=127.0.0.1:49305}: rathole::server: Try to handshake a control channel
INFO connection{addr=127.0.0.1:49305}: rathole::server: Control channel established service=web
INFO connection{addr=127.0.0.1:49305}:handle{service=web}:run_tcp_connection_pool:
     rathole::server: Listening at 127.0.0.1:5202

INFO handle{service=web}: rathole::client: Starting 4b5e57f6eb2f42b9039b3d1e13929295f231749c...
INFO handle{service=web}:run: rathole::client: Control channel established

这个顺序说明 5202 能不能连,取决于客户端有没有连上:从握手到 5202 开始监听只隔了 0.3 毫秒,但前提是那一次握手成功。客户端没起来时,5202 根本不监听,外部连过去直接是连接被拒(10061),不是超时。

所以看到「连接被拒」先查客户端日志,别在服务端上翻防火墙。

部署前要先确认端口

最容易卡住的地方不是配置,是端口。实测时先在一台服务器上部署服务端,客户端始终连不上——那条链路的防火墙只放行了 1 个端口,2333 和 5202 都在外面。

如果 2333 不通,客户端会停在握手之前,日志里只有一行 Starting,永远等不到 Control channel established;如果只有 5202 不通,客户端日志完全正常,但从外面访问会超时。这两种失败长得完全不同,先分清是哪一个。

顺序应该是:先用 ss -lntp 确认监听,再从另一台机器用 curl 确认能连进来,两步都过了再写配置。

它适合什么场景

手上有一台公网服务器、又想把家里的 NAS 或开发机暴露出去,rathole 够用:单文件 4051456 字节、16 行配置、没有面板服务。相比之下 frp 功能更全(带面板和插件),代价是配置更长。

它不做网关也不做负载均衡——没有健康检查、没有多实例分发。定位就是一条点对点的隧道,一个本地端口对上一个公网端口。