nginx-rtmp模块中的事件注册和事件分发

概述

  在上一篇博文中,我们介绍了nginx-rtmp模块的网络数据接收事件、网络数据发送事件和网络保活(ping)事件;在网络数据接收事件发生的时候,当我们读取了网络数据,并且在解析rtmp协议包之后,需要根据协议包中的MessageTypeID字段,做相应的事件处理;

  我们知道,rtmp协议是进行实时音视频流传输的一个协议,对于服务端来说,肯定不止存在一路流,所以在进行流传输之前,客户端和服务端需要进行协商;协商传输的流的信息,在rtmp协议的定义中,有规定根据不同的MessageTypeID的值,定义了不同的行为;

  其中需要注意的是,在RTMP协议的规定中,其实可以将MessageTypeID分为以下类:
消息一:取值{1,2,3,5,6}的可以看成是一类,实现的是rtmp协议控制消息;
消息二:将{15,16,17,18,19,20}看成一类,这个定义的是amf格式的消息;
消息三:将{8,9}看成一类消息,用于传输音视频流的;
消息四:将{4,20}这个归为剩余的一类

 针对于”消息二“,它其实是以amf的格式,去传输一些命令,例如用于流连接的”connect“命令,用于播放的”play“命令,用于推流的”pushlish“命令等;

源码分析

  接下来,我们看一下nginn-rtmp模块是如何进行事件分发的;在上一篇博文中,我们提到,在解析完数据之后,会调用ngx_rtmp_receive_message方法进行数据分发,所以我们现在来看一下这个方法:

ngx_int_t ngx_rtmp_receive_message(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, ngx_chain_t *in)
{
    ngx_rtmp_core_main_conf_t  *cmcf;
    ngx_array_t                *evhs;
    size_t                      n;
    ngx_rtmp_handler_pt        *evh;
	//获取RTMP{}域的配置信息
    cmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_core_module);
    //校验MessageTypeID的值是否大于RTMP协议中规定的最大的值
    if (h->type > NGX_RTMP_MSG_MAX) {
        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                "unexpected RTMP message type: %d", (int)h->type);
        return NGX_OK;
    }

	//获取MessageTypeID对应的注册事件(这个待会儿会详细讲解)
    evhs = &cmcf->events[h->type];
    evh = evhs->elts;

    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
            "nhandlers: %d", evhs->nelts);
	//同一个MessageTypeID会被注册多个处理方法,所以这个地方会循环遍历每一个注册的方法,取处理事件
    for(n = 0; n < evhs->nelts; ++n, ++evh) {
        if (!evh) {
            continue;
        }
        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                "calling handler %d", n);
		//调用处理方法,取处理事件,并且根据返回值做相应的处理
        switch ((*evh)(s, h, in)) {
            case NGX_ERROR:
                ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                        "handler %d failed", n);
                return NGX_ERROR;
            case NGX_DONE:
                return NGX_OK;
        }
    }

    return NGX_OK;
}

  好了,上面是整个nginx-rtmp模块的事件分发方法,他的代码逻辑比较简单,仅仅是根据MessageTypeID字段,从rtmp模块全局域配置文件的管理结构体cmcf中的events数组中 ,获取到能够处理这个消息的方法列表,然后遍历调用方法列表中的每一个方法,让他们取处理这个消息;

  在这个过程中,大家可能会有一个疑惑,每个MessageTypeID的处理方法是在什么时候注册到cmcf的events数组中去的呢?我们通过查询events数组的引用,可以发现在ngx_rtmp_init_event_handlers方法中,回去跟他注册方法,我们看一下这个方法中,干了些什么:

static ngx_int_t ngx_rtmp_init_event_handlers(ngx_conf_t *cf, ngx_rtmp_core_main_conf_t *cmcf)
{
    ngx_hash_init_t             calls_hash;
    ngx_rtmp_handler_pt        *eh;
    ngx_rtmp_amf_handler_t     *h;
    ngx_hash_key_t             *ha;
    size_t                      n, m;
	//protocol event,协议控制消息,也就是上面提到的“消息一”{1,2,3,5,6}
    static size_t               pm_events[] = {
        NGX_RTMP_MSG_CHUNK_SIZE,
        NGX_RTMP_MSG_ABORT,
        NGX_RTMP_MSG_ACK,
        NGX_RTMP_MSG_ACK_SIZE,
        NGX_RTMP_MSG_BANDWIDTH
    };
	
	//amf event,也就是上面提到的“消息二”{15,16,17,18,19,20}
    static size_t               amf_events[] = {
        NGX_RTMP_MSG_AMF_CMD,
        NGX_RTMP_MSG_AMF_META,
        NGX_RTMP_MSG_AMF_SHARED,
        NGX_RTMP_MSG_AMF3_CMD,
        NGX_RTMP_MSG_AMF3_META,
        NGX_RTMP_MSG_AMF3_SHARED
    };

    /* init standard protocol events */
    for(n = 0; n < sizeof(pm_events) / sizeof(pm_events[0]); ++n) 
	{
        eh = ngx_array_push(&cmcf->events[pm_events[n]]);
        *eh = ngx_rtmp_protocol_message_handler;
    }

    /* init amf events */
    for(n = 0; n < sizeof(amf_events) / sizeof(amf_events[0]); ++n) 
	{
        eh = ngx_array_push(&cmcf->events[amf_events[n]]);
        *eh = ngx_rtmp_amf_message_handler;
    }

    /* init user protocol events 用户自定义消息,也就是MessageTypeID=4的处理方式*/
    eh = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_USER]);
    *eh = ngx_rtmp_user_message_handler;

    /* aggregate to audio/video map MessageTypeID=22的消息处理方式*/
    eh = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_AGGREGATE]);
    *eh = ngx_rtmp_aggregate_message_handler;
	
    /*
    针对于“消息二”,这种类型,他是AMF格式的数据,数据部分存储的是命令,
    说明它可以由很多种命令,这个地方就是注册各种amf命令的处理方法
    */
    ngx_array_init(&cmcf->amf_arrays, cf->pool, 1, sizeof(ngx_hash_key_t));

    h = cmcf->amf.elts;
    for(n = 0; n < cmcf->amf.nelts; ++n, ++h)
	{
        ha = cmcf->amf_arrays.elts;
        for(m = 0; m < cmcf->amf_arrays.nelts; ++m, ++ha) 
		{
            if (h->name.len == ha->key.len && !ngx_strncmp(h->name.data, ha->key.data, ha->key.len))
            {
                break;
            }
        }

        if (m == cmcf->amf_arrays.nelts)
		{
            ha = ngx_array_push(&cmcf->amf_arrays);
            ha->key = h->name;
            ha->key_hash = ngx_hash_key_lc(ha->key.data, ha->key.len);
            ha->value = ngx_array_create(cf->pool, 1, sizeof(ngx_rtmp_handler_pt));
            if (ha->value == NULL) {
                return NGX_ERROR;
            }
        }

        eh = ngx_array_push((ngx_array_t*)ha->value);
        *eh = h->handler;
    }
	//amf命令是通过hash方式进行管理的,这个地方是准备hash需要的参数
    calls_hash.hash = &cmcf->amf_hash;
    calls_hash.key = ngx_hash_key_lc;		//hash调用的方法
    calls_hash.max_size = 512;
    calls_hash.bucket_size = ngx_cacheline_size;
    calls_hash.name = "amf_hash";
    calls_hash.pool = cf->pool;
    calls_hash.temp_pool = NULL;

    if (ngx_hash_init(&calls_hash, cmcf->amf_arrays.elts, cmcf->amf_arrays.nelts)
            != NGX_OK)
    {
        return NGX_ERROR;
    }

    return NGX_OK;
}

  上面是整个rtmp协议注册MessageTypeID处理方式到cmcf->events中的逻辑,从上面我们可以看到,不仅仅根据MessageTypeID注册了事件,还注册了amf的事件;这个地方由存在疑问了,从上面的代码中我们可以看到amf的相关事件是从cmcf->amf这个中取出来的,那么cmcf->amf又是在什么时候给他赋值的呢?通过对初始化的流程分析,我们发现这个cmcf->amf是在初始化的时候,在ngx_rtmp_cmd_module.c文件的ngx_rtmp_cmd_postconfiguration方法中赋值的,我们现在看一下这个方法的逻辑:

static ngx_int_t ngx_rtmp_cmd_postconfiguration(ngx_conf_t *cf)
{
    ngx_rtmp_core_main_conf_t          *cmcf;
    ngx_rtmp_handler_pt                *h;
    ngx_rtmp_amf_handler_t             *ch, *bh;
    size_t                              n, ncalls;
	//获取rtmp{}域的信息
    cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module);

    /* redirect disconnects to deleteStream
     * to free client modules from registering
     * disconnect callback */
	//将连接断开的事件注册到events的事件数组中去
    h = ngx_array_push(&cmcf->events[NGX_RTMP_DISCONNECT]);
    if (h == NULL) {
        return NGX_ERROR;
    }

    *h = ngx_rtmp_cmd_disconnect_init;

    /* register AMF callbacks */
	//获取到amf的事件个数,其中ngx_rtmp_cmd_map存储了rtmp协议amf中定义的所有的事件
    ncalls = sizeof(ngx_rtmp_cmd_map) / sizeof(ngx_rtmp_cmd_map[0]);
	//为amf事件分配存储空间
    ch = ngx_array_push_n(&cmcf->amf, ncalls);
    if (ch == NULL) {
        return NGX_ERROR;
    }

    bh = ngx_rtmp_cmd_map;
	//依次遍历map,将事件全部增加到cmcf->amf中取,这个地方使用了两个指针进行处理;
    for(n = 0; n < ncalls; ++n, ++ch, ++bh) {
        *ch = *bh;
    }

	//针对于一些回调方法,设置初始值,后面会用到,会使用他们创建一个逻辑的函数调用链
    ngx_rtmp_connect = ngx_rtmp_cmd_connect;
    ngx_rtmp_disconnect = ngx_rtmp_cmd_disconnect;
    ngx_rtmp_create_stream = ngx_rtmp_cmd_create_stream;
    ngx_rtmp_close_stream = ngx_rtmp_cmd_close_stream;
    ngx_rtmp_delete_stream = ngx_rtmp_cmd_delete_stream;
    ngx_rtmp_publish = ngx_rtmp_cmd_publish;
    ngx_rtmp_play = ngx_rtmp_cmd_play;
    ngx_rtmp_seek = ngx_rtmp_cmd_seek;
    ngx_rtmp_pause = ngx_rtmp_cmd_pause;

    ngx_rtmp_stream_begin = ngx_rtmp_cmd_stream_begin;
    ngx_rtmp_stream_eof = ngx_rtmp_cmd_stream_eof;
    ngx_rtmp_stream_dry = ngx_rtmp_cmd_stream_dry;
    ngx_rtmp_recorded = ngx_rtmp_cmd_recorded;
    ngx_rtmp_set_buflen = ngx_rtmp_cmd_set_buflen;

    return NGX_OK;
}

  上面是针对于amf事件的注册方式;
  根据MessageTypeID可以找到对应的消息处理方式,但是如果传递的是amf格式的数据,我们只能解析出amf格式的数据,这样还是不能够定位到具体的处理方法中去;那么nginx-rtmp模块又是如何定位到amf命令对应的方法中去的呢?
  根据上面的说明,我们可以知道,当MessageTypeID字段的取值为amf数据的时候(也就是MessageTypeID字段取值为”消息二“中的某一个),会调用ngx_rtmp_amf_message_handler;我们看一下这个方法做了些什么:

ngx_int_t ngx_rtmp_amf_message_handler(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, ngx_chain_t *in)
{
    ngx_rtmp_amf_ctx_t          act;
    ngx_rtmp_core_main_conf_t  *cmcf;
    ngx_array_t                *ch;
    ngx_rtmp_handler_pt        *ph;
    size_t                      len, n;

    static u_char               func[128];

    static ngx_rtmp_amf_elt_t   elts[] = {

        { NGX_RTMP_AMF_STRING,
          ngx_null_string,
          func,   sizeof(func) },
    };

    /* AMF command names come with string type, but shared object names
     * come without type */
    if (h->type == NGX_RTMP_MSG_AMF_SHARED ||
        h->type == NGX_RTMP_MSG_AMF3_SHARED)
    {
        elts[0].type |= NGX_RTMP_AMF_TYPELESS;
    } else {
        elts[0].type &= ~NGX_RTMP_AMF_TYPELESS;
    }

    if ((h->type == NGX_RTMP_MSG_AMF3_SHARED ||
         h->type == NGX_RTMP_MSG_AMF3_META ||
         h->type == NGX_RTMP_MSG_AMF3_CMD)
         && in->buf->last > in->buf->pos)
    {
        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                "AMF3 prefix: %ui", (ngx_int_t)*in->buf->pos);
        ++in->buf->pos;
    }

    cmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_core_module);

    /* read AMF func name & transaction id */
    ngx_memzero(&act, sizeof(act));
    act.link = in;
    act.log = s->connection->log;
    memset(func, 0, sizeof(func));
	//获取amf的命令
    if (ngx_rtmp_amf_read(&act, elts, sizeof(elts) / sizeof(elts[0])) != NGX_OK)
    {
        ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                "AMF cmd failed");
        return NGX_ERROR;
    }

    /* skip name */
    in = act.link;
    in->buf->pos += act.offset;

    len = ngx_strlen(func);
	//根据amf命令,进行hash,找到命令对应的方法
    ch = ngx_hash_find(&cmcf->amf_hash, ngx_hash_strlow(func, func, len), func, len);

    if (ch && ch->nelts) {
        ph = ch->elts;
        //同一个命令,在多个子模块中可能被注册,所以处理方法可能存在多个,循环调用每个方法,去通知各个子模块
        for (n = 0; n < ch->nelts; ++n, ++ph) {
            ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                "AMF func '%s' passed to handler %d/%d",
                func, n, ch->nelts);
            switch ((*ph)(s, h, in)) {
                case NGX_ERROR:
                    return NGX_ERROR;
                case NGX_DONE:
                    return NGX_OK;
            }
        }
    } else {
        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
            "AMF cmd '%s' no handler", func);
    }

    return NGX_OK;
}

  在这个方法中会获取到对应的amf命令,然后根据amf命令进行hash,通过ngx_rtmp_init_event_handlers方法中对于amf的hash值注册的方法,找到对应的处理方法,然后调用方法即可

  上面已经分析了整个RTMP消息中的事件注册和事件分发的逻辑;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

brid.huang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值