基于Ntrip的实时多线程RTCM数据流接收及解码

本文详细介绍了Ntrip协议在实时多线程环境中接收和解码RTCM数据流的过程。首先,解析配置文件获取测站信息,然后创建线程池,每个线程对应一个测站。通过HTTP请求向Caster发送请求,接收数据流,解码RTCM数据,最后存储和处理观测数据。主要涉及配置文件解析、Ntrip协议、HTTP请求、数据流解码和星历解码等多个技术环节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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
则:

  1. 认证信息 :mountPoints=//xxxxxxx:xxxxxxx@
  2. 主机及其端口 :ntrip.gnsslab.cn:2101
  3. 协议 :RTCM_3.0
  4. 测站(挂载点) :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;
                    } 
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值