一个简单高效的 WebSocket 隧道库和命令行工具,使用 Go 语言编写。
# 构建服务器
go build -o wstun-server ./cmd/wstun-server
# 构建客户端
go build -o wstun-client ./cmd/wstun-client
# 或同时构建两者
go build ./cmd/wstun-server ./cmd/wstun-client
创建服务器配置文件 server.yaml:
# 服务器监听地址(必填)
listen_addr: ":8080"
# 服务定义目录(必填)
services_dir: "./services"
# TLS 证书(可选,两者必须同时设置)
# tls_cert: "/path/to/cert.pem"
# tls_key: "/path/to/key.pem"
# 日志级别(可选,默认: "info")
# 有效值: "debug", "info", "warn", "error"
log_level: "info"
# 最大并发连接数(可选,0 = 无限制)
max_connections: 0
在 services 目录下创建服务定义文件,例如 services/my-service.yaml:
# 服务认证令牌(必填)
service_token: "your-secret-token-here"
# 服务监听地址(必填)
# 客户端连接后,此地址将暴露该服务
listen_addr: ":9000"
服务名称由文件名决定(去掉 .yaml 扩展名)。
./wstun-server -c server.yaml
创建客户端配置文件 client.yaml:
# 服务器 WebSocket URL(必填,仅支持 ws:// 或 wss://)
server_url: "ws://localhost:8080"
# 服务名称(必填,与服务器端服务定义文件名一致)
service_name: "my-service"
# 服务认证令牌(必填,必须与服务器端一致)
service_token: "your-secret-token-here"
# 目标地址(必填,客户端将转发到此本地地址)
target_addr: "localhost:8080"
# 跳过 TLS 证书验证(可选,默认: false)
# 警告:仅用于测试!
tls_insecure: false
# 重连间隔(可选,默认: 5s)
reconnect_interval: 5s
# 最大重连次数(可选,0 = 无限制)
max_reconnect_attempts: 0
# 日志级别(可选,默认: "info")
log_level: "info"
# WebSocket 心跳配置(可选)
ping_interval: 30s # Ping 发送间隔(默认: 30s)
pong_timeout: 60s # 等待 Pong 回应超时(默认: 60s)
write_timeout: 10s # 写操作超时(默认: 10s)
./wstun-client -c client.yaml
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
listen_addr | string | ✅ | - | 服务器监听地址,例如 :8080 |
services_dir | string | ✅ | - | 服务定义文件目录路径 |
tls_cert | string | ❌ | - | TLS 证书文件路径 |
tls_key | string | ❌ | - | TLS 私钥文件路径 |
log_level | string | ❌ | "info" | 日志级别:debug/info/warn/error |
max_connections | int | ❌ | 0 | 最大并发连接数(0 = 无限制) |
retry_attempts | int | ❌ | 0 | 连接请求最大重试次数(0 = 不重试) |
retry_interval | duration | ❌ | 5s | 初始重试间隔 |
retry_max_interval | duration | ❌ | 60s | 最大重试间隔(指数退避上限) |
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
server_url | string | ✅ | - | WebSocket 服务器 URL(ws:// 或 wss://) |
service_name | string | ✅ | - | 服务名称(字母数字、连字符、下划线,最多 255 字符) |
service_token | string | ✅ | - | 服务认证令牌 |
target_addr | string | ✅ | - | 本地目标服务地址 |
tls_insecure | bool | ❌ | false | 跳过 TLS 验证(仅测试用) |
reconnect_interval | duration | ❌ | 5s | 重连间隔 |
max_reconnect_attempts | int | ❌ | 0 | 最大重连次数(0 = 无限制) |
log_level | string | ❌ | "info" | 日志级别:debug/info/warn/error |
ping_interval | duration | ❌ | 30s | WebSocket Ping 发送间隔 |
pong_timeout | duration | ❌ | 60s | 等待 Pong 回应的超时时间,超时则关闭连接 |
write_timeout | duration | ❌ | 10s | WebSocket 写操作超时时间 |
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
service_token | string | ✅ | 服务认证令牌,客户端必须提供相同令牌 |
listen_addr | string | ✅ | 服务监听地址,例如 :9000 或 0.0.0.0:9000 |
场景:将本地运行的 HTTP 服务器(端口 8080)通过 WebSocket 隧道暴露到远程服务器。
server.yaml):listen_addr: ":8080"
services_dir: "./services"
log_level: "info"
services/web-app.yaml):service_token: "web-app-secret-token-xyz"
listen_addr: ":9000"
./wstun-server -c server.yaml
# 服务现在监听在 :8080(WebSocket)和 :9000(暴露的服务)
client.yaml):server_url: "ws://your-server.com:8080"
service_name: "web-app"
service_token: "web-app-secret-token-xyz"
target_addr: "localhost:8080" # 本地 HTTP 服务器
log_level: "info"
./wstun-client -c client.yaml
现在访问 http://your-server.com:9000 即可访问本地的 HTTP 服务。
场景:在不稳定的网络环境下,启用服务器重试机制提高连接可靠性。
服务器配置 (server.yaml):
listen_addr: ":8080"
services_dir: "./services"
log_level: "info"
# 服务器重试配置(可选)
retry_attempts: 3 # 连接请求失败时最多重试3次
retry_interval: 5s # 初始重试间隔5秒
retry_max_interval: 60s # 最大重试间隔60秒(指数退避上限)
重试工作原理:
CONNECT:[ULID]使用建议:
retry_attempts: 0):保持与旧版本兼容,不增加延迟retry_interval 和 retry_max_intervalopenssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes
server.yaml):listen_addr: ":8443"
services_dir: "./services"
tls_cert: "cert.pem"
tls_key: "key.pem"
log_level: "info"
client.yaml):server_url: "wss://your-server.com:8443" # 使用 wss:// 协议
service_name: "web-app"
service_token: "web-app-secret-token-xyz"
target_addr: "localhost:8080"
tls_insecure: true # 仅用于自签名证书测试
log_level: "info"
一个服务器可以管理多个服务:
服务定义 1 (services/api.yaml):
service_token: "api-token-123"
listen_addr: ":9001"
服务定义 2 (services/database.yaml):
service_token: "db-token-456"
listen_addr: ":9002"
客户端 1 - 转发 API 服务:
server_url: "ws://server.com:8080"
service_name: "api"
service_token: "api-token-123"
target_addr: "localhost:3000"
客户端 2 - 转发数据库服务:
server_url: "ws://server.com:8080"
service_name: "database"
service_token: "db-token-456"
target_addr: "localhost:5432"
wstun 使用双连接模型:
控制连接(Control Connection)
WAITING 消息表示就绪CONNECT:[ULID] 通知新连接数据连接(Data Connection)
HANDLE:[ULID] 认领连接客户端 服务器 | | |-- WAITING --------->| (1) 建立控制连接 | | |<-- CONNECT:[ULID] --| (2) 服务器通知新连接 | | |-- HANDLE:[ULID] --->| (3) 建立数据连接 | | |<=== 二进制数据 ====>| (4) 双向数据传输 | |
在 Go 项目中作为库使用:
package main
import (
"context"
"io"
"log"
"net/http"
"cnb.cool/yankeguo/wstun"
"github.com/gorilla/websocket"
)
func main() {
// 服务器端示例
serverExample()
// 客户端示例
clientExample()
}
func serverExample() {
// 加载服务配置
services, err := wstun.LoadAllServices("./services")
if err != nil {
log.Fatal(err)
}
// 创建认证函数
authFunc := func(serviceName, serviceToken string) bool {
service, ok := services[serviceName]
if !ok {
return false
}
return service.ServiceToken == serviceToken
}
// 创建服务器协议处理器
sp := wstun.NewServerProtocol(authFunc)
sp.DataHandler = func(incoming io.ReadWriteCloser, outgoing *websocket.Conn) {
// 处理双向数据传输
// incoming: 原始 TCP 连接
// outgoing: 到客户端的 WebSocket
}
// 设置 HTTP 处理器
http.HandleFunc("/", sp.HandleWebSocket)
log.Fatal(http.ListenAndServe(":8080", nil))
}
func clientExample() {
// 创建客户端协议处理器
cp := wstun.NewClientProtocol(
"ws://server:8080",
"my-service",
"my-token",
)
// 设置连接处理器
cp.ConnectHandler = func(ctx context.Context, connID string) (io.ReadWriteCloser, error) {
// 连接到本地目标服务
return net.Dial("tcp", "localhost:8080")
}
// 启动客户端(阻塞直到连接关闭)
if err := cp.Start(); err != nil {
log.Fatal(err)
}
}
# 运行所有测试
go test -v ./...
# 运行竞态检测
go test -race ./...
# 运行特定测试
go test -v -run TestProtocolFlow
# 生成覆盖率报告
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out
# 格式化代码
go fmt ./...
# 静态分析
go vet ./...
# 使用 golint(需要安装)
golint ./...
wstun/ ├── cmd/ │ ├── wstun-server/ # 服务器命令行工具 │ └── wstun-client/ # 客户端命令行工具 ├── examples/ # 配置示例 ├── openspec/ # OpenSpec 文档 ├── *.go # 库实现 ├── *_test.go # 单元测试 └── README.md # 本文件
tls_insecure: truemax_connections 限制max_reconnect_attempts 防止无限重连reconnect_interval 实现退避策略max_connectionslog_level: "warn" 或 "error" 减少日志输出# 推荐的监控指标
- 活动连接数
- 连接建立/关闭速率
- 数据传输速率
- 重连次数
- 错误率
A: 检查以下几点:
A: 可能的原因:
reconnect_intervaltarget_addrmax_connectionsA: 设置详细日志:
log_level: "debug"
这将显示所有协议消息和连接事件。
A: 当前版本仅支持 TCP over WebSocket。UDP 支持可能在未来版本添加。
A: 是的!项目已经过全面测试,包括:
在典型硬件上(4 核 CPU,8GB RAM)的性能表现:
github.com/gorilla/websocket v1.5.3 - WebSocket 实现github.com/oklog/ulid/v2 v2.1.1 - ULID 生成gopkg.in/yaml.v3 v3.0.1 - YAML 解析请查看 LICENSE 文件了解详情。
欢迎贡献!请随时提交 issues 和 pull requests。
在提交 PR 之前,请确保:
go test ./...)go test -race ./...)go fmt ./...)感谢所有贡献者和使用 wstun 的用户!
ClientOptions 补充字段注释(ping_interval、pong_timeout、write_timeout)LoadAllServices 简化文件扩展名处理(使用 filepath.Ext)cmd 中无意义的 fmt.Sprintf 调用项目状态: 生产就绪 🚀 | 测试覆盖: 全面 ✅ | 竞态检测: 通过 ✅