Dmtp内网穿透
简介
Dmtp内网穿透(Relay) 是 TouchSocket Pro 提供的一项强大功能,基于 Dmtp 协议实现安全、高效的内网穿透服务。它允许外部网络通过中继服务器访问位于内网的服务,无需复杂的路由配置或公网 IP。
主要特性
- TCP/UDP 双协议:同时支持 TCP 和 UDP 协议的内网穿透
- 端口映射:将内网服务端口映射到中继服务器的公网端口
- 鉴权控制:支持通过元数据(
Metadata)在服务端对注册请求进行鉴权 - 注销事件:支持监听注册取消事件(主动注销或客户端断线均会触发)
- 崩溃恢复:通过持久化
RelayId,进程崩溃重启后可自动预注销服务端残留注册 - 高性能:基于 Dmtp 协议,采用引用计数和内存池优化,减少内存分配
应用场景
- 远程访问内网 Web 服务
- 物联网设备远程管理
- 开发环境临时分享
- 跨网络的服务调用
- 企业内外网服务互通
快速开始
安装
Dmtp内网穿透功能需要 TouchSocketPro 商业版授权。
Install-Package TouchSocketPro.Dmtp
基本架构
Dmtp内网穿透系统由三个主要组件组成:
- 中继服务器(Relay Server):部署在有公网 IP 的服务器上,接收外部连接并转发到内网
- 内网客户端(Relay Client):运行在内网环境,连接到中继服务器并注册端口映射
- 外部客户端(External Client):通过中继服务器的公网端口访问内网服务
[外部客户端] ──→ [中继服务器:8889] ──→ [内网客户端] ──→ [内网服务:8848]
中继服务器
启动中继服务器
中继服务器通过 AddDmtpRelayService() 注入依赖,并在插件链中调用 UseDmtpRelay() 启用穿透功能。同时支持挂载注册插件(AddDmtpRelayRegisteringPlugin)和注销插件(AddDmtpRelayUnregisteringPlugin)。
关键配置说明:
| 方法 | 说明 |
|---|---|
AddDmtpRelayService() | 注册中继服务所需的依赖(必须在服务端调用) |
UseDmtpRelay() | 在插件链中启用 Dmtp 内网穿透功能 |
AddDmtpRelayRegisteringPlugin | 挂载注册事件插件,服务端鉴权、配置监听参数 |
AddDmtpRelayUnregisteringPlugin | 挂载注销事件插件,监听中继取消(主动注销或断线均触发) |
通过 e.Metadata 读取客户端携带的自定义令牌(例如 relay-secret),在注册时进行鉴权。将 e.IsPermitOperation = false 并设置 e.Message 可拒绝注册请求。
TCP 穿透
TCP 穿透适用于需要建立长连接、传输流式数据的场景,例如远程 SSH、数据库连接、Web 服务等。
注册 TCP 穿透
内网客户端连接中继服务器后,调用 RegisterTcpRelayAsync 注册端口映射。localIPHost 是内网实际服务地址,remoteIPHost 是希望在中继服务器上监听的公网地址。
参数说明:
| 参数 | 说明 |
|---|---|
localIPHost | 内网服务的实际地址(如 127.0.0.1:8848),中继客户端会代理连接到此地址 |
remoteIPHost | 中继服务器上的监听地址(如 0.0.0.0:8889),外部客户端连接此端口 |
metadata | 可选元数据,可携带鉴权令牌等自定义信息传递给服务端注册插件 |
注册成功后返回 ITcpRelayRegistration,其中 RelayId 是本次穿透的唯一标识,ConnectionCount 是当前活跃连接数。
UDP 穿透
UDP 穿透适用于对实时性要求较高、允许一定丢包的场景,例如视频流、音频通话、游戏数据、物联网遥测等。
注册 UDP 穿透
与 TCP 类似,只需将 RegisterTcpRelayAsync 替换为 RegisterUdpRelayAsync。注册成功后外部客户端向中继服务器的公网 UDP 端口发送数据,中继服务器会通过内网客户端转发到内网 UDP 服务。
- UDP 是无连接协议,
IUdpRelayRegistration没有ConnectionCount属性 - 中继服务器为每个外部 UDP 端点(IP+Port)维护一个会话,会话在超时或内网服务响应后自动清理
- UDP 穿透的延迟由两段链路叠加:外部客户端→中继服务器 和 中继服务器→内网服务
崩溃恢复与 RelayId 持久化
问题背景
每次调用 RegisterTcpRelayAsync 或 RegisterUdpRelayAsync 成功后,中继服务器会在对应端口上启动监听。如果内网客户端进程崩溃(非正常退出),服务端的监听不会自动清除,残留的注册会持续占用端口。
当进程重启后再次尝试注册同一个端口时,会收到端口已占用的错误,无法完成注册。
解决方案:持久化 RelayId + 预注销
核心流程:
- 每次注册成功后,将
registration.RelayId写入持久化存储(文件、数据库等) - 下次启动时,先读取持久化的
RelayId - 如果
RelayId不为空,在新注册之前调用UnregisterRelayAsync(lastRelayId)清除服务端残留 - 无论预注销是否成功,均不清除持久化的
RelayId(预注销失败说明服务端已自行清理,不影响流程) - 新注册成功后,以新的
RelayId覆盖持久化值(幂等,无需区分首次还是重启)
示例中使用本地文件持久化 RelayId,生产环境建议替换为数据库、Redis 等更可靠的存储方案,尤其是多实例部署时需要保证 RelayId 的唯一性和可用性。
内网测试服务
以下是用于演示的简单 TCP 和 UDP 回显服务,实际使用时替换为真实的内网服务地址即可。
完整演示
TCP 穿透完整示例
UDP 穿透完整示例
注意事项
- 端口冲突:确保中继服务器上的注册端口未被其他进程占用
- 防火墙配置:确保中继服务器的公网端口对外网开放
- 安全性:生产环境务必配置
VerifyToken和元数据鉴权,避免未授权注册 - 崩溃恢复:务必持久化
RelayId并在重启后执行预注销,否则进程崩溃后无法重新注册同一端口 - 带宽消耗:所有流量都经过中继服务器,注意服务器带宽限制
- 重连策略:不要对中继客户端使用
UseReconnection,重连后 actor 状态保留但服务端 session 已更换;建议使用定期健康检查,检测到下线时完整重启(含重新注册)