Nginx RTMP源码分析--ngx_rtmp_control_module

16 篇文章 6 订阅
13 篇文章 7 订阅

ngx_rtmp_control_module

RTMP控制模块主要包括录制指令、关闭指令、重定向指令三个控制命令。和其他RTMP有所不同的地方是:RTMP控制模块是注册在HTTP模块下的,它是通过在HTTP配置域下进行控制配置,并通过HTTP Get请求来实现外部对RTMP服务的控制功能。

源码分析

注册HTTP回调
static char *
ngx_rtmp_control(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_core_loc_conf_t  *clcf;

    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
    clcf->handler = ngx_rtmp_control_handler;

    return ngx_conf_set_bitmask_slot(cf, cmd, conf);
}

通过ngx_rtmp_control将ngx_rtmp_control_handler注册到HTTP服务上,当接收到HTTP客户端请求时,进行相应的指令操作并返回结果。

ngx_rtmp_control_handler回调函数解析
一、解析HTTP请求的URI,获取到section和method。section和method的具体含义如下表所示:
sectionmethod说明
recordstart录制开始
recordstop录制结束
droppublisher关闭推流
dropsubscriber关闭拉流
dropclient关闭匹配的所有客户端连接
redirectpublisher推流重定向到的新流名称
redirectsubscriber拉流重定向到的新流名称
redirectclient客户端连接重定向到的新流名称
    /* uri format: .../section/method?args */

    ngx_str_null(&section);
    ngx_str_null(&method);

    for (n = r->uri.len; n; --n) {
        p = &r->uri.data[n - 1];

        if (*p != '/') {
            continue;
        }

        if (method.data) {
            section.data = p + 1;
            section.len  = method.data - section.data - 1;
            break;
        }

        method.data = p + 1;
        method.len  = r->uri.data + r->uri.len - method.data;
    }
二、使用宏定义来简化代码。

其实可以将代码扩展开来如下,以record(录制)为例进行说明:

if (llcf->control & NGX_RTMP_CONTROL_RECORD &&
        section.len == sizeof("record") - 1 &&
        ngx_strncmp(section.data, "record", sizeof("record") - 1) == 0)
    {
        return ngx_rtmp_control_record(r, &method);
    }

#define NGX_RTMP_CONTROL_SECTION(flag, secname)                             \
    if (llcf->control & NGX_RTMP_CONTROL_##flag &&                          \
        section.len == sizeof(#secname) - 1 &&                              \
        ngx_strncmp(section.data, #secname, sizeof(#secname) - 1) == 0)     \
    {                                                                       \
        return ngx_rtmp_control_##secname(r, &method);                      \
    }

    NGX_RTMP_CONTROL_SECTION(RECORD, record);
    NGX_RTMP_CONTROL_SECTION(DROP, drop);
    NGX_RTMP_CONTROL_SECTION(REDIRECT, redirect);

三、录制

录制请求的格式参考官方wiki如下:

http://server.com/control/record/start|stop?srv=SRV&app=APP&name=NAME&rec=REC

查找RTMP配置的所有Application及其所有的直播流,直到匹配上URI(r)中的参数,然后调用ngx_rtmp_control_record_handler函数进行具体的录制指令的执行。

msg = ngx_rtmp_control_walk(r, ngx_rtmp_control_record_handler);

现在我们首先看看在查找到对应的直播RTMP会话后,录制指令是如何执行的:
1、解析URI中的rec,然后通过ngx_rtmp_record_find查找在RTMP的配置项中的录制块名称。

    if (ngx_http_arg(r, (u_char *) "rec", sizeof("rec") - 1, &rec) != NGX_OK) {
        rec.len = 0;
    }

    rn = ngx_rtmp_record_find(racf, &rec);
    if (rn == NGX_CONF_UNSET_UINT) {
        return "Recorder not found";
    }

2、解析出method的具体指令是开始录制或者结束录制,然后分别调用不同的录制函数。

    if (ctx->method.len == sizeof("start") - 1 &&
        ngx_strncmp(ctx->method.data, "start", ctx->method.len) == 0)
    {
        rc = ngx_rtmp_record_open(s, rn, &ctx->path);

    } else if (ctx->method.len == sizeof("stop") - 1 &&
               ngx_strncmp(ctx->method.data, "stop", ctx->method.len) == 0)
    {
        rc = ngx_rtmp_record_close(s, rn, &ctx->path);

    } else {
        return "Undefined method";
    }

我们可以看到,对于method参数传入start,则执行打开录制的函数;如果method参数是stop,则执行关闭录制的函数。至于函数ngx_rtmp_record_open和函数ngx_rtmp_record_close的具体内容,我们已经在录制模块中介绍了,有疑问的读者可以阅读参考第N章RTMP录制模块。

四、关闭RTMP会话

关闭RTMP会话的格式参考官方wiki如下:

http://server.com/control/drop/publisher|subscriber|client?
srv=SRV&app=APP&name=NAME&addr=ADDR&clientid=CLIENTID

method包含三种:publisher,subscriber和client。首先是判断获取到的method类型,分别对ctx->filter设置不同的参数:
publisher:NGX_RTMP_CONTROL_FILTER_PUBLISHER
subscriber:NGX_RTMP_CONTROL_FILTER_SUBSCRIBER
client:NGX_RTMP_CONTROL_FILTER_CLIENT
设置ctx->filter的意义是在ngx_rtmp_control_walk_session函数中查找RTMP会话,并根据ctx->filter对RTMP会话进行筛选,选择出合适的RTMP会话进行下一步操作。


    if (ctx->method.len == sizeof("publisher") - 1 &&
        ngx_memcmp(ctx->method.data, "publisher", ctx->method.len) == 0)
    {
        ctx->filter = NGX_RTMP_CONTROL_FILTER_PUBLISHER;

    } else if (ctx->method.len == sizeof("subscriber") - 1 &&
               ngx_memcmp(ctx->method.data, "subscriber", ctx->method.len)
               == 0)
    {
        ctx->filter = NGX_RTMP_CONTROL_FILTER_SUBSCRIBER;

    } else if (method->len == sizeof("client") - 1 &&
               ngx_memcmp(ctx->method.data, "client", ctx->method.len) == 0)
    {
        ctx->filter = NGX_RTMP_CONTROL_FILTER_CLIENT;

    } else {
        msg = "Undefined filter";
        goto error;
    }

第二步是调用ngx_rtmp_control_walk查找所有符合条件的RTMP会话,然后调用设置的回调函数ngx_rtmp_control_drop_handler运行。


    msg = ngx_rtmp_control_walk(r, ngx_rtmp_control_drop_handler);
    if (msg != NGX_CONF_OK) {
        goto error;
    }

我们可以看到ngx_rtmp_control_drop_handler回调中只有一步简单的操作,就是将RTMP会话关闭。

ngx_rtmp_control_ctx_t  *ctx;

    ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module);

    ngx_rtmp_finalize_session(s);

    ++ctx->count;

最后是将control/drop的操作结果给出HTTP响应信息。

   。。。。。。
    ngx_http_send_header(r);

    return ngx_http_output_filter(r, &cl);
五、重定向流

官方wiki说明:主要是多了有一个newname,代表重定向的新流名称。

http://server.com/control/redirect/publisher|subscriber|client?
srv=SRV&app=APP&name=NAME&addr=ADDR&clientid=CLIENTID&newname=NEWNAME

第一步同drop流类似,也是首先判断method参数,并设置ctx->filter。

    ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module);

    if (ctx->method.len == sizeof("publisher") - 1 &&
        ngx_memcmp(ctx->method.data, "publisher", ctx->method.len) == 0)
    {
        ctx->filter = NGX_RTMP_CONTROL_FILTER_PUBLISHER;

    } else if (ctx->method.len == sizeof("subscriber") - 1 &&
               ngx_memcmp(ctx->method.data, "subscriber", ctx->method.len)
               == 0)
    {
        ctx->filter = NGX_RTMP_CONTROL_FILTER_SUBSCRIBER;

    } else if (ctx->method.len == sizeof("client") - 1 &&
               ngx_memcmp(ctx->method.data, "client", ctx->method.len) == 0)
    {
        ctx->filter = NGX_RTMP_CONTROL_FILTER_CLIENT;

    } else {
        msg = "Undefined filter";
        goto error;
    }

第二步是调用ngx_rtmp_control_walk遍历寻找指定的RTMP会话,并对其调用ngx_rtmp_control_redirect_handler进行重定向新RTMP会话。

    msg = ngx_rtmp_control_walk(r, ngx_rtmp_control_redirect_handler);
    if (msg != NGX_CONF_OK) {
        goto error;
    }

最后是返回HTTP操作的响应结果

 ngx_http_send_header(r);

    return ngx_http_output_filter(r, &cl);

ngx_rtmp_control_redirect_handler中,首先会调用 ngx_rtmp_close_stream(s, &vc);来关闭旧的流名称,但是其并没有调用ngx_rtmp_finalize_session(s)将RTMP会话进行实际关闭,这样可以看出其实际上是在逻辑上关闭。我们知道ngx_rtmp_close_stream其实是一个函数链表,对本节中最重要的两个函数是next_close_stream = ngx_rtmp_close_stream;
将旧的流在逻辑上关闭后,针对不同的ctx->filter类型,决定执行ngx_rtmp_publish或ngx_rtmp_play。

查找流

接下来我们看本章中的另一个重点源码内容,就是ngx_rtmp_control_walk函数。它的主要作用是查找到符合要求的相应的RTMP会话,然后调用ngx_rtmp_control_handler_t执行指令。ngx_rtmp_control_handler_t就是我们上面介绍的三种指令类型的回调函数,我们再重温一遍:录制回调ngx_rtmp_control_record_handler,关闭会话回调ngx_rtmp_control_drop_handler,重定向回调ngx_rtmp_control_redirect_handler。
首先是获取HTTP请求中的srv参数,用来匹配nginx.conf中rtmp配置的server。默认是配置的第一块server{}。

    if (ngx_http_arg(r, (u_char *) "srv", sizeof("srv") - 1, &srv) == NGX_OK) {
        sn = ngx_atoi(srv.data, srv.len);
    }

然后在server{}块中,查找名称和app参数相同的application{}块。

 if (ngx_http_arg(r, (u_char *) "app", sizeof("app") - 1, &app) != NGX_OK) {
        app.len = 0;
    }

    pcacf = cscf->applications.elts;

    for (n = 0; n < cscf->applications.nelts; ++n, ++pcacf) {
        if (app.len && ((*pcacf)->name.len != app.len ||
                        ngx_strncmp((*pcacf)->name.data, app.data, app.len)))
        {
            continue;
        }

        s = ngx_rtmp_control_walk_app(r, *pcacf);
        if (s != NGX_CONF_OK) {
            return s;
        }
    }

下一步就是获取HTTP请求中是否携带name参数。如果没有携带name参数,则会遍历此application中所有的RTMP会话,找到符合下一项的(例如:addr、clientid等),这一点需要特别注意,control指令不携带RTMP会话名称将会使application中所有符合其他条件的RTMP会话被执行,不携带RTMP会话名称对于线上服务可能是非常危险的。

 if (ngx_http_arg(r, (u_char *) "name", sizeof("name") - 1, &name) != NGX_OK)
    {
        for (n = 0; n < (ngx_uint_t) lacf->nbuckets; ++n) {
            for (ls = lacf->streams[n]; ls; ls = ls->next) {
                s = ngx_rtmp_control_walk_stream(r, ls);
                if (s != NGX_CONF_OK) {
                    return s;
                }
            }
        }

        return NGX_CONF_OK;
    }

如果携带有RTMP会话名称,会从streams数组中获取到匹配上的RTMP会话链。然后执行ngx_rtmp_control_walk_stream。

    for (ls = lacf->streams[ngx_hash_key(name.data, name.len) % lacf->nbuckets];
         ls; ls = ls->next) 
    {
        len = ngx_strlen(ls->name);
        if (name.len != len || ngx_strncmp(name.data, ls->name, name.len)) {
            continue;
        }

        s = ngx_rtmp_control_walk_stream(r, ls);
        if (s != NGX_CONF_OK) {
            return s;
        }
    }

ngx_rtmp_control_walk_stream的本质是遍历lctx链表,然后执行ngx_rtmp_control_walk_session判断RTMP会话是否匹配。
在ngx_rtmp_control_walk_session函数中,会获取HTTP请求中的addr和clientid,判断是否匹配;并判断指令是对推流有效还是对拉流有效。最后将匹配到的接口添加到ctx->sessions数组中。交由上层回调函数使用。

ss = ngx_array_push(&ctx->sessions);

本章小结

通过本章的介绍,相信大家对于RTMP中如何执行指令有了比较清楚的了解。大家明白了录制指令、关闭会话指令、重定向指令的具体实现是什么样子的,大家在以后具体的使用中会有更深刻的理解。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值