跳到主要内容
版本:4.2

Dmtp内网穿透

简介

Dmtp内网穿透(Relay) 是 TouchSocket Pro 提供的一项强大功能,基于 Dmtp 协议实现安全、高效的内网穿透服务。它允许外部网络通过中继服务器访问位于内网的服务,无需复杂的路由配置或公网 IP。

主要特性

  • TCP/UDP 双协议:同时支持 TCP 和 UDP 协议的内网穿透
  • 端口映射:将内网服务端口映射到中继服务器的公网端口
  • 鉴权控制:支持通过元数据(Metadata)在服务端对注册请求进行鉴权
  • 注销事件:支持监听注册取消事件(主动注销或客户端断线均会触发)
  • 崩溃恢复:通过持久化 RelayId,进程崩溃重启后可自动预注销服务端残留注册
  • 高性能:基于 Dmtp 协议,采用引用计数和内存池优化,减少内存分配

应用场景

  • 远程访问内网 Web 服务
  • 物联网设备远程管理
  • 开发环境临时分享
  • 跨网络的服务调用
  • 企业内外网服务互通

快速开始

安装

提示

Dmtp内网穿透功能需要 TouchSocketPro 商业版授权。

Install-Package TouchSocketPro.Dmtp

基本架构

Dmtp内网穿透系统由三个主要组件组成:

  1. 中继服务器(Relay Server):部署在有公网 IP 的服务器上,接收外部连接并转发到内网
  2. 内网客户端(Relay Client):运行在内网环境,连接到中继服务器并注册端口映射
  3. 外部客户端(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 注意事项
  • UDP 是无连接协议,IUdpRelayRegistration 没有 ConnectionCount 属性
  • 中继服务器为每个外部 UDP 端点(IP+Port)维护一个会话,会话在超时或内网服务响应后自动清理
  • UDP 穿透的延迟由两段链路叠加:外部客户端→中继服务器 和 中继服务器→内网服务

崩溃恢复与 RelayId 持久化

问题背景

每次调用 RegisterTcpRelayAsyncRegisterUdpRelayAsync 成功后,中继服务器会在对应端口上启动监听。如果内网客户端进程崩溃(非正常退出),服务端的监听不会自动清除,残留的注册会持续占用端口。

当进程重启后再次尝试注册同一个端口时,会收到端口已占用的错误,无法完成注册。

解决方案:持久化 RelayId + 预注销

核心流程:

  1. 每次注册成功后,将 registration.RelayId 写入持久化存储(文件、数据库等)
  2. 下次启动时,先读取持久化的 RelayId
  3. 如果 RelayId 不为空,在新注册之前调用 UnregisterRelayAsync(lastRelayId) 清除服务端残留
  4. 无论预注销是否成功,均不清除持久化的 RelayId(预注销失败说明服务端已自行清理,不影响流程)
  5. 新注册成功后,以新的 RelayId 覆盖持久化值(幂等,无需区分首次还是重启)
🔄 正在加载代码...
生产建议

示例中使用本地文件持久化 RelayId,生产环境建议替换为数据库、Redis 等更可靠的存储方案,尤其是多实例部署时需要保证 RelayId 的唯一性和可用性。


内网测试服务

以下是用于演示的简单 TCP 和 UDP 回显服务,实际使用时替换为真实的内网服务地址即可。

🔄 正在加载代码...

完整演示

TCP 穿透完整示例

🔄 正在加载代码...

UDP 穿透完整示例

🔄 正在加载代码...

注意事项

  1. 端口冲突:确保中继服务器上的注册端口未被其他进程占用
  2. 防火墙配置:确保中继服务器的公网端口对外网开放
  3. 安全性:生产环境务必配置 VerifyToken 和元数据鉴权,避免未授权注册
  4. 崩溃恢复:务必持久化 RelayId 并在重启后执行预注销,否则进程崩溃后无法重新注册同一端口
  5. 带宽消耗:所有流量都经过中继服务器,注意服务器带宽限制
  6. 重连策略:不要对中继客户端使用 UseReconnection,重连后 actor 状态保留但服务端 session 已更换;建议使用定期健康检查,检测到下线时完整重启(含重新注册)