如何将本地 WebSocket 服务暴露至公网:使用内网穿透实现远程通信
WebSocket 开发中的网络限制与解决方案
WebSocket 是一种支持全双工通信的网络协议,广泛应用于实时聊天、数据推送、协同编辑等场景。在开发过程中,开发者通常会在本地启动服务进行调试。然而,默认情况下这类服务仅限局域网访问,无法被外部设备或远程团队成员连接,严重制约了联调效率和测试覆盖范围。
为解决这一问题,可以借助内网穿透工具将本地运行的服务映射到公网。本文将以 Java + Netty 构建的 WebSocket 服务为例,结合轻量级穿透工具,实现安全稳定的远程接入。
搭建基于 Spring Boot 的 WebSocket 服务端
首先准备基础开发环境:
- JDK 1.8 或更高版本
- 构建工具:Maven
- 开发 IDE:IntelliJ IDEA
项目中引入一个基于 Netty 封装的 HTTP 框架,替代默认的 Tomcat 容器以避免端口冲突:
<dependency>
<groupId>io.github.fzdwx</groupId>
<artifactId>sky-http-springboot-starter</artifactId>
<version>0.10.6</version>
</dependency>
注意:需移除或注释掉原有的 spring-boot-starter-web 依赖,防止嵌入式 Web 服务器与 Netty 冲突。
编写 WebSocket 接口逻辑
创建一个 REST 控制器,用于升级 HTTP 连接为 WebSocket 协议:
@GetMapping("/eth/getConnect")
public void establishConnection(HttpServerRequest request) {
request.upgradeToWebSocket(ws -> {
// 连接建立时发送欢迎消息
ws.mountOpen(handler -> {
ws.send("连接成功,开始聊天吧!");
});
// 监听客户端文本消息
ws.mountText(message -> {
System.out.println("收到消息: " + message);
// 模拟服务端回复(从控制台输入)
Scanner scanner = new Scanner(System.in);
System.out.print("回复: ");
String reply = scanner.nextLine();
ws.send(reply);
});
});
}
启动应用后,默认监听 9999 端口。若看到日志输出表明 Netty 已就绪,则服务已正常运行。
配置内网穿透,发布服务至公网
为了让外网设备能够访问该服务,需要将本地 9999 端口映射为公网可访问地址。以下步骤介绍如何通过命令行工具完成隧道配置。
安装客户端(国内用户推荐):
curl -L https://www.cpolar.com/static/downloads/install-release-cpolar.sh | sudo bash
验证安装是否成功:
cpolar version
登录官网注册账号,获取认证令牌并执行绑定:
cpolar authtoken your_auth_token_here
设置开机自启并启动后台服务:
sudo systemctl enable cpolar
sudo systemctl start cpolar
sudo systemctl status cpolar
服务启动后,访问 http://127.0.0.1:9200 打开管理界面,新建一条 TCP 类型的隧道,将公网请求转发至本地 127.0.0.1:9999。
获取公网地址并测试连接
在隧道列表中复制生成的公网连接信息,格式如:tcp://4.tcp.cpolar.cn:12345
此时,任何位于公网的客户端均可通过此地址连接本地 WebSocket 服务。
使用 Go 编写客户端进行远程测试
选择 Go 语言作为客户端示例,展示跨语言互通能力。
安装 Gorilla WebSocket 库:
go get github.com/gorilla/websocket
编写客户端代码:
package main
import (
"fmt"
"log"
"net/url"
"github.com/gorilla/websocket"
)
func main() {
// 替换为实际的公网隧道地址
serverURL := url.URL{
Scheme: "ws",
Host: "4.tcp.cpolar.cn:12345", // 公网地址
Path: "/eth/getConnect",
}
conn, _, err := websocket.DefaultDialer.Dial(serverURL.String(), nil)
if err != nil {
log.Fatal("连接失败:", err)
}
defer conn.Close()
// 接收来自服务端的消息
go func() {
for {
_, msg, _ := conn.ReadMessage()
fmt.Println("← 收到:", string(msg))
}
}()
// 发送消息给服务端
go func() {
var input string
for {
fmt.Print("→ 发送: ")
fmt.Scanln(&input)
conn.WriteMessage(websocket.TextMessage, []byte(input))
}
}()
select {} // 阻塞主协程
}
验证双向通信
运行客户端程序后,若成功接收"连接成功"提示,说明握手完成。双方可在各自控制台输入内容,实现实时互发消息。
- 客户端发送 → 服务端控制台打印
- 服务端手动输入回复 → 客户端即时显示
整个流程无需公网 IP,不修改路由器配置,也无需复杂 DNS 设置,极大简化了远程协作流程。
关于临时地址与固定端口的建议
免费版生成的公网地址具有时效性(通常 24 小时内有效),适用于短期测试。如需长期稳定连接,建议在平台预留一个固定的 TCP 端口,并将其配置进隧道规则中,确保域名和端口不变。