基于nginx-1.8.1+nginx-rtmp-module方案
多种实时流请求触发中继的实现
1.概述
跟中继功能相关模块,目前有三个,分别是notify,auto-push,relay。
其中notify模块暂不介绍。
模块名:ngx_rtmp_auto_push_module;
应用于nginx多进程工作方式,当某一进程受理rtmp实时推流时,同时向其它工作进程实时推流,保证所有工作进程都有该实时流数据,以便各工作进程基于该流的服务,从而保证nginx服务的完整性;
各进程流同步是基于本地套接字来实现的;
剖析主要的三个函数:
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;
初始化本地侦听套接字
1.根据ngx_rtmp_init_connection查找ngx_listening_t实例来复制;
2.sokcet_name:
saun->sun_family = AF_UNIX;
*ngx_snprintf((u_char *) saun->sun_path,sizeof(saun->sun_path),
"%V/"NGX_RTMP_AUTO_PUSH_SOCKNAME ".%i",
&apcf->socket_dir,ngx_process_slot) = 0;
3.该套接字请求产生的ngx_rtmp_session_t *s,其中s->auto_pushed = 1;
推流事件上来时:
typedefstruct ngx_rtmp_auto_push_ctx_s ngx_rtmp_auto_push_ctx_t;
structngx_rtmp_auto_push_ctx_s {
ngx_int_t *slots; /*NGX_MAX_PROCESSES */
u_char name[NGX_RTMP_MAX_NAME];
u_char args[NGX_RTMP_MAX_ARGS];
ngx_event_t push_evt;
};
1.生成ngx_rtmp_auto_push_ctx_t *ctx;//上下文;
2.调用ngx_rtmp_auto_push_reconnect函数;
3.ngx_rtmp_auto_push_reconnect函数内再调用relay模块的ngx_rtmp_relay_push()函数;
4.实现断开重连的功能;
推流会话断开时:
1.源session会话(推流)断开时,清除重连事件即可;
2.目标主机session会话(拉流)断开时/* skipnon-relays & publishers */,激活断开重连定时器;
前提是ngx_rtmp_relay_module模块该session的上下文指示的publish存在,若不存在说明源session会话已断开,不需要重连;
模块名:ngx_rtmp_relay_module;
实现基于rtmp流的中继功能;
重点剖析以下几部分:
typedefstruct {
ngx_array_t pulls; /* ngx_rtmp_relay_target_t * */
ngx_array_t pushes; /* ngx_rtmp_relay_target_t * */
ngx_array_t static_pulls; /* ngx_rtmp_relay_target_t * */
ngx_array_t static_events; /* ngx_event_t* */
ngx_log_t *log;
ngx_uint_t nbuckets;
ngx_msec_t buflen;
ngx_flag_t session_relay;
ngx_msec_t push_reconnect;
ngx_msec_t pull_reconnect;
ngx_rtmp_relay_ctx_t **ctx;
}ngx_rtmp_relay_app_conf_t;
typedefstruct {
ngx_url_t url;
ngx_str_t app;
ngx_str_t name;
ngx_str_t tc_url;
ngx_str_t page_url;
ngx_str_t swf_url;
ngx_str_t flash_ver;
ngx_str_t play_path;
ngx_int_t live;
ngx_int_t start;
ngx_int_t stop;
void *tag; /* usually module reference */
void *data; /* module-specific data */
ngx_uint_t counter; /* mutableconnection counter */
}ngx_rtmp_relay_target_t;
typedefstruct {
ngx_rtmp_conf_ctx_t cctx;
ngx_rtmp_relay_target_t *target;
}ngx_rtmp_relay_static_t;
typedefstruct ngx_rtmp_relay_ctx_s ngx_rtmp_relay_ctx_t;
structngx_rtmp_relay_ctx_s {
ngx_str_t name;
ngx_str_t url;
ngx_log_t log;
ngx_rtmp_session_t *session;
ngx_rtmp_relay_ctx_t *publish;
ngx_rtmp_relay_ctx_t *play;
ngx_rtmp_relay_ctx_t *next;
ngx_str_t app;
ngx_str_t tc_url;
ngx_str_t page_url;
ngx_str_t swf_url;
ngx_str_t flash_ver;
ngx_str_t play_path;
ngx_int_t live;
ngx_int_t start;
ngx_int_t stop;
ngx_event_t push_evt;
ngx_event_t *static_evt;
void *tag;
void *data;
};
ngx_rtmp_relay_postconfiguration
详见代码
h = ngx_array_push(&cmcf->events[NGX_RTMP_HANDSHAKE_DONE]);
*h = ngx_rtmp_relay_handshake_done;
next_publish = ngx_rtmp_publish;
ngx_rtmp_publish = ngx_rtmp_relay_publish;
next_play = ngx_rtmp_play;
ngx_rtmp_play = ngx_rtmp_relay_play;
next_delete_stream =ngx_rtmp_delete_stream;
ngx_rtmp_delete_stream =ngx_rtmp_relay_delete_stream;
next_close_stream = ngx_rtmp_close_stream;
ngx_rtmp_close_stream =ngx_rtmp_relay_close_stream;
ch = ngx_array_push(&cmcf->amf);
ngx_str_set(&ch->name,"_result");
ch->handler = ngx_rtmp_relay_on_result;
ch = ngx_array_push(&cmcf->amf);
ngx_str_set(&ch->name,"_error");
ch->handler = ngx_rtmp_relay_on_error;
ch = ngx_array_push(&cmcf->amf);
ngx_str_set(&ch->name,"onStatus");
ch->handler = ngx_rtmp_relay_on_status;
ngx_rtmp_relay_push_pull
1.解析relay相关参数,生成ngx_rtmp_relay_app_conf_t* racf实例;
2.主要解析3部分:
2.2.4控制上下文
ngx_rtmp_relay_ctx_t结构内的三个成员:
ngx_rtmp_relay_ctx_t *publish;
ngx_rtmp_relay_ctx_t *play;
ngx_rtmp_relay_ctx_t *next;
hash表(开链法),结点链表结构
pull时:
警告:
push、pull应用中成对配置。若配置了pull(目标主机不存在或流不存在),而没有配置push时,往服务器推流时无法正常拉流,因为此时拉流请求会激活pull操作,且pull操作失败导致ngx_rtmp_relay_close调用,最终关闭拉流请求,虽然此时服务器存在该流。
ngx_rtmp_relay_init_process();
ngx_rtmp_relay_static_pull_reconnect();
ngx_rtmp_relay_create_connection():
1.生成ngx_rtmp_relay_ctx_t*rctx;
2.生成ngx_peer_connection_t*pc,同时获取ngx_peer_connection_t *pc;
3.生成ngx_rtmp_session_t*rs,其中设置rs->relay = 1;
返回ngx_rtmp_relay_static_pull_reconnect函数后,设置rs-> static_relay= 1;
静态拉流session,对于本机来说是推流,当该session断开时,会激活断开重连事件;
ngx_rtmp_relay_pull:
return ngx_rtmp_relay_create(s, name, target,
ngx_rtmp_relay_create_remote_ctx,
ngx_rtmp_relay_create_local_ctx);
ngx_rtmp_relay_create_remote_ctx:publish
ngx_rtmp_relay_create_local_ctx :play
ngx_rtmp_relay_push:
returnngx_rtmp_relay_create(s, name, target,
ngx_rtmp_relay_create_local_ctx,
ngx_rtmp_relay_create_remote_ctx);
ngx_rtmp_relay_create_local_ctx : publish
ngx_rtmp_relay_create_remote_ctx: play
ngx_rtmp_relay_close函数
静态pull创建的session,对于本机来说是推流会话,该会话的上下文不存放在hash表中,其中上下文的publish,play,next指针均为空。因此关闭时首先筛选静态pull的会话,即static_relay==1时,并激活重连事件。
详见代码
/* play end disconnect? */
if (ctx->publish != ctx) {
for (cctx =&ctx->publish->play; *cctx; cctx = &(*cctx)->next) {
if (*cctx == ctx) {
*cctx = ctx->next;
break;
}
}
ngx_log_debug2(NGX_LOG_DEBUG_RTMP,ctx->session->connection->log, 0,
"relay: play disconnectapp='%V' name='%V'",
&ctx->app,&ctx->name);
/* push reconnect */
if (s->relay && ctx->tag== &ngx_rtmp_relay_module &&
!ctx->publish->push_evt.timer_set)
{
ngx_add_timer(&ctx->publish->push_evt,racf->push_reconnect);
}
if (ctx->publish->play == NULL&& ctx->publish->session->relay) {
ngx_log_debug2(NGX_LOG_DEBUG_RTMP,
ctx->publish->session->connection->log, 0,
"relay: publish disconnectempty app='%V' name='%V'",
&ctx->app,&ctx->name);
ngx_rtmp_finalize_session(ctx->publish->session);
}
ctx->publish = NULL;
return;
}
详述:
1).从hash结点链表中移除ctx;
2).如果还是push中向目标主机创建的推流会话,对于本机是拉流会话,激活断开重连事件;
3).结点链表中没有拉流请求,且推流会话是中继,则断开ctx指向的推流会话(调用ngx_rtmp_finalize_session);
4).设置ctx->publish=NULL;
详见代码
/* publish end disconnect */
if (ctx->push_evt.timer_set) {
ngx_del_timer(&ctx->push_evt);
}
for (cctx = &ctx->play; *cctx; cctx= &(*cctx)->next) {
(*cctx)->publish = NULL;
ngx_rtmp_finalize_session((*cctx)->session);
}
ctx->publish = NULL;
hash = ngx_hash_key(ctx->name.data,ctx->name.len);
cctx = &racf->ctx[hash %racf->nbuckets];
for (; *cctx && *cctx != ctx; cctx= &(*cctx)->next);
if (*cctx) {
*cctx = ctx->next;
}
详述:
1).如果激活了重连事件,则取消;
2).清除结点链表中所有的拉流会话,publish=NULL且ngx_rtmp_finalize_session;
两类推流
3).设置ctx->publish=NULL;
4).清除该推流会话的hash结点;
增加联动其它请求源计数功能
详见红色部分,以下类同
struct ngx_rtmp_relay_ctx_s {
ngx_str_t name;
ngx_str_t url;
ngx_log_t log;
ngx_rtmp_session_t *session;
ngx_rtmp_relay_ctx_t *publish;
ngx_rtmp_relay_ctx_t *play;
ngx_rtmp_relay_ctx_t *next;
//hls,hlf relations
ngx_uint_t refcount;
ngx_rtmp_relay_callback_t *ls_rcb;
ngx_rtmp_relay_callback_t *free_rcb;
ngx_str_t app;
ngx_str_t tc_url;
ngx_str_t page_url;
ngx_str_t swf_url;
ngx_str_t flash_ver;
ngx_str_t play_path;
ngx_int_t live;
ngx_int_t start;
ngx_int_t stop;
ngx_event_t push_evt;
ngx_event_t *static_evt;
void *tag;
void *data;
};
其中
typedef ngx_int_t(*ngx_rtmp_publish_delete_stream_pt)
(ngx_str_t *app, ngx_str_t *name, void *conf);
typedef struct ngx_rtmp_relay_ctx_s ngx_rtmp_relay_ctx_t;
struct ngx_rtmp_relay_callback_s
{
ngx_rtmp_relay_callback_t *next;
void *conf;
ngx_rtmp_publish_delete_stream_pt pds;
} ;
联动中继
规则同rtmp拉流请求时一样,只有第一个请求时才会调用该函数,原型如下:
ngx_int_t
ngx_rtmp_relay_pull_others_open(ngx_str_t*app, ngx_str_t *name, ngx_uint_t srv,
ngx_rtmp_relay_callback_t *src_rcb,ngx_rtmp_relay_callback_t **rcb);
详述:
1.根据svr,app,name找到ngx_rtmp_conf_ctx_t(ngx_rtmp_relay_get_conf_ctx);
2.从ngx_rtmp_relay_app_conf_t->ctx查找结点:
2.1.找到,返回ngx_rtmp_relay_ctx_t,并修改refcount;
2.2.没有找到,调用ngx_rtmp_relay_create_connection返回ngx_rtmp_relay_ctx_t,并添 加hash表中;
结束中继
同rtmp拉流关闭时一样,当最后一个拉流关闭时才会调用该函数,原型如下:
ngx_int_t
ngx_rtmp_relay_pull_others_close(ngx_str_t*app, ngx_str_t *name,
ngx_uint_tsrv,ngx_rtmp_relay_callback_t *rcb);
详述:
1.根据svr,app,name找到ngx_rtmp_conf_ctx_t(ngx_rtmp_relay_get_conf_ctx);
2.找到ngx_rtmp_relay_app_conf_t->ctx,对应hash结点,refcount减1;如果rcb有值,同时移除从ls_rcb移除rcb;
3.若refcount为0且play为null,且是中继推流会话,则断开该会话(relay,推流会话);
4.调用ngx_rtmp_finalize_session断开会话,会转到ngx_rtmp_relay_close函数执行;
ngx_rtmp_relay_close函数,需要改动两个地方:
1.拉流断开时,是否关闭推流会话,在原判断的基础上增加refcount==0,例如:
if (ctx->publish->play == NULL &&
//hls,hlf relations
ctx->publish->refcount == 0 &&
ctx->publish->session->relay)
{
ngx_log_debug2(NGX_LOG_DEBUG_RTMP,
ctx->publish->session->connection->log, 0,
"relay: publishdisconnect empty app='%V' name='%V'",
&ctx->app,&ctx->name);
ngx_rtmp_finalize_session(ctx->publish->session);
}
2.推流会话关闭时,回调ls_rcb函数;该回调应用于非rtmp模块需要绑定ngx_rtmp_close_stream函数链表,例如:
//发布流断开回调
if (ctx->ls_rcb)
{
rcb = ctx->ls_rcb;
for (; rcb ; rcb = rcb->next)
{
if (rcb->pds)
{
rcb->pds(&ctx->app,&ctx->name,rcb->conf);
}
ngx_rtmp_relay_free_rcb(rcb,ctx);
}
ctx->ls_rcb = NULL;
}
文件名:ngx_http_auth_hls_module.c
模块名:ngx_http_auth_hls_module,属http模块。
该模块提供了hls流请求session管理,token认证功能,现增加联动中继功能;
以下阐述针对hls请求联动中继功能实现的修改部分。
详见红色部,以下类同
//auth_hls配置
typedef struct
{
ngx_flag_t enable; //hls认证启用标识,默认不启用
ngx_str_t url; //认证url(本地location,其内调用代理 完成认证),默认auth_hls
ngx_flag_t hls_relay; //联动hls中继,默认不启用
ngx_uint_t hls_srv; //rtmp server序号,默认0
ngx_msec_t timeout; //闲时周期,默认30秒
ngx_uint_t nbuckets; //最大hash数(开链表示,实际容量不 受限制),默认1024
ngx_pool_t *pool;
ngx_log_t *log;
ngx_http_hls_relay_t *free;
ngx_http_hls_relay_t **hls_relay_ref;
} ngx_http_auth_hls_conf_t;
//session结构体
struct ngx_http_auth_hls_session_s
{
ngx_http_auth_hls_session_t *next;
ngx_str_t session;
ngx_str_t token;
ngx_str_t host;
ngx_http_hls_relay_t *relay;
ngx_pool_t *pool;
ngx_event_t idle_timer;
};
//relay结构体
structngx_http_hls_relay_s
{
ngx_http_hls_relay_t *next;
u_char app[NGX_RTMP_MAX_NAME];
u_char name[NGX_RTMP_MAX_NAME];
ngx_uint_t refcount;
ngx_rtmp_relay_callback_t *rcb;
ngx_http_auth_hls_conf_t *conf;
ngx_http_auth_hls_session_t **sessions;
};
hls流属http协议的实时流,之前已经提供hls流会话的管理;
if(conf->hls_relay && !relay->refcount)
{
ngx_memzero(&src_cb,sizeof(ngx_rtmp_relay_callback_t));
src_cb.conf = conf;
src_cb.pds = hls_publish_delete_stream;
ngx_rtmp_relay_pull_others_open(app,name,conf->hls_srv,&src_cb,&relay->rcb);
}
relay->refcount++;
if(relay->refcount == 0)
{
if (conf->hls_relay)
{
ngx_rtmp_relay_pull_others_close(&app,
&name,conf->hls_srv,relay->rcb);
}
...
hlf流属http协议的实时流,与hls流不同在于hlf流是基于http协议的长连接;
hlf流有ngx_http_hlf_module,ngx_rtmp_hlf_module两个提供实现;
文件:ngx_http_hlf_module.c,属http模块。
//模块配置
typedef struct
{
ngx_flag_t hlf_relay; //联动hls中继,默认不启用
ngx_uint_t hlf_srv; //rtmp server序号,默认0
} ngx_http_hlf_loc_conf_t;
文件
ngx_rtmp_hlf_module.h
ngx_rtmp_hlf_module.c
ngx_rtmp_hlf_shared.c
属rtmp模块
//hlf配置
typedef struct {
ngx_flag_t enable; //启用标识(其它选项,后续增加)
ngx_int_t nbuckets; //hash桶个数,默认1024
ngx_int_t queue_max; //缓冲最大帧数,默认256,最小32
ngx_msec_t idle_timeout; //无流时保持时长 ,默认直接断开(0)
ngx_flag_t hlf_relay; //联动中继,默认off
ngx_rtmp_hlf_ctx_t **ctxs;
ngx_pool_t *pool;
ngx_rtmp_hlf_ctx_t *free_ctxs;
} ngx_rtmp_hlf_app_conf_t;
当hlf流请求时,即ngx_rtmp_hlf_add_stream函数内增加调用relay模块的ngx_rtmp_relay_pull_others_open接口,例如:
//联动中继
if (conf->hlf_relay && ctx->streams == NULL)
{
ngx_rtmp_relay_pull_others_open(app,name,srv,NULL,NULL);
}
当hlf请求关闭时,即ngx_rtmp_hlf_del_stream函数内增加调用relay模块的ngx_rtmp_relay_pull_others_close接口,例如:
//没有请求流,移除ctx
if (ctx->streams == NULL)
{
conf = ctx->conf;
if (conf->hlf_relay)
{
ngx_rtmp_relay_pull_others_close(&st->app,&st->name,ctx->hlf_srv,NULL);
}
...
源码详见:
https://download.csdn.net/download/laichanghe/10455405
QQ:19993939
备注:文档内QQ号是错的,在此纠正