简介
本章我们来看看RTMP服务的auto_push模块。我们知道当一个推流客户端将一路直播流上行推到RTMP直播服务器上,此时由一个worker进程负责接收上行推流数据的。如果我们在nginx.conf文件中配置开启多个worker,那么当有用户进行播放拉流的时候,其创建的socket连接是随机分配到一个worker上的,并不能保证一定是在我们的接收推流的进程上。而auto_push的主要功能是接收推流的进程将其接收到上行数据,再转发给所有的兄弟worker进程一份。这样就可以保证所有的worker进程上都拥有直播上行推流数据,这样当用户播放时,不管是连接到哪一个进程上,都能保证可以获取到下行数据,正常的观看直播。下面我们就来看看RTMP的源码中是如何实现这部分功能的。auto_push在默认情况下是关闭的,需要配置rtmp_auto_push on来打开。还有连接重试功能,通过rtmp_auto_push_reconnect进行配置。最后是本地unix域套接字的目录在rtmp_socket_dir进行配置。
源码分析
我们看到在ngx_rtmp_auto_push_module.c文件中很多函数里面都有NGX_HAVE_UNIX_DOMAIN的宏定义判断,通过这个我们可以知道auto_push功能是只有在Unix/Linux等系统上才起作用,在Windows系统上无效。不过我们的服务器一般都是Linux系统,所以不妨碍我们对其的了解。
首先我们来看ngx_rtmp_auto_push_init_process,在Nginx进程启动的时候调用。
if (ngx_process != NGX_PROCESS_WORKER) {
return NGX_OK;
}
通过这段代码我们知道ngx_rtmp_auto_push_init_process是在Worker启动时才会进行下一步的运行逻辑。
next_publish = ngx_rtmp_publish;
ngx_rtmp_publish = ngx_rtmp_auto_push_publish;
next_delete_stream = ngx_rtmp_delete_stream;
ngx_rtmp_delete_stream = ngx_rtmp_auto_push_delete_stream;
设置推流和删除流的回调函数,将auto_push模块的推流和删除流函数加入到整体的执行链表中。
/*TODO: clone all RTMP listenings? */
ls = cycle->listening.elts;
lss = NULL;
for (n = 0; n < cycle->listening.nelts; ++n, ++ls) {
if (ls->handler == ngx_rtmp_init_connection) {
lss = ls;
break;
}
}
if (lss == NULL) {
return NGX_OK;
}
当前版本的代码实现中,只会对第一个设置的rtmp监听端口服务有效。
。。。。。。
if (listen(s, NGX_LISTEN_BACKLOG) == -1) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
"listen() to worker_socket, backlog %d failed",
NGX_LISTEN_BACKLOG);
goto sock_error;
}
在这里是创建一个Unix域套接字并设置相关属性信息,然后绑定和监听新建的Unix域套接字。
ngx_rtmp_auto_push_exit_process
apcf = (ngx_rtmp_auto_push_conf_t *) ngx_get_conf(cycle->conf_ctx,
ngx_rtmp_auto_push_module);
if (apcf->auto_push == 0) {
return;
}
*ngx_snprintf(path, sizeof(path),
"%V/" NGX_RTMP_AUTO_PUSH_SOCKNAME ".%i",
&apcf->socket_dir, ngx_process_slot)
= 0;
ngx_delete_file(path);
在worker进程退出时,会关闭在启动函数(ngx_rtmp_auto_push_init_process)中创建的Unix域套接字。
ngx_rtmp_auto_push_publish
if (s->auto_pushed || (s->relay && !s->static_relay)) {
goto next;
}
判断当前的RTMP会话是否是来自自动转推的,或者当前RTMP会话是否是中继并且不是静态中继。
auto_pushed参数会在初始化RTMP会话时判断是否是Unix域套接字进行设定。
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_auto_push_index_module);
if (ctx == NULL) {
ctx = ngx_palloc(s->connection->pool,
sizeof(ngx_rtmp_auto_push_ctx_t));
if (ctx == NULL) {
goto next;
}
ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_auto_push_index_module);
}
ngx_memzero(ctx, sizeof(*ctx));
设置当前RTMP会话的auto_push对应的ctx。在RTMP的每个模块中我们都可以看到会设置对应模块的ctx。
ctx->push_evt.data = s;
ctx->push_evt.log = s->connection->log;
ctx->push_evt.handler = ngx_rtmp_auto_push_reconnect;
ctx->slots = ngx_pcalloc(s->connection->pool,
sizeof(ngx_int_t) * NGX_MAX_PROCESSES);
if (ctx->slots == NULL) {
goto next;
}
ngx_memcpy(ctx->name, v->name, sizeof(ctx->name));
ngx_memcpy(ctx->args, v->args, sizeof(ctx->args));
ngx_rtmp_auto_push_reconnect(&ctx->push_evt);
最后设置ctx对应的时间属性,并开始真正的转推连接。
ngx_rtmp_auto_push_delete_stream 删除自动转推流
apcf = (ngx_rtmp_auto_push_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,
ngx_rtmp_auto_push_module);
if (apcf->auto_push == 0) {
goto next;
}
非auto_push的,直接跳过。
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_auto_push_index_module);
if (ctx) {
if (ctx->push_evt.timer_set) {
ngx_del_timer(&ctx->push_evt);
}
goto next;
}
如果RTMP会话对应的auto_push模块ctx已经设置推流定时器,那么久删除推流定时器,并跳到最后。
/* skip non-relays & publishers */
rctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module);
if (rctx == NULL ||
rctx->tag != &ngx_rtmp_auto_push_module ||
rctx->publish == NULL)
{
goto next;
}
判断当前的RTMP会话是否是中继会话、如果是中继会话但是并不是auto_push会话或者不是publish会话,则直接跳到最后。
slot = (ngx_process_t *) rctx->data - &ngx_processes[0];
ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"auto_push: disconnect slot=%i app='%V' name='%V'",
slot, &rctx->app, &rctx->name);
pctx = ngx_rtmp_get_module_ctx(rctx->publish->session,
ngx_rtmp_auto_push_index_module);
if (pctx == NULL) {
goto next;
}
pctx->slots[slot] = 0;
/* push reconnect */
if (!pctx->push_evt.timer_set) {
ngx_add_timer(&pctx->push_evt, apcf->push_reconnect);
}
最后找的需要被删除的的RTMP会话所在的slot位置,找到中继的publish会话,并添加push_evt定时器,用于重新建立auto_push的自动转推RTMP会话。
ngx_rtmp_auto_push_reconnect
最后我们来看本模块中最重要的一个函数ngx_rtmp_auto_push_reconnect,它是push_evt定时器的回调函数,它的主要作用就是建立同各个兄弟worker进程的RTMP中继会话。
for (n = 0; n < NGX_MAX_PROCESSES; ++n, ++slot) {
......
if (ngx_rtmp_relay_push(s, &name, &at) == NGX_OK) {
*slot = 1;
npushed++;
continue;
}
......
}
我们看到第一个for循环中,会设置中继会话的对端信息,即at,然后调用ngx_rtmp_relay_push,创建向其他兄弟woker进程推流的中继RTMP会话。
if (ccf->worker_processes == npushed + 1) {
return;
}
/* several workers failed */
slot = ctx->slots;
for (n = 0; n < NGX_MAX_PROCESSES; ++n, ++slot) {
pid = ngx_processes[n].pid;
if (n == ngx_process_slot || *slot == 1 ||
pid == 0 || pid == NGX_INVALID_PID)
{
continue;
}
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"auto_push: connect failed: slot=%i pid=%P name='%s'",
n, pid, ctx->name);
}
if (!ctx->push_evt.timer_set) {
ngx_add_timer(&ctx->push_evt, apcf->push_reconnect);
}
这个首先是判断第一个for循环中,是否和每个兄弟worker进程创建auto_push中继会话成功,如果不成功,第二个for循环会打印出来所有没有建立成功的auto push会话信息,然后是添加push_evt定时器,用于下一次重试auto push建立中继会话。
小结
通过本章的介绍,我们知道了RTMP auto push模块的实际运行原理,即进程间转发。这就带来一个问题:每个进程上面的流都是一样的,也就是同一个直播流被负责了N份,对于现在的多核服务器来说,并不能真正有效的利用多核的并行效率。所以这个模块只适合作为demo项目或者增进对RTMP服务的理解使用,是不建议在线上开启auto push模块的。至于如何在线上开启多个worker以充分利用服务器的每个内核,我们将在后面的章节进行介绍。