Nginx-rtmp之监听端口的管理
1. 概述
监听端口属于 server 虚拟主机,它是由 server{} 块下的 listen 配置项决定的。
每监听一个 TCP 端口,都将使用一个独立的 ngx_rtmp_conf_port_t 结构体来表示:
typedef struct {
/* socket 地址家族 */
int family;
/* 监听端口 */
in_port_t port;
/* 监听的端口下对应着的所有 ngx_rtmp_conf_addr_t 地址 */
ngx_array_t addrs; /* array of ngx_rtmp_conf_addr_t */
} ngx_rtmp_conf_port_t;
一个端口,可能对应着多个地址(当主机上有多个 IP 地址时),该地址用 ngx_rtmp_conf_addr_t 表示:
typedef struct {
struct sockaddr *sockaddr;
socklen_t socklen;
ngx_rtmp_conf_ctx_t *ctx;
unsigned bind:1;
unsigned wildcard:1;
#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
unsigned ipv6only:2;
#endif
unsigned so_keepalive:2;
unsigned proxy_protocol:1;
#if (NGX_HAVE_KEEPALIVE_TUNABLE)
int tcp_keepidle;
int tcp_keepintvl;
int tcp_keepcnt;
#endif
} ngx_rtmp_conf_addr_t;
在 ngx_rtmp_core_main_conf_t 结构体中,保存着 nginx.conf 中所有需要监听的端口:
typedef struct {
...
/* 该数组保存着的元素是指向 ngx_rtmp_listen_t 结构体的指针,
* 每个元素代表一个需要监听的端口 */
ngx_array_t listen; /* ngx_rtmp_listen_t */
...
}ngx_rtmp_core_main_conf_t;
Nginx 每从 nginx.conf 中解析中一个 listen 配置项,就把该配置项的值组织成 ngx_rtmp_listen_t 结构体添加到
ngx_rtmp_core_main_conf_t 的成员 listen 数组中,如下:
typedef struct {
u_char sockaddr[NGX_SOCKADDRLEN];
socklen_t socklen;
/* server ctx */
ngx_rtmp_conf_ctx_t *ctx;
unsigned bind:1;
unsigned wildcard:1;
#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
unsigned ipv6only:2;
#endif
unsigned so_keepalive:2;
unsigned proxy_protocol:1;
#if (NGX_HAVE_KEEPALIVE_TUNABLE)
int tcp_keepidle;
int tcp_keepintvl;
int tcp_keepcnt;
#endif
} ngx_rtmp_listen_t;
对同一个端口 1935,可以同时监听 127.0.0.1:8000、192.168.56.101:1935 这两个地址,当一台物理机器具备多个 IP
地址时这是很有用的。对应到 RTMP 的框架上,Nginx 是使用 ngx_rtmp_conf_addr_t 结构体来表示一个对应着具体地址
的监听端口,因此,一个 ngx_rtmp_conf_port_t 将会对应多个 ngx_rtmp_conf_addr_t,而 ngx_rtmp_conf_addr_t 是
以动态数组的形式保存在 ngx_rtmp_conf_port_t 中的 addrs 成员中的。
2. 监听端口的解析与管理
2.1 ngx_rtmp_core_listen:解析 listen 配置项
static char *ngx_rtmp_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
size_t len, off;
in_port_t port;
ngx_str_t *value;
ngx_url_t u;
ngx_uint_t i, m;
ngx_module_t **modules;
struct sockaddr *sa;
ngx_rtmp_listen_t *ls;
struct sockaddr_in *sin;
ngx_rtmp_core_main_conf_t *cmcf;
#if (NGX_HAVE_INET6)
struct sockaddr_in6 *sin6;
#endif
/* 指向 "listen xxx" 配置项,如 "listen 1935" */
value = cf->args->elts;
ngx_memzero(&u, sizeof(ngx_url_t));
/* 将监听的端口赋给 u.url,如 "1935" */
u.url = value[1];
u.listen = 1;
/* 解析该监听的端口 */
if (ngx_parse_url(cf->pool, &u) != NGX_OK) {
if (u.err) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"%s in \"%V\" of the \"listen\" directive",
u.err, &u.url);
}
return NGX_CONF_ERROR;
}
cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module);
ls = cmcf->listen.elts;
/* 遍历listen数组,若有元素,将待监听的地址与数组中的保存的地址进行比较,
* 若有相同的,则返回错误,否则添加到listen数组中 */
for (i = 0; i < cmcf->listen.nelts; i++) {
sa = (struct sockaddr *) ls[i].sockaddr;
if (sa->sa_family != u.family) {
continue;
}
switch (sa->sa_family) {
#if (NGX_HAVE_INET6)
case AF_INET6:
off = offsetof(struct sockaddr_in6, sin6_addr);
len = 16;
sin6 = (struct sockaddr_in6 *) sa;
port = sin6->sin6_port;
break;
#endif
default: /* AF_INET */
off = offsetof(struct sockaddr_in, sin_addr);
len = 4;
sin = (struct sockaddr_in *) sa;
port = sin->sin_port;
break;
}
if (ngx_memcmp(ls[i].sockaddr + off, (u_char *) &u.sockaddr + off, len)
!= 0)
{
continue;
}
if (port != u.port) {
continue;
}
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"duplicate \"%V\" address and port pair", &u.url);
return NGX_CONF_ERROR;
}
ls = ngx_array_push(&cmcf->listen);
if (ls == NULL) {
return NGX_CONF_ERROR;
}
ngx_memzero(ls, sizeof(ngx_rtmp_listen_t));
ngx_memcpy(ls->sockaddr, (u_char *) &u.sockaddr, u.socklen);
ls->socklen = u.socklen;
ls->wildcard = u.wildcard;
ls->ctx = cf->ctx;
#if (nginx_version >= 1009011)
modules = cf->cycle->modules;
#else
modules = ngx_modules;
#endif
for (m = 0; modules[m]; m++) {
if (modules[m]->type != NGX_RTMP_MODULE) {
continue;
}
}
/* 若监听端口后面还附加有其他条件,则进行处理,否则直接返回 */
for (i = 2; i < cf->args->nelts; i++) {
if (ngx_strcmp(value[i].data, "bind") == 0) {
ls->bind = 1;
continue;
}
if (ngx_strncmp(value[i].data, "ipv6only=o", 10) == 0) {
#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
struct sockaddr *sa;
u_char buf[NGX_SOCKADDR_STRLEN];
sa = (struct sockaddr *) ls->sockaddr;
if (sa->sa_family == AF_INET6) {
if (ngx_strcmp(&value[i].data[10], "n") == 0) {
ls->ipv6only = 1;
} else if (ngx_strcmp(&value[i].data[10], "ff") == 0) {
ls->ipv6only = 0;
} else {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid ipv6only flags \"%s\"",
&value[i].data[9]);
return NGX_CONF_ERROR;
}
ls->bind = 1;
} else {
len = ngx_sock_ntop(sa,
#if (nginx_version >= 1005003)
ls->socklen,
#endif
buf, NGX_SOCKADDR_STRLEN, 1);
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"ipv6only is not supported "
"on addr \"%*s\", ignored", len, buf);
}
continue;
#else
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"bind ipv6only is not supported "
"on this platform");
return NGX_CONF_ERROR;
#endif
}
if (ngx_strncmp(value[i].data, "so_keepalive=", 13) == 0) {
if (ngx_strcmp(&value[i].data[13], "on") == 0) {
ls->so_keepalive = 1;
} else if (ngx_strcmp(&value[i].data[13], "off") == 0) {
ls->so_keepalive = 2;
} else {
#if (NGX_HAVE_KEEPALIVE_TUNABLE)
u_char *p, *end;
ngx_str_t s;
end = value[i].data + value[i].len;
s.data = value[i].data + 13;
p = ngx_strlchr(s.data, end, ':');
if (p == NULL) {
p = end;
}
if (p > s.data) {
s.len = p - s.data;
ls->tcp_keepidle = ngx_parse_time(&s, 1);
if (ls->tcp_keepidle == (time_t) NGX_ERROR) {
goto invalid_so_keepalive;
}
}
s.data = (p < end) ? (p + 1) : end;
p = ngx_strlchr(s.data, end, ':');
if (p == NULL) {
p = end;
}
if (p > s.data) {
s.len = p - s.data;
ls->tcp_keepintvl = ngx_parse_time(&s, 1);
if (ls->tcp_keepintvl == (time_t) NGX_ERROR) {
goto invalid_so_keepalive;
}
}
s.data = (p < end) ? (p + 1) : end;
if (s.data < end) {
s.len = end - s.data;
ls->tcp_keepcnt = ngx_atoi(s.data, s.len);
if (ls->tcp_keepcnt == NGX_ERROR) {
goto invalid_so_keepalive;
}
}
if (ls->tcp_keepidle == 0 && ls->tcp_keepintvl == 0
&& ls->tcp_keepcnt == 0)
{
goto invalid_so_keepalive;
}
ls->so_keepalive = 1;
#else
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"the \"so_keepalive\" parameter accepts "
"only \"on\" or \"off\" on this platform");
return NGX_CONF_ERROR;
#endif
}
ls->bind = 1;
continue;
#if (NGX_HAVE_KEEPALIVE_TUNABLE)
invalid_so_keepalive:
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid so_keepalive value: \"%s\"",
&value[i].data[13]);
return NGX_CONF_ERROR;
#endif
}
if (ngx_strcmp(value[i].data, "proxy_protocol") == 0) {
ls->proxy_protocol = 1;
continue;
}
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"the invalid \"%V\" parameter", &value[i]);
return NGX_CONF_ERROR;
}
return NGX_CONF_OK;
}
2.2 ngx_rtmp_add_ports: 将监听端口添加到 ports 数组中
static ngx_int_t ngx_rtmp_add_ports(ngx_conf_t *cf, ngx_array_t *ports,
ngx_rtmp_listen_t *listen)
{
in_port_t p;
ngx_uint_t i;
struct sockaddr *sa;
struct sockaddr_in *sin;
ngx_rtmp_conf_port_t *port;
ngx_rtmp_conf_addr_t *addr;
#if (NGX_HAVE_INET6)
struct sockaddr_in6 *sin6;
#endif
sa = (struct sockaddr *) &listen->sockaddr;
switch (sa->sa_family) {
#if (NGX_HAVE_INET6)
case AF_INET6:
sin6 = (struct sockaddr_in6 *) sa;
p = sin6->sin6_port;
break;
#endif
default: /* AF_INET */
sin = (struct sockaddr_in *) sa;
p = sin->sin_port;
break;
}
/* 首先比较 ports 数组中是否已经存在该待监听端口的,若已经存在,
* 则不用再次添加了,直接跳到 found 下,否则添加一个新的端口 */
port = ports->elts;
for (i = 0; i < ports->nelts; i++) {
if (p == port[i].port && sa->sa_family == port[i].family) {
/* a port is already in the port list */
port = &port[i];
goto found;
}
}
/* add a port to the port list */
port = ngx_array_push(ports);
if (port == NULL) {
return NGX_ERROR;
}
port->family = sa->sa_family;
port->port = p;
if (ngx_array_init(&port->addrs, cf->temp_pool, 2,
sizeof(ngx_rtmp_conf_addr_t))
!= NGX_OK)
{
return NGX_ERROR;
}
found:
/* 这里有可能一个端口对应几个 IP 地址 */
addr = ngx_array_push(&port->addrs);
if (addr == NULL) {
return NGX_ERROR;
}
addr->sockaddr = (struct sockaddr *) &listen->sockaddr;
addr->socklen = listen->socklen;
addr->ctx = listen->ctx;
addr->bind = listen->bind;
addr->wildcard = listen->wildcard;
addr->so_keepalive = listen->so_keepalive;
addr->proxy_protocol = listen->proxy_protocol;
#if (NGX_HAVE_KEEPALIVE_TUNABLE)
addr->tcp_keepidle = listen->tcp_keepidle;
addr->tcp_keepintvl = listen->tcp_keepintvl;
addr->tcp_keepcnt = listen->tcp_keepcnt;
#endif
#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
addr->ipv6only = listen->ipv6only;
#endif
return NGX_OK;
}
2.3 ngx_rtmp_optimize_servers
static char *ngx_rtmp_optimize_servers(ngx_conf_t *cf, ngx_array_t *ports)
{
ngx_uint_t i, p, last, bind_wildcard;
ngx_listening_t *ls;
ngx_rtmp_port_t *mport;
ngx_rtmp_conf_port_t *port;
ngx_rtmp_conf_addr_t *addr;
port = ports->elts;
for (p = 0; p < ports->nelts; p++) {
ngx_sort(port[p].addrs.elts, (size_t) port[p].addrs.nelts,
sizeof(ngx_rtmp_conf_addr_t), ngx_rtmp_cmp_conf_addrs);
addr = port[p].addrs.elts;
last = port[p].addrs.nelts;
/*
* if there is the binding to the "*:port" then we need to bind()
* to the "*:port" only and ignore the other bindings
*/
if (addr[last - 1].wildcard) {
addr[last - 1].bind = 1;
bind_wildcard = 1;
} else {
bind_wildcard = 0;
}
i = 0;
while (i < last) {
if (bind_wildcard && !addr[i].bind) {
i++;
continue;
}
/* 创建并初始化 ngx_listening_t 结构体 */
ls = ngx_create_listening(cf, addr[i].sockaddr, addr[i].socklen);
if (ls == NULL) {
return NGX_CONF_ERROR;
}
ls->addr_ntop = 1;
ls->handler = ngx_rtmp_init_connection;
ls->pool_size = 4096;
/* TODO: error_log directive */
ls->logp = &cf->cycle->new_log;
ls->log.data = &ls->addr_text;
ls->log.handler = ngx_accept_log_error;
ls->keepalive = addr[i].so_keepalive;
#if (NGX_HAVE_KEEPALIVE_TUNABLE)
ls->keepidle = addr[i].tcp_keepidle;
ls->keepintvl = addr[i].tcp_keepintvl;
ls->keepcnt = addr[i].tcp_keepcnt;
#endif
#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
ls->ipv6only = addr[i].ipv6only;
#endif
mport = ngx_palloc(cf->pool, sizeof(ngx_rtmp_port_t));
if (mport == NULL) {
return NGX_CONF_ERROR;
}
ls->servers = mport;
if (i == last - 1) {
mport->naddrs = last;
} else {
mport->naddrs = 1;
i = 0;
}
switch (ls->sockaddr->sa_family) {
#if (NGX_HAVE_INET6)
case AF_INET6:
if (ngx_rtmp_add_addrs6(cf, mport, addr) != NGX_OK) {
return NGX_CONF_ERROR;
}
break;
#endif
default: /* AF_INET */
if (ngx_rtmp_add_addrs(cf, mport, addr) != NGX_OK) {
return NGX_CONF_ERROR;
}
break;
}
addr++;
last--;
}
}
return NGX_CONF_OK;
}
Nginx-rtmp之监听端口的管理的更多相关文章
- SRS之监听端口的管理:RTMP
1. 监听端口管理的入口函数 监听端口的管理入口在 run_master 函数中,如下: int run_master() { ... if ((ret = _srs_server->liste ...
- Nginx 中 fastcgi_pass 监听端口 unix socket和tcp socket差
Nginx 中 fastcgi_pass 监听端口 unix socket和tcp socket差别 Nginx连接fastcgi的方式有2种:unix domain socket和TCP,Uni ...
- nginx监听端口和反向代理端口的权限问题
Linux的SELinux安全性控制除作用于文件系统外还作用于端口,这使得那些作为服务启动的进程只能在规定的几个端口上监听.为叙述方便我们称之为受控端口. nginx监听端口 要查看当前有哪些受控端口 ...
- 修改Oracle监听端口
修改oracle监听端口 修改端口号的整体步骤:1.1 查看当前监听的状态1.2 停止监听1.3 修改监听文件的端口号1.4 修改初始化参数local_listener1.5 重启 ...
- Windows下修改Oracle监听端口
先放开防火墙的端口,再来改监听端口. 只有一个实例: net stop OracleOraDb11g_home1TNSListener 再运行 NET Manager 修改实例名与Listener的端 ...
- Oracle RAC 11gR2 修改本地及SCAN监听端口
昨天同事说有套RAC集群客户要求修改数据库的监听端口,在处理的过程中,发现网上的相关资料都不是很全面,所以整理了一下,希望给其他有需要的朋友提供一点有利的参考资料.具体操作过程如下: ...
- Apache配置多个监听端口和不同的网站目录的简单方法(转)
转自http://www.waaqi.com/archives/707.html 由于开发的多项目,每个项目又要独立,要用根目录地址. 所以这时候我们需要配置多个不同目录的Apache,如果是外部网可 ...
- .net core Kestrel宿主服务器自定义监听端口配置
在.net core的web程序中,除了可以在项目中硬编码服务器的监听端口外,还可以在外部通过json文件配置. 方法如下: 第一步:在项目中新建一个名为Hosting.json的文件.当然,文件名可 ...
- 黄聪:windows下使用xampp3.2.2配置多个监听端口和不同的网站目录
windows下使用xampp3.2.2配置多个监听端口和不同的网站目录 一:配置Apache文件httpd.conf 打开Apache的配置文件httpd.conf,可以通过点击xampp的Apac ...
随机推荐
- MYSQL 修改语句(数据)
修改数据(UPDATE) 如果你失忆了,希望你能想起曾经为了追求梦想的你. 我们玩QQ.微信.淘宝等等,都会有一个操作:修改信息 淘宝常用的嘛,新增了收货地址,也可以修改它,微信/ ...
- postgres 索引
索引是一种特殊的查询表,可以使用搜索引擎的数据库以加快数据检索.简单地说,索引是表中的数据的一个指针,在一个数据库中的索引是非常相似,如:一本书的目录. 例如,如果想在一本书中引用的所有页面讨论某个话 ...
- Action向视图传值的6种方式(转)
在使用ASP.NET MVC进行项目开发时,经常会碰到从Action向视图传值的问题,今天我就把我所知道的方式总结了一下,分成了以下六种: 1.使用ViewData进行传值 在Action中,有如下代 ...
- bisect:维护一个有序的列表
介绍 bisect模块实现了一个算法来向列表中插入元素,同时仍然保证列表有序 有序插入 import bisect ''' 可以使用bisect.insort向一个列表中插入元素 ''' values ...
- nginx的反向代理的优势,特点于原理(一)
说到反向代理,首先先说一下反向代理的概念 反向代理(Reverse Proxy)方式是指以代理服务器来接受客户端的连接请求,然后将请求转发给网络上的web服务器(可能是apache,nginx,tom ...
- Linux计划任务与压缩归档
计划任务分为两种形式 第一种:定时性的:也就是例行,每隔一定的周期就要重复来做这个任务. 第二种:突发性的:临时决定,只执行一次的任务. 用到的命令有两个 at:它是一个可以处理仅执行一次的任务就结束 ...
- 取消任务(Task)
private static void TaskCancelDemo() { //向应该被取消的 System.Threading.CancellationToken 发送信号 Cancellatio ...
- Python——print函数输出对齐问题
原创声明:本文系博主原创文章,转载及引用请注明出处. 当我们使用print函数时,若指定输出宽度,例如: >>> import math >>> print('|P ...
- Mybatis config.xml 配置
<!-- xml标准格式 --><?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE ...
- Django:报错 raise MigrationSchemaMissing("Unable to create the django_migrations table (%s)" % exc)
Django 执行迁移生成表: python manage.py migrate 报错: raise MigrationSchemaMissing("Unable to create the ...