srs源码分析1【配置文件解析】

一、整体了解

解析代码前,先从源码注释中大概了解srs配置信息的组成结构:

/**
* the config directive.
* the config file is a group of directives,
* all directive has name, args and child-directives.
* for example, the following config text:
        vhost vhost.ossrs.net {
            enabled         on;
            ingest livestream {
                enabled      on;
                ffmpeg       /bin/ffmpeg;
            }
        }
* will be parsed to:
*       SrsConfDirective: name="vhost", arg0="vhost.ossrs.net", child-directives=[
*           SrsConfDirective: name="enabled", arg0="on", child-directives=[]
*           SrsConfDirective: name="ingest", arg0="livestream", child-directives=[
*               SrsConfDirective: name="enabled", arg0="on", child-directives=[]
*               SrsConfDirective: name="ffmpeg", arg0="/bin/ffmpeg", child-directives=[]
*           ]
*       ]
* @remark, allow empty directive, for example: "dir0 {}"
* @remark, don't allow empty name, for example: ";" or "{dir0 arg0;}
*/

从上可知,srs的配置信息实际上是存在一个由SrsConfDirective(指令,或称为:配置项)组成的树形结构中的,每个指令包含:指令名、参数列表、子指令集。

二、配置结构

SrsConfDirective类型的定义一目了然:

class SrsConfDirective
{
public:
    // 指令是从配置文件的多少行解析出来的
    int conf_line;
    // 指令名
    std::string name;
    // 指令的参数集
    std::vector<std::string> args;
    // 子指令集合
    std::vector<SrsConfDirective*> directives;
public:
    SrsConfDirective();
    virtual ~SrsConfDirective();
public:
    // 获取该指令的对应索引上的参数(感觉有点多余,一个函数不就可以了吗)
    virtual std::string arg0();
    virtual std::string arg1();
    virtual std::string arg2();
public:
    // 通过索引获取子指令
    virtual SrsConfDirective* at(int index);
    // 通过指令名获取子指令
    virtual SrsConfDirective* get(std::string _name);
    // 查询指令名和第一个参数都匹配的子指令
    virtual SrsConfDirective* get(std::string _name, std::string _arg0);
public:
    virtual bool is_vhost();
    virtual bool is_stream_caster();
public:
    // 从配置文件中(已读到buffer中)解析出配置信息,实际上直接调用的parse_conf
    virtual int parse(_srs_internal::SrsConfigBuffer* buffer);
private:
    // 正在解析的指令的类型
    enum SrsDirectiveType {
        // root的子指令
        parse_file, 
        // root的多级子指令
        parse_block
    };
    // 解析配置文件中的一个指令区域,第一个区域就是root区域
    virtual int parse_conf(_srs_internal::SrsConfigBuffer* buffer, SrsDirectiveType type);
    /**
    * read a token from buffer.
    * a token, is the directive args and a flag indicates whether has child-directives.
    * @param args, the output directive args, the first is the directive name, left is the args.
    * @param line_start, the actual start line of directive.
    * @return, an error code indicates error or has child-directives.
    */
    virtual int read_token(_srs_internal::SrsConfigBuffer* buffer, std::vector<std::string>& args, int& line_start);
};

 这里可以看到:name就是指令名,args就是指令参数集合,directives就是该指令的子指令集合;通过directives,整个配置构成了一个树形结构,根节点就是root。

三、配置解析

实际上、真正解析配置文件是由SrsConfDirective类中的两个方法实现:
virtual int parse_conf(_srs_internal::SrsConfigBuffer* buffer, SrsDirectiveType type);

virtual int read_token(_srs_internal::SrsConfigBuffer* buffer, std::vector<std::string>& args, int& line_start);

1、parse_conf是用来循环解析当前指令区域的所有指令,当指令区中的某个指令遇到子指令块时,会嵌套调用parse_conf去解析子指令块。parse_conf的实现逻辑实际上是一个状态机,具体解析命令名、命令参数、返回解析状态 是由read_token去完成的。

// see: ngx_conf_parse
int SrsConfDirective::parse_conf(SrsConfigBuffer* buffer, SrsDirectiveType type)
{
    int ret = ERROR_SUCCESS;
    
    while (true) {
	/* 循环解析当前指令区域的所有指令(也就是说已
	经有父指令了,因为当前指令区域就是这个父指
	令的子指令集合区域)
	*/
        std::vector<string> args;// 存放当前指令的指令名和参数
        int line_start = 0;// 当前解析完的行号
        ret = read_token(buffer, args, line_start);
        
        /**
        * ret maybe:
        * ERROR_SYSTEM_CONFIG_INVALID           error.
        * ERROR_SYSTEM_CONFIG_DIRECTIVE         directive terminated by ';' found
        * ERROR_SYSTEM_CONFIG_BLOCK_START       token terminated by '{' found
        * ERROR_SYSTEM_CONFIG_BLOCK_END         the '}' found
        * ERROR_SYSTEM_CONFIG_EOF               the config file is done
        */
        if (ret == ERROR_SYSTEM_CONFIG_INVALID) {// 解析出错,直接退出
            return ret;
        }
        if (ret == ERROR_SYSTEM_CONFIG_BLOCK_END) {
            if (type != parse_block) {// 解析出错(莫名其妙多了一个"}"),直接退出
                srs_error("line %d: unexpected \"}\", ret=%d", buffer->line, ret);
                return ret;
            }
            return ERROR_SUCCESS;
        }
        if (ret == ERROR_SYSTEM_CONFIG_EOF) {// 根配置解析完毕
            if (type == parse_block) {// 不是根配置,出错
                srs_error("line %d: unexpected end of file, expecting \"}\", ret=%d", conf_line, ret);
                return ret;
            }
            return ERROR_SUCCESS;
        }
        
        if (args.empty()) {// 连指令名都没有解析出来,出错
            ret = ERROR_SYSTEM_CONFIG_INVALID;
            srs_error("line %d: empty directive. ret=%d", conf_line, ret);
            return ret;
        }
        
        // build directive tree.
        SrsConfDirective* directive = new SrsConfDirective();// 构建当前解析的指令

        directive->conf_line = line_start;
        directive->name = args[0];
        args.erase(args.begin());// 删除第一个arg,因为第一个是指令的name
        directive->args.swap(args);
        
        directives.push_back(directive);// 将当前指令添加到其父指令的子指集合中
        
        if (ret == ERROR_SYSTEM_CONFIG_BLOCK_START) {// 解析当前指令的子指令
            if ((ret = directive->parse_conf(buffer, parse_block)) != ERROR_SUCCESS) {
                return ret;
            }
        }
    }
    // 继续解析当前指令区域的下一个子指令
    return ret;
}

2、read_token用来直接从配置文件内容中解析出命令名、命令参数、并返回当前的解析状态(如果按标志配置,每行只有一个命令的话,read_token可以理解为每次解析一行配置文件)。返回的状态有下面这些:

ERROR_SYSTEM_CONFIG_EOF// 配置文件解析完毕

ERROR_SYSTEM_CONFIG_INVALID// 配置解析出错

ERROR_SYSTEM_CONFIG_DIRECTIVE// 一个配置项解析完毕

ERROR_SYSTEM_CONFIG_BLOCK_START// 开始一个新的配置块

ERROR_SYSTEM_CONFIG_BLOCK_END// 当前配置块解析完毕

// see: ngx_conf_read_token
int SrsConfDirective::read_token(SrsConfigBuffer* buffer, vector<string>& args, int& line_start)
{
	
	/*
	1、首先明确,什么是一个token:
	   token是处在两个相邻空格,换行符,双引号,单引号等之间的字符串.
	2、这里是一个字符一个字符的解析的
	**/

    int ret = ERROR_SUCCESS;

    char* pstart = buffer->pos;// 当前解析到的位置

    bool sharp_comment = false;//注释(#)
    
    bool d_quoted = false;//标志位,表示已扫描一个双引号,期待另一个单引号
    bool s_quoted = false;//标志位,表示已扫描一个双引号,期待另一个双引号
    
    bool need_space = false;//标志位,表示需要空白字符
    bool last_space = true;//标志位,表示上一个字符为token分隔符
    
    while (true) {
        if (buffer->empty()) {
            ret = ERROR_SYSTEM_CONFIG_EOF;
            
            if (!args.empty() || !last_space) {// 配置内容不完整
                srs_error("line %d: unexpected end of file, expecting ; or \"}\"", buffer->line);
                return ERROR_SYSTEM_CONFIG_INVALID;
            }
            srs_trace("config parse complete");// 配置解析完毕
            
            return ret;
        }
        
        char ch = *buffer->pos++;
        
        if (ch == SRS_LF) {// 换行
            buffer->line++;
            sharp_comment = false;
        }
        
        if (sharp_comment) {// 如果当前是注释行,直接跳过
            continue;
        }
        
        if (need_space) {
            if (is_common_space(ch)) {// 是空白字符
                last_space = true;
                need_space = false;
                continue;
            }
            if (ch == ';') {
                return ERROR_SYSTEM_CONFIG_DIRECTIVE;// 一个配置项结束
            }
            if (ch == '{') {
                return ERROR_SYSTEM_CONFIG_BLOCK_START;// 一个新配置区域开始
            }
            srs_error("line %d: unexpected '%c'", buffer->line, ch);
            return ERROR_SYSTEM_CONFIG_INVALID; 
        }
        
        // last charecter is space.
        if (last_space) {
            if (is_common_space(ch)) {
                continue;
            }
            pstart = buffer->pos - 1;
            switch (ch) {
                case ';':
                    if (args.size() == 0) {
                        srs_error("line %d: unexpected ';'", buffer->line);
                        return ERROR_SYSTEM_CONFIG_INVALID;
                    }
                    return ERROR_SYSTEM_CONFIG_DIRECTIVE;
                case '{':
                    if (args.size() == 0) {
                        srs_error("line %d: unexpected '{'", buffer->line);
                        return ERROR_SYSTEM_CONFIG_INVALID;
                    }
                    return ERROR_SYSTEM_CONFIG_BLOCK_START;
                case '}':
                    if (args.size() != 0) {
                        srs_error("line %d: unexpected '}'", buffer->line);
                        return ERROR_SYSTEM_CONFIG_INVALID;
                    }
                    return ERROR_SYSTEM_CONFIG_BLOCK_END;
                case '#':
                    sharp_comment = 1;
                    continue;
                case '"':
                    pstart++;
                    d_quoted = true;
                    last_space = 0;
                    continue;
                case '\'':
                    pstart++;
                    s_quoted = true;
                    last_space = 0;
                    continue;
                default:
                    last_space = 0;
                    continue;
            }
        } else {
        // last charecter is not space
            if (line_start == 0) {
                line_start = buffer->line;
            }
            
            bool found = false;// 找到一个token
            if (d_quoted) {
                if (ch == '"') {
                    d_quoted = false;
                    need_space = true;
                    found = true;
                }
            } else if (s_quoted) {
                if (ch == '\'') {
                    s_quoted = false;
                    need_space = true;
                    found = true;
                }
            } else if (is_common_space(ch) || ch == ';' || ch == '{') {
                last_space = true;
                found = 1;
            }
            
            if (found) {
                int len = (int)(buffer->pos - pstart);
                char* aword = new char[len];
                memcpy(aword, pstart, len);
                aword[len - 1] = 0;
                
                string word_str = aword;// 解析出一个token
                if (!word_str.empty()) {
                    args.push_back(word_str);
                }
                srs_freepa(aword);
                
                if (ch == ';') {// 一个配置项解析完
                    return ERROR_SYSTEM_CONFIG_DIRECTIVE;
                }
                if (ch == '{') {// 一个新的子配置区域
                    return ERROR_SYSTEM_CONFIG_BLOCK_START;
                }
            }
        }
    }
    
    return ret;
}

四、配置管理

前面介绍了配置的组织和解析。前面的配置只包括从配置文件中解析出来的配置,实际上,配置除了配置文件还有启动命令参数中的配置。这些启动命令参数中的配置是存放在SrsConfig类型全局变量中的,SrsConfig同时通过成员属性root(SrsConfDirective*)去管理配置文件中的配置,更重要的是,SrsConfig可以去动态加载配置文件,原生配置文件的动态加载流程如下:
(1)发送一个信号SIGHUP到srs进程,srs这个信号的处理函数会将该信号写入管道

(2)srs中信号管理器运行着一个独立的协程,他会循环读对应的管道,一旦读到信号,就会修改SrsServer中对应的标志。比如读到SIGHUP,就会将signal_reload置为true。

(3)srs的main协程的循环处理函数SrsServer::do_cycle()中,会定期检测signal_reload标志,如果为true则会_srs_config->reload()去启动配置动态加载

(4)SrsConfig::reload中会首先加载配置文件,并解析得到一个新的SrsConfDirective中。然后检测新的SrsConfDirective中的配置项是不是在代码中注册过的。接着,SrsConfig::reload_conf比较存放新旧配置的两个SrsConfDirective,如果检测到配置有差异,就会调用所有订阅者ISrsReloadHandler的相应的回调方法,使得订阅者重新加载配置。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值