引言
在 NPS 项目概述中,我们对这款强大的内网穿透工具进行了宏观的介绍。本篇文章将深入 NPS 的服务端(Server)核心,从 nps/server/server.go
文件入手,详细剖析其架构设计、任务管理、流量处理以及系统监控等关键功能。理解 server.go
的运作机制,是掌握 NPS 核心原理的基石。
server.go
:服务端的"大脑"
server.go
文件是 NPS 服务端的启动入口和主要协调者。它负责初始化各项服务、管理客户端连接、调度隧道任务以及收集系统运行状态。
核心组件概览
在 server.go
中,我们可以看到几个关键的全局变量和结构:
Bridge *bridge.Bridge
:这是服务端与客户端之间通信的核心桥梁。它负责维护客户端连接、处理客户端发送的指令以及转发数据。RunList sync.Map
:一个并发安全的 Map,用于存储当前正在运行的所有隧道(proxy.Service
实例)。通过sync.Map
,NPS 能够高效地管理和访问多个并发的隧道服务。once sync.Once
:用于确保某些初始化操作(如flowSession
)只执行一次。
NPS 服务端核心组件架构可以用下图表示:
初始化与任务加载
服务端的启动从初始化开始:
init()
函数:简单地初始化RunList
为一个空的sync.Map
。InitFromCsv()
函数:- 负责从持久化存储(通过
file.GetDb().JsonDb
访问,通常是 JSON 文件)中加载预设的隧道任务。 - 如果配置了
public_vkey
(公共验证密钥),会创建一个特殊的公共客户端,允许未注册的客户端通过此密钥连接。 - 遍历所有已存储的隧道任务,如果任务状态为
true
(启用),则调用AddTask()
函数将其启动。
- 负责从持久化存储(通过
服务端核心循环:DealBridgeTask()
DealBridgeTask()
是服务端的一个关键 goroutine,它在一个无限循环中监听来自 Bridge
的各种事件通道,实现对隧道和客户端的动态管理:
Bridge.OpenTask
:当有新的隧道任务需要启动时,从该通道接收任务并调用AddTask()
。Bridge.CloseTask
:当有隧道任务需要停止时,从该通道接收任务 ID 并调用StopServer()
。Bridge.CloseClient
:当客户端断开连接或需要被移除时,从该通道接收客户端 ID,并调用DelTunnelAndHostByClientId()
删除该客户端关联的所有隧道和主机。Bridge.SecretChan
:处理特殊的"秘密连接"。如果连接的密码与某个启用状态的隧道匹配,则会启动一个proxy.NewBaseServer
来处理该连接。
DealBridgeTask()
的处理流程可以用下图表示:
服务端启动流程:StartNewServer()
StartNewServer()
是 NPS 服务端的主启动函数,它负责协调各项服务的启动:
- 初始化
Bridge
:根据配置的桥接端口 (bridgePort
)、类型 (bridgeType
) 和断开连接策略 (bridgeDisconnect
),创建一个bridge.Bridge
实例。 - 启动
Bridge
监听:在一个独立的 goroutine 中启动Bridge.StartTunnel()
,使其开始监听客户端连接。 - 启动 P2P 服务:如果
p2p_port
在配置中指定,NPS 会启动多个 P2P 服务器实例,用于支持点对点连接。 - 启动任务处理协程:启动
DealBridgeTask()
goroutine,开始处理来自Bridge
的任务事件。 - 启动客户端流量处理:启动
dealClientFlow()
goroutine,定期处理客户端的流量数据。 - 启动主代理服务:根据传入的隧道配置 (
cnf.Mode
),通过NewMode()
函数实例化对应的proxy.Service
,并在独立的 goroutine 中启动它。这个proxy.Service
实例会被存储在RunList
中。
服务端启动流程可以用下图表示:
代理模式工厂:NewMode()
NewMode()
函数是 NPS 服务端实现多协议支持的关键。它根据隧道配置中的 Mode
字段,返回不同的 proxy.Service
实现,每种实现对应一种代理模式:
tcp
/file
:通过proxy.NewTunnelModeServer(proxy.ProcessTunnel, ...)
创建,用于标准的 TCP 流量转发。socks5
:通过proxy.NewSock5ModeServer(...)
创建,提供 SOCKS5 代理服务。httpProxy
:通过proxy.NewTunnelModeServer(proxy.ProcessHttp, ...)
创建,用于 HTTP 代理。tcpTrans
:通过proxy.NewTunnelModeServer(proxy.HandleTrans, ...)
创建,用于 TCP 透明传输。udp
:通过proxy.NewUdpModeServer(...)
创建,用于 UDP 流量转发。webServer
:这是一个特殊的模式,它会初始化数据库中的任务,并启动一个httpHostServer
类型的隧道,然后通过proxy.NewWebServer(...)
创建 Web 管理界面服务。httpHostServer
:通过proxy.NewHttp(...)
创建,用于处理 HTTP/HTTPS 域名解析和代理。
隧道任务生命周期管理
server.go
提供了一系列函数来管理隧道任务的生命周期:
AddTask(t *file.Tunnel)
:添加并启动一个新的隧道任务。它会根据任务模式进行一些预处理(如端口测试),然后通过NewMode()
实例化并启动对应的代理服务。StopServer(id int)
:停止指定 ID 的隧道服务。它会从RunList
中查找对应的服务实例,调用其Close()
方法,并更新数据库中任务的状态。StartTask(id int)
:启动一个已存在的、但当前处于停止状态的隧道任务。DelTask(id int)
:删除指定 ID 的隧道任务,如果任务正在运行,会先停止它。
客户端与流量数据处理
dealClientFlow()
:一个定时器(每分钟触发一次)协程,用于定期调用dealClientData()
。dealClientData()
:遍历所有客户端,更新它们的在线状态、最后在线时间、版本信息,并计算总的流入/流出流量。这些数据用于 Web 管理界面的展示。DelTunnelAndHostByClientId(clientId int, justDelNoStore bool)
:删除指定客户端 ID 关联的所有隧道和主机配置。DelClientConnect(clientId int)
:断开指定客户端 ID 的连接。
仪表盘数据获取:GetDashboardData()
GetDashboardData()
函数负责收集并返回用于 Web 管理界面仪表盘的数据,包括:
- NPS 版本、主机数量、客户端数量(在线/总数)。
- 总流入/流出流量。
- 各种隧道类型(TCP、UDP、SOCKS5、HTTP 代理、秘密代理、P2P)的数量。
- 桥接类型、HTTP/HTTPS 代理端口、IP 限制、流量存储间隔、P2P IP/端口、日志级别等配置信息。
- 系统资源使用情况:CPU 使用率、系统负载、内存使用率(Swap 和 Virtual Memory)、网络 I/O 和 TCP 连接数。
数据持久化:flowSession()
flowSession()
是一个独立的 goroutine,它会根据配置的间隔(默认为 1 分钟)定时将主机、任务、客户端和全局流量数据持久化到 JSON 文件中,确保数据在服务重启后不会丢失。
总结
nps/server/server.go
作为 NPS 服务端的“大脑”,通过精巧的并发设计和模块化管理,实现了强大的内网穿透功能。它不仅负责启动和协调各种代理服务,还提供了完善的任务生命周期管理、客户端连接管理、流量统计以及系统监控能力。对 server.go
的深入理解,为我们后续剖析 NPS 的具体代理实现和客户端通信奠定了基础。
在下一篇文章中,我们将继续深入 nps/server/proxy
目录,探索不同代理模式的具体实现细节。