概述
在上一篇博文中,我们介绍了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消息中的事件注册和事件分发的逻辑;