基于Ntrip的实时多线程RTCM数据流接收及解码
Ntrip协议基本框架
NtripSource能够持续提供实时GNSS数据,如原始GNSS观测数据格式:BINEX格式、差分数据格式RTCM。常常由接收机承担,Ntrip系统为每个参考站接收机定义唯一的内部编号,称之为挂载点Mountpoint。
NtripSources, which generate data streams at a specific location.
NtripServers接受来自Source的GNSS数据流。采用既定的NtripCaster的IP地址通过互联网与NtripCaster进行认证并及逆行实时的GNSS数据流传输。
NtripServers upload the data of an NtripSource to an NtripCaster using a “mountpoint” as identification. Several NtripClients can access the data of a desired data stream in parallel by requesting a source by its mountpoint on the NtripCaster.
NtripCaster是Ntrip协议的核心,相当于一个数据中转站。一方面通过身份认证允许既定的NtripSevers接入数据中转站,一方面通过身份认证允许NtripClient获取特定挂载点MountPoint的请求并反馈其不正确操作。
The NtripCaster is a server supporting a subset of HTTP/RTSP request and response
messages and adjusted to low-bandwidth streaming data (from 50 up to 500 Bytes/sec
per stream). The NtripCaster accepts request-messages on a single port from either the NtripServer or the NtripClient. Depending on these messages, the NtripCaster decides whether there is streaming data to receive or to send.
NtripClient通过NtripCaster获取所需要的NtripSource,是GNSS数据流的最终消费者。NtripClient通过互联网请求NtripCaster获得NtripSource的source-table,根据需求获得相关挂载点的信息,通过使用相应挂载点的用户名和密码进行认证,NtripCaster认证通过后会向NtripClient传输源源不断的数据流。
An NtripClient will be accepted by and receive data from an NtripCaster if the NtripClient sends the correct request message. With respect to the message format and status code, the HTTP-based NtripClient to NtripCaster communication is fully compatible to HTTP 1.1, but in this context Ntrip uses only non-persistent connections.
配置文件设置
若整体格式为:
mountPoints=//xxxxxxx:xxxxxxx@ntrip.gnsslab.cn:2101/CUT000AUS0 RTCM_3.0 39.45 328.87 no 1
则:
- 认证信息 :mountPoints=//xxxxxxx:xxxxxxx@
- 主机及其端口 :ntrip.gnsslab.cn:2101
- 协议 :RTCM_3.0
- 测站(挂载点) :CUT000AUS0
可以有多个mountPoints=//xxxxxx格式,多个测站,会有多个线程对应。
解析配置文件信息
在主程序中调用解析文件函数parse_config_info(),gArgs.conf是上述配置文件。
int i,j,n = parse_config_info(gArgs.conf, configs);//主机、端口、协议、路径、认证信息、精度、维度
其中,解析配置文件函数在fileopt.c下
/**
* 从配置文件中解析配置信息
* 返回成功解析的测量站配置信息的数量
* */
int parse_config_info(char *file_name, ConfigInfo *configs[]) {
printf("begin to parse config file: %s\n", file_name);
config_t config = {
0};
if(ini_parse(file_name, handler, &config) < 0){
printf("Can't load config file: '%s'\n", file_name);
return 0;
}
//端口
if(gArgs.port == -1 && config.port >= MIN_LSN_PORT && config.port <= MAX_LSN_PORT){
gArgs.port = config.port;
}
//日志水平
if(gArgs.log_level == -1 && config.log_level >= 0 && config.log_level <= 4){
gArgs.log_level = config.log_level;
}
//日志文件
if(gArgs.log_file == NULL && config.log_file != NULL && strcmp(config.log_file, "") != 0){
gArgs.log_file = strdup(config.log_file);
}
//数据记录粒度
if(gArgs.time_unit == -1 && config.time_unit >= 1 && config.time_unit <= 3){
gArgs.time_unit = config.time_unit;
}
//文件过期时间
if(gArgs.expire_days == -1 && config.expire_days >= 0){
gArgs.expire_days = config.expire_days;
}
//数据文件
if(gArgs.data_path == NULL && config.data_file != NULL && strcmp(config.data_file, "") != 0){
gArgs.data_path = strdup(config.data_file);
}
if(gArgs.data_off == -1) gArgs.data_off = config.data_off;
if(gArgs.recv_off == -1) gArgs.recv_off = config.recv_off;
if(gArgs.eph_off == -1) gArgs.eph_off = config.eph_off;
//数据流文件
if(gArgs.recv_path == NULL && config.recv_file != NULL && strcmp(config.recv_file, "") != 0){
gArgs.recv_path = strdup(config.recv_file);
}
//星历文件
if(gArgs.eph_path == NULL && config.eph_file != NULL && strcmp(config.eph_file, "") != 0){
gArgs.eph_path = strdup(config.eph_file);
}
//测站列表
if(config.mount_points == NULL || strcmp(config.mount_points, "") == 0) return 0;
int i=0;
char *token = strtok(config.mount_points, ",");
while(token != NULL) {
//读取这一段配置信息
ConfigInfo *cfg = malloc(sizeof(*cfg));
char protocal[16],url[256];
if(sscanf(token,"%s %s %f %f %s %s",url,protocal,&cfg->f1,&cfg->f2,cfg->s1,cfg->s2) == 6) {
//协议信息
protocal[15] = '\0';
if (strcmp(protocal,"RTCM_3") == 0) cfg->proto = RTCM30;
else if (strcmp(protocal,"RTCM_3.0") == 0) cfg->proto = RTCM30;
else if (strcmp(protocal,"RTCM_3.1") == 0) cfg->proto = RTCM31;
else if (strcmp(protocal,"RTCM_2.3") == 0) cfg->proto = RTCM23;
else if (strcmp(protocal,"RTCM_2.1") == 0) cfg->proto = RTCM21;
else if (strcmp(protocal,"RTCM_2.0") == 0) cfg->proto = RTCM20;
else if (strcmp(protocal,"RTCM_2.2") == 0) cfg->proto = RTCM22;
else if (strcmp(protocal,"BD") == 0) cfg->proto = BD;
else if (strcmp(protocal,"TRIM") == 0) cfg->proto = TRIM;
else if (strcmp(protocal,"BINEX") == 0) cfg->proto = BINEX;
else if (strcmp(protocal,"XIHE_RTCM_3") == 0) cfg->proto = XIHE_RTCM30;
else if (strcmp(protocal,"XIHE_RTCM_3.0") == 0) cfg->proto = XIHE_RTCM30;
else if (strcmp(protocal,"IGMAS_RTCM_3") == 0) cfg->proto = IGMAS_RTCM30;
else if (strcmp(protocal,"IGMAS_RTCM_3.0") == 0) cfg->proto = IGMAS_RTCM30;
else if (strcmp(protocal,"RTCM_3.2") == 0) cfg->proto = RTCM32;
else cfg->proto = NONE;
//服务器信息
struct evhttp_uri *uri = evhttp_uri_parse(url);
cfg->port = evhttp_uri_get_port(uri);
cfg->host = evhttp_uri_get_host(uri);
cfg->path = evhttp_uri_get_path(uri);
//认证信息(找到第一个冒号,冒号前算用户名,冒号后算密码)
const char *userinfo = evhttp_uri_get_userinfo(uri);
if(userinfo != NULL) {
char *spliter = strchr(userinfo,':');
if(spliter != NULL) {
char user[MAX_USER_LEN];
char pass[MAX_PASS_LEN];
int pos = spliter - userinfo;
int len = strlen(userinfo);
if(pos < MAX_USER_LEN && len-pos-1 < MAX_PASS_LEN) {
strncpy(user,userinfo,pos);
user[pos] = '\0';
strncpy(pass,spliter+1,len-pos-1);
pass[len-pos-1] = '\0';
encode(cfg->auth,MAX_AUTH_LEN,user,pass);
configs[i++] = cfg;
}