RTK Server
一个面向个人自建场景的轻量级 RTK / CORS / NTRIP 服务端,使用 Go 编写,适合部署在 Linux 服务器或 Windows 主机上,用来接收多个基站上传的 RTCM 数据,并通过 NTRIP Rev1 向多个流动站分发。
本项目重点是:
- 单体程序,部署简单
- 不依赖数据库
- 不依赖 TLS、域名、Web 框架构建链路
- 支持固定基站挂载点和最近站虚拟挂载点
- 支持自动接纳未预配置的新基站
- 自带状态 API 和可视化管理后台
主要功能
1. 基站 RTCM 接入
- 监听
9000/TCP - 基站连接后首行发送:
BASE <station_id>
- 首行之后的所有内容按 RTCM 二进制流处理
- 已预配置站点按配置绑定;未预配置站点也允许接入并自动注册
- 同一个
station_id新连接会顶掉旧连接 - 离线超过 7 天的站点会自动从列表中清理;动态站点会从运行时注册表中移除
- 实时统计:
- 在线状态
- 最后活动时间
- RTCM 实时字节率
- 总字节数
2. NTRIP Rev1 分发
- 监听
2101/TCP - 支持:
GET /<mountpoint> HTTP/1.0
GET /<mountpoint> HTTP/1.1
- 支持 Basic Auth
- 认证成功返回:
ICY 200 OK
- 支持多个 rover 同时连接同一个 mountpoint
- 支持 rover 反向发送
$GPGGA/$GNGGA
3. 虚拟挂载点 NEAREST
- rover 连接
NEAREST后,服务器等待 GGA - 从 GGA 中解析经纬度
- 在当前在线基站中用 Haversine 算法选择最近站
- 当前会话内固定选用首次最近站
4. 管理后台
默认访问:
http://127.0.0.1:8080/
后台为内置静态页面,无需 Node.js 或前端打包工具,支持响应式布局,包含:
- 总览页
- 服务运行摘要
- 基站状态
- 客户端会话
- 配置摘要
- 地图页
- 基站相对坐标位置图
- 在线 / 离线状态
- 站点详情
- 命令页
- 一键生成
curl - 一键生成
nc - 一键生成 PowerShell 测试命令
- 一键生成模拟基站上传命令
- 一键生成
- 日志页
- 在线查看最近日志
- 支持关键字和级别过滤
- 设备向导页
- UM982 接入流程
- UM960 接入流程
- NTRIP / 网络配置提示
- 常见排障说明
目录结构
rtk-server/
go.mod
main.go
config.yaml.example
internal/
api/
config/
hub/
ingest/
model/
ntrip/
util/
编译方法
Linux / macOS
go mod tidy
go build -o rtk-server .
Windows
go mod tidy
go build -o rtk-server.exe .
启动方法
- 复制配置模板:
cp config.yaml.example config.yaml
-
修改
config.yaml -
启动服务:
./rtk-server -config config.yaml
Windows:
.\rtk-server.exe -config config.yaml
配置说明
示例文件见 config.yaml.example。
server:
base_ingest_port: 9000
ntrip_port: 2101
api_port: 8080
base_stations:
- station_id: GD_SZ_001
mountpoint: GD_SZ_001
lat: 22.000000
lon: 113.000000
height: 10.0
rovers:
- username: rover
password: "123456"
display_name: 默认流动站账号
- username: customer_a
password: "change_me_1"
display_name: 深圳客户 A
allowed_mountpoints:
- GD_SZ_001
- NEAREST
note: 适用于深圳片区流动站
nearest:
mountpoint: NEAREST
gga_timeout_seconds: 10
关键字段说明:
base_stations[].station_id- 已预配置基站上传时首行
BASE <station_id>中使用的编号
- 已预配置基站上传时首行
base_stations[].mountpoint- 预配置固定 NTRIP 挂载点名称
rovers[]- rover 登录账号列表
rovers[].display_name- 管理后台中显示给运维人员的客户名称
rovers[].allowed_mountpoints- 为空时表示可访问全部挂载点;配置后仅允许访问列出的挂载点
rovers[].note- 用于记录客户说明、交付备注或接入限制
nearest.mountpoint- 最近站虚拟挂载点
nearest.gga_timeout_seconds- 等待 GGA 的超时时间
管理后台中的“流动站连接信息”页面会根据这些字段直接生成每个客户的连接地址、端口、账号密码和可用挂载点说明,并支持一键复制。
说明:
- 现在基站接入不要求白名单
- 如果某个
station_id已写入base_stations,则沿用配置中的挂载点和坐标 - 如果某个
station_id未预配置,也允许接入,服务端会自动创建一个同名挂载点 - 自动创建站点默认没有坐标,因此不会参与
NEAREST最近站计算,除非后续补进配置文件 - 离线超过 7 天后,站点会自动从后台列表中清理;其中动态注册站点会从运行时直接移除
基站端上传说明
这套服务的基站接入方式不是标准 NTRIP Source 协议,而是一个更轻量的私有 TCP 上传协议。
只要基站侧、串口转发程序、边缘网关或上位机脚本能够建立 TCP 长连接,并按指定格式先发送一行标识头,再持续发送 RTCM 二进制流,就可以接入。
1. 上传目标
- 默认端口:
9000/TCP - 协议:原始 TCP 长连接
- 连接方向:基站侧主动连接服务器
例如服务器公网地址为 107.173.157.41,则上传目标为:
107.173.157.41:9000
2. 上传协议格式
每条基站连接建立后,第一行必须先发送:
BASE <station_id>\n
例如:
BASE GD_SZ_001\n
规则必须满足:
BASE必须大写BASE与station_id之间只有一个空格- 行尾必须有换行符
\n - 第一行之后立刻开始发送 RTCM 二进制流
- 服务器不会再解析第二条文本命令,后续所有字节都会被当作 RTCM 数据处理
3. 完整连接顺序
正确顺序如下:
- 基站端连接服务器
9000/TCP - 先发送
BASE <station_id>\n - 紧接着持续发送 RTCM 原始字节流
- 保持连接不断开
即:
[TCP Connect]
-> BASE GD_SZ_001\n
-> RTCM bytes
-> RTCM bytes
-> RTCM bytes
-> ...
4. station_id 与配置文件的关系
服务端根据首行里的 station_id 判断这个连接属于哪个基站。
station_id 必须与 config.yaml 中 base_stations[].station_id 完全一致。
例如:
base_stations:
- station_id: GD_SZ_001
mountpoint: GD_SZ_001
- station_id: GD_SZ_002
mountpoint: GD_SZ_002
则:
- 上传
BASE GD_SZ_001\n会绑定到基站GD_SZ_001 - 该基站对应的固定挂载点是
GD_SZ_001 - 上传
BASE GD_SZ_002\n会绑定到基站GD_SZ_002 - 该基站对应的固定挂载点是
GD_SZ_002
5. 预配置站点与自动注册站点
现在基站接入不再依赖白名单。
现场会有两类站点:
- 预配置站点
- 已写在
config.yaml里 - 使用配置中的挂载点、坐标和高度
- 可以参与
NEAREST
- 已写在
- 自动注册站点
- 没写进
config.yaml - 只要上传了合法的
BASE <station_id>\n就允许接入 - 服务端会自动创建一个站点
- 默认挂载点名就是
station_id - 默认没有坐标,因此不会参与
NEAREST
- 没写进
因此现场部署时,建议统一这三处命名:
config.yaml中的base_stations[].station_id- 上传首行中的
station_id - 现场表格、标签、维护文档中的基站编号
如果某站只想先临时上线看流,不必先配进 config.yaml;
如果后续要参与最近站或使用固定命名挂载点,再把它补进配置即可。
6. 同一基站重复连接的行为
如果同一个 station_id 已有旧连接在线,而新的连接又接入:
- 新连接会顶掉旧连接
- 旧连接会被服务器主动关闭
- 新连接成为当前有效数据源
这意味着:
- 同一站号不要让两台设备同时上传
- 如果基站侧有自动重连,直接重连即可
7. 适合的基站侧链路结构
服务端只要求看到:
BASE <station_id>\n + RTCM bytes
因此前端设备的实现方式可以有多种。
方式 A:基站串口输出 RTCM,上位机或网关转发
推荐流程:
- 基站串口持续输出 RTCM
- 工控机 / Linux 网关 / 小主机读取串口
- 转发程序主动连接服务器
9000/TCP - 发送
BASE <station_id>\n - 将后续 RTCM 字节原样转发到服务器
方式 B:设备前挂一个小型中转程序
适用场景:
- 设备能输出 RTCM
- 但设备本身不能自定义连接建立后的首行内容
推荐流程:
- 设备输出原始 RTCM
- 中转程序负责连服务器
- 中转程序先发
BASE <station_id>\n - 中转程序继续转发 RTCM
方式 C:设备只能纯 TCP 推 RTCM,不能插入首行
这种情况不能直接接本服务。
因为本服务要求连接建立后必须先收到:
BASE <station_id>\n
如果设备没有办法插入这行头,就必须增加一个前置转发程序。
8. 上传端最小要求
无论前端是 UM982、UM960、ML307,还是“基站 + 上位机脚本”,只要满足这几条即可:
- 能稳定输出 RTCM
- 能主动连接服务器
IP:9000 - 建连后先发
BASE <station_id>\n - 后续持续发送 RTCM 原始字节
- 网络断开后能自动重连
9. 典型错误
以下都属于错误接入方式:
- 直接发送 RTCM,不发送
BASE首行 - 发送
BASE:GD_SZ_001 - 发送
BASE GD_SZ_001但没有换行 station_id与配置不一致- 每次只发一点数据就断开,而不是持续推流
10. 常见现象与排查
现象 1:后台完全看不到基站在线
优先检查:
- 是否真的连到了服务器
9000/TCP - 首行是否发成了
BASE <station_id>\n - 首行中的
station_id是否为空、拼错或格式不合法 - 服务器防火墙是否放行
9000/TCP
现象 2:短暂上线又立刻掉线
优先检查:
- 上传程序是否只发了首行,没有持续送 RTCM
- 上传程序是否异常退出
- 网络是否抖动
- 是否被另一条同站号连接顶掉
现象 3:基站显示在线,但流动站没有改正数据
优先检查:
- 基站是否真的持续输出 RTCM
- 该
station_id是否对应到了预期挂载点 - 流动站账号是否有该挂载点权限
- 如果使用
NEAREST,流动站是否回传了 GGA
11. 建议重点观察的日志
排查上传问题时,建议优先看管理后台“日志”页,重点观察:
base ingest listening on :9000base station online, station_id=...base station offline, station_id=...publish rtcm failed, station_id=...
12. 最小上传示例
Linux / macOS:
{ printf 'BASE GD_SZ_001\n'; cat sample.rtcm; } | nc 127.0.0.1 9000
Windows PowerShell:
$client = New-Object System.Net.Sockets.TcpClient('127.0.0.1',9000)
$stream = $client.GetStream()
$header = [Text.Encoding]::ASCII.GetBytes("BASE GD_SZ_001`n")
$stream.Write($header, 0, $header.Length)
[byte[]]$buf = 1..1024 | ForEach-Object { 0 }
$stream.Write($buf, 0, $buf.Length)
$stream.Close()
$client.Close()
实际联调时,建议把示例中的测试字节替换为真实基站输出的 RTCM 数据流,而不是随机内容。
API 接口
1. 服务状态
curl http://127.0.0.1:8080/api/status
2. 基站列表
curl http://127.0.0.1:8080/api/stations
3. 客户端列表
curl http://127.0.0.1:8080/api/clients
4. 聚合数据
curl http://127.0.0.1:8080/api/overview
5. 在线日志
curl http://127.0.0.1:8080/api/logs
测试方法
1. 模拟基站上传
Linux / macOS:
{ printf 'BASE GD_SZ_001\n'; cat sample.rtcm; } | nc 127.0.0.1 9000
简单压测:
{ printf 'BASE GD_SZ_001\n'; head -c 1048576 /dev/urandom; } | nc 127.0.0.1 9000
Windows PowerShell:
$client = New-Object System.Net.Sockets.TcpClient('127.0.0.1',9000)
$stream = $client.GetStream()
$header = [Text.Encoding]::ASCII.GetBytes("BASE GD_SZ_001`n")
$stream.Write($header, 0, $header.Length)
$buf = New-Object byte[] 4096
0..4095 | ForEach-Object { $buf[$_] = [byte]($_ % 256) }
$stream.Write($buf, 0, $buf.Length)
$stream.Close()
$client.Close()
2. 测试固定 mountpoint
curl --http1.0 -u rover:123456 \
-H "User-Agent: NTRIP curl-test" \
http://127.0.0.1:2101/GD_SZ_001 --output -
3. 查看 sourcetable
curl --http1.0 http://127.0.0.1:2101/
4. 手工构造 NTRIP 请求
printf 'GET /GD_SZ_001 HTTP/1.0\r\nUser-Agent: NTRIP nc-test\r\nAuthorization: Basic cm92ZXI6MTIzNDU2\r\n\r\n' | nc 127.0.0.1 2101
5. 测试 NEAREST
{
printf 'GET /NEAREST HTTP/1.0\r\nUser-Agent: NTRIP nc-test\r\nAuthorization: Basic cm92ZXI6MTIzNDU2\r\n\r\n';
sleep 1;
printf '$GNGGA,020305.00,2200.0000,N,11300.0000,E,1,18,0.7,10.0,M,0.0,M,,*00\r\n';
} | nc 127.0.0.1 2101
UM982 / UM960 接入提示
当前管理后台里已经内置中文接入向导,适合现场配置时边看边做。重点建议:
- 先确认基站已经稳定输出 RTCM
- 先验证固定挂载点,再验证
NEAREST - 如果设备使用精确坐标基站模式,注意设备自身解算坐标与设定坐标误差过大时可能不会输出 RTCM
- 如遇异常,先看管理后台日志页中的:
- 基站上线 / 下线
- rover 认证失败
- mountpoint 不存在
- NEAREST 选站结果
适用场景
适合:
- 个人自建 RTK / CORS 服务
- 小规模多基站、多 rover 内网分发
- 调试 UM982 / UM960 / ML307 一类设备链路
- 不需要多租户、计费、数据库和复杂权限控制的场景
不包含的能力
本项目当前不实现:
- TLS
- 数据库
- 计费系统
- Web 登录权限
- 多租户
- VRS
- NTRIP Source 协议
- 基站密码认证
开发说明
如果你后续继续在这个项目上演进,推荐流程是:
- 先在本地修改和验证
- 觉得版本稳定后再提交到仓库
- 对较大改动建立版本标签或分支
这样会比较适合你现在“先快速迭代、再挑好的版本纳入版本控制”的工作方式。