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 .

启动方法

  1. 复制配置模板:
cp config.yaml.example config.yaml
  1. 修改 config.yaml

  2. 启动服务:

./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. 完整连接顺序

正确顺序如下:

  1. 基站端连接服务器 9000/TCP
  2. 先发送 BASE <station_id>\n
  3. 紧接着持续发送 RTCM 原始字节流
  4. 保持连接不断开

即:

[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

因此现场部署时,建议统一这三处命名:

  1. config.yaml 中的 base_stations[].station_id
  2. 上传首行中的 station_id
  3. 现场表格、标签、维护文档中的基站编号

如果某站只想先临时上线看流,不必先配进 config.yaml;
如果后续要参与最近站或使用固定命名挂载点,再把它补进配置即可。

6. 同一基站重复连接的行为

如果同一个 station_id 已有旧连接在线,而新的连接又接入:

  • 新连接会顶掉旧连接
  • 旧连接会被服务器主动关闭
  • 新连接成为当前有效数据源

这意味着:

  • 同一站号不要让两台设备同时上传
  • 如果基站侧有自动重连,直接重连即可

7. 适合的基站侧链路结构

服务端只要求看到:

BASE <station_id>\n + RTCM bytes

因此前端设备的实现方式可以有多种。

方式 A:基站串口输出 RTCM,上位机或网关转发

推荐流程:

  1. 基站串口持续输出 RTCM
  2. 工控机 / Linux 网关 / 小主机读取串口
  3. 转发程序主动连接服务器 9000/TCP
  4. 发送 BASE <station_id>\n
  5. 将后续 RTCM 字节原样转发到服务器

方式 B:设备前挂一个小型中转程序

适用场景:

  • 设备能输出 RTCM
  • 但设备本身不能自定义连接建立后的首行内容

推荐流程:

  1. 设备输出原始 RTCM
  2. 中转程序负责连服务器
  3. 中转程序先发 BASE <station_id>\n
  4. 中转程序继续转发 RTCM

方式 C:设备只能纯 TCP 推 RTCM,不能插入首行

这种情况不能直接接本服务。
因为本服务要求连接建立后必须先收到:

BASE <station_id>\n

如果设备没有办法插入这行头,就必须增加一个前置转发程序。

8. 上传端最小要求

无论前端是 UM982、UM960、ML307,还是“基站 + 上位机脚本”,只要满足这几条即可:

  1. 能稳定输出 RTCM
  2. 能主动连接服务器 IP:9000
  3. 建连后先发 BASE <station_id>\n
  4. 后续持续发送 RTCM 原始字节
  5. 网络断开后能自动重连

9. 典型错误

以下都属于错误接入方式:

  • 直接发送 RTCM,不发送 BASE 首行
  • 发送 BASE:GD_SZ_001
  • 发送 BASE GD_SZ_001 但没有换行
  • station_id 与配置不一致
  • 每次只发一点数据就断开,而不是持续推流

10. 常见现象与排查

现象 1:后台完全看不到基站在线

优先检查:

  1. 是否真的连到了服务器 9000/TCP
  2. 首行是否发成了 BASE <station_id>\n
  3. 首行中的 station_id 是否为空、拼错或格式不合法
  4. 服务器防火墙是否放行 9000/TCP

现象 2:短暂上线又立刻掉线

优先检查:

  1. 上传程序是否只发了首行,没有持续送 RTCM
  2. 上传程序是否异常退出
  3. 网络是否抖动
  4. 是否被另一条同站号连接顶掉

现象 3:基站显示在线,但流动站没有改正数据

优先检查:

  1. 基站是否真的持续输出 RTCM
  2. 该 station_id 是否对应到了预期挂载点
  3. 流动站账号是否有该挂载点权限
  4. 如果使用 NEAREST,流动站是否回传了 GGA

11. 建议重点观察的日志

排查上传问题时,建议优先看管理后台“日志”页,重点观察:

  • base ingest listening on :9000
  • base 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 协议
  • 基站密码认证

开发说明

如果你后续继续在这个项目上演进,推荐流程是:

  1. 先在本地修改和验证
  2. 觉得版本稳定后再提交到仓库
  3. 对较大改动建立版本标签或分支

这样会比较适合你现在“先快速迭代、再挑好的版本纳入版本控制”的工作方式。

S
Description
Lightweight private RTK/NTRIP server with dashboard
Readme
189 KiB
0 Stars 1 Watchers 0 Forks
1.0.11
Latest
2026-06-08 15:16:04 +08:00
Languages
Go 46.7%
JavaScript 36.5%
CSS 16.2%
Dockerfile 0.4%
HTML 0.2%