srs源码分析3-srs的启动
本文分析的srs版本是0.6.0
srs源码分析1-搭建环境
srs源码分析2-浅析state_threads
srs源码分析3-srs的启动
srs源码分析4-客户端的连接
srs源码分析5-handshake
srs源码分析6-connect
以下正在写作中。。。
srs源码分析7-create stream
srs源码分析8-推流-publish
srs源码分析9-推流-unpublish
srs源码分析10-拉流-play
srs源码分析11-拉流-pause
srs源码分析12-转发-forward
先从main函数开始分析,分析srs的启动过程。
int main(int argc, char** argv){int ret = ERROR_SUCCESS;/*注册SIGHUB信号处理函数,用于重载配置文件。*/signal(SIGNAL_RELOAD, handler);/*解析命令行参数,解析配置文件。*/if ((ret = config->parse_options(argc, argv)) != ERROR_SUCCESS) {return ret;}/*初始化st和日志*/if ((ret = _server()->initialize()) != ERROR_SUCCESS) {return ret;}/*开始监听客户端的连接*/if ((ret = _server()->listen()) != ERROR_SUCCESS) {return ret;}/*运行主协程*/if ((ret = _server()->cycle()) != ERROR_SUCCESS) {return ret;}return 0;
}
在main函数中做了如下几件事:
- 注册SIGHUB信号处理函数,用于在srs运行期间使用该信号重载配置文件。
- 解析命令行参数,解析配置文件。
- 初始化st库,设置日志id。
- 创建listen协程,用于监听客户端的连接。
- 运行主协程
SIGHUB信号的处理
#define SIGNAL_RELOAD SIGHUPsignal(SIGNAL_RELOAD, handler); /*注册信号处理函数*//*信号处理函数*/
void handler(int signo)
{srs_trace("get a signal, signo=%d", signo);_server()->on_signal(signo);
}void SrsServer::on_signal(int signo)
{if (signo == SIGNAL_RELOAD) { signal_reload = true; /*将配置重载标志设置为true,表示需要重载配置文件。*/}
}
在main函数的开始位置,会为SIGHUB
信号注册处理函数。当此信号触发时,会将signal_reload标志置为true。
初始化SrsServer
int SrsServer::initialize()
{int ret = ERROR_SUCCESS;/*给st设置epoll*/if (st_set_eventsys(ST_EVENTSYS_ALT) == -1) {ret = ERROR_ST_SET_EPOLL;srs_error("st_set_eventsys use linux epoll failed. ret=%d", ret);return ret;}srs_verbose("st_set_eventsys use linux epoll success");/*初始化st协程库*/if(st_init() != 0){ ret = ERROR_ST_INITIALIZE;srs_error("st_init failed. ret=%d", ret);return ret;}srs_verbose("st_init success");/*设置log id*/log_context->generate_id();srs_info("log set id success");return ret;
}
在这个函数中,主要是初始化st库,在st_init()函数中会创建idle协程,当没有就绪协程时,会进入idle协程执行,同时会将主线程封装称为主协程,以便可以调度。
创建listen协程
int SrsServer::listen()
{int ret = ERROR_SUCCESS;SrsConfDirective* conf = NULL;/*从配置文件中读取监听端口信息*/conf = config->get_listen();srs_assert(conf);/*关闭之前的listener,在重载配置文件时才有用。*/close_listeners();/*创建监听器listener*/for (int i = 0; i < (int)conf->args.size(); i++) {SrsListener* listener = new SrsListener(this, SrsListenerStream);listeners.push_back(listener);/*获取监听端口号*/int port = ::atoi(conf->args.at(i).c_str()); /*开始监听*/if ((ret = listener->listen(port)) != ERROR_SUCCESS) { srs_error("listen at port %d failed. ret=%d", port, ret);return ret;}}return ret;
}
srs的配置文件中默认监听的是1935端口号,srs可以监听多个端口号。
在srs启动的时候会调用SrsServer::listen()函数,在重载配置文件时,也会调用此函数。重载配置文件时,监听的端口号可能发生了变化,所以需要将之前的listener关闭,重新建立listener。
int SrsListener::listen(int _port)
{int ret = ERROR_SUCCESS;port = _port;/*创建监听套接字*/if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {ret = ERROR_SOCKET_CREATE;srs_error("create linux socket error. ret=%d", ret);return ret;}srs_verbose("create linux socket success. fd=%d", fd);/*设置套接字选项*/int reuse_socket = 1; if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse_socket, sizeof(int)) == -1) {ret = ERROR_SOCKET_SETREUSE;srs_error("setsockopt reuse-addr error. ret=%d", ret);return ret;}srs_verbose("setsockopt reuse-addr success. fd=%d", fd);sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(port);addr.sin_addr.s_addr = INADDR_ANY;/*绑定监听地址*/if (bind(fd, (const sockaddr*)&addr, sizeof(sockaddr_in)) == -1) {ret = ERROR_SOCKET_BIND;srs_error("bind socket error. ret=%d", ret);return ret;}srs_verbose("bind socket success. fd=%d", fd);if (::listen(fd, SERVER_LISTEN_BACKLOG) == -1) {ret = ERROR_SOCKET_LISTEN;srs_error("listen socket error. ret=%d", ret);return ret;}srs_verbose("listen socket success. fd=%d", fd);/*将监听套接字封装到协程中的套接字中*/if ((stfd = st_netfd_open_socket(fd)) == NULL){ret = ERROR_ST_OPEN_SOCKET;srs_error("st_netfd_open_socket open socket failed. ret=%d", ret);return ret;}srs_verbose("st open socket success. fd=%d", fd);/*创建一个协程,用于监听这个套接字。*/if ((tid = st_thread_create(listen_thread, this, 1, 0)) == NULL) {ret = ERROR_ST_CREATE_LISTEN_THREAD;srs_error("st_thread_create listen thread error. ret=%d", ret);return ret;}srs_verbose("create st listen thread success.");srs_trace("server started, listen at port=%d, fd=%d", port, fd);return ret;
}
创建listen socket fd,并将其封装成st中的_st_netfd_t,最后创建一个协程用于监听客户端的连接。
void* SrsListener::listen_thread(void* arg)
{SrsListener* obj = (SrsListener*)arg;srs_assert(obj != NULL);obj->loop = true;obj->listen_cycle(); /*执行监听事件循环*/return NULL;
}
协程的入口函数,注意此函数必须是类的静态成员函数。
void SrsListener::listen_cycle()
{int ret = ERROR_SUCCESS;log_context->generate_id();srs_trace("listen cycle start, port=%d, type=%d, fd=%d", port, type, fd);while (loop) {/*调用st中的st_accept函数,等待客户端的连接。*/st_netfd_t client_stfd = st_accept(stfd, NULL, NULL, ST_UTIME_NO_TIMEOUT); if(client_stfd == NULL){srs_warn("ignore accept thread stoppped for accept client error");continue;}srs_verbose("get a client. fd=%d", st_netfd_fileno(client_stfd));/*通知server,有新的客户端连接进来。*/if ((ret = server->accept_client(type, client_stfd)) != ERROR_SUCCESS) {srs_warn("accept client error. ret=%d", ret);continue;}srs_verbose("accept client finished. conns=%d, ret=%d", (int)conns.size(), ret);}
}
listen协程会"阻塞"在st_accept函数上,当有客户端连接进来时,listen协程会从st_accept返回。
运行主协程
int SrsServer::cycle()
{int ret = ERROR_SUCCESS;while (true) {st_usleep(SRS_TIME_RESOLUTION_MS * 1000); /*睡眠500ms*/srs_update_system_time_ms(); /*更新当前时间*/if (signal_reload) { /*是否重载载入配置文件*/signal_reload = false;srs_info("get signal reload, to reload the config.");if ((ret = config->reload()) != ERROR_SUCCESS) { /*重新载入配置文件*/srs_error("reload config failed. ret=%d", ret);return ret;}srs_trace("reload config success.");}}return ret;
}
在_server()->listen()中创建listen协程后,此协程还没有得到CPU的执行权,会main函数中接着执行。执行到SrsServer::cycle函数后,主协程
会进入while循环。当主协程
执行st_usleep函数时,会让出CPU的执行权,st会重新调度就绪队列中的协程。此时就绪队列中只有上面刚刚创建的listen协程,listen协程获取CPU执行权后,会一直执行到st_accept函数,因为此时没有客户端连接,所以listen协程会让出CPU执行权,再次进入st的调度程序中。此时就绪队列中没有任何就绪的协程,所以CPU的执行权进入了idle协程,idle协程会通过超时阻塞在epoll_wait上等待客户端的连接,使用了带有超时的epoll_wait,所以idle协程可以处理主协程
的睡眠事件。
主协程
在睡眠的同时会监视signal_reload变量,当通过SIGHUB信号将其设置为true时,主协程
会进入配置文件重载处理程序中。
总结
srs启动后会有三个重要的协程被创建,分别是:idle协程、listen协程、主协程
。
idle协程:在没有就绪协程时执行,用于监听读、写、定时器事件。
listen协程:用于监听客户端的连接。
主协程
:用于更新时间和配置文件的重载。
srs源码分析3-srs的启动相关推荐
- janusgraph源码分析1-下载编译启动 1
date: 2018-04-26 title: "janusgraph源码分析1-下载编译启动" author: "邓子明" tags: - 源码 - janu ...
- SRS源码分析-rtmp转rtc流程
前言 SRS4.0支持将RTMP流转换成RTC流,本文将结合源码分析下这个过程. 配置 首先,需要在SRS4.0的启动配置文件里面开启RTC Server和RTC 能力,可以参考官方提供的配置文件./ ...
- Solr初始化源码分析-Solr初始化与启动
用solr做项目已经有一年有余,但都是使用层面,只是利用solr现有机制,修改参数,然后监控调优,从没有对solr进行源码级别的研究.但是,最近手头的一个项目,让我感觉必须把solrn内部原理和扩展机 ...
- Android Instrumentation源码分析(附Activity启动流程)
转载请注明出处:http://blog.csdn.net/ahence/article/details/54959235 Instrumentation概念 官方说明 Instrumentation类 ...
- uboot源码分析(基于S5PV210)之启动第一阶段
目录 一.start.S引入 1.u-boot.lds中找到start.S入口 2.SourceInsight中如何找到文件 3.SI中找文件技巧 二.start.S解析 1.不简单的头文件包含 2. ...
- Android 双开沙箱 VirtualApp 源码分析(四)启动插件 Service
上一章:Android 双开沙箱 VirtualApp 源码分析(三)App 启动 原生 Service 创建过程 首先有必要了解一下原生 framework 对 Service 的创建,因为在 VA ...
- SpringMVC源码分析_1 SpringMVC容器启动和加载原理
SpringMVC源码分析_1 SpringMVC启动和加载原理 ...
- k8s garbage collector源码分析(1)-启动分析
k8s garbage collector分析(1)-启动分析 garbage collector介绍 Kubernetes garbage collector即垃圾收集器,存在于kube-contr ...
- Spring Cloud Eureka 源码分析(一) 服务端启动过程
2019独角兽企业重金招聘Python工程师标准>>> 一. 前言 我们在使用Spring Cloud Eureka服务发现功能的时候,简单的引入maven依赖,且在项目入口类根据服 ...
最新文章
- [导入][转]好企业是什么样?
- TClientDataSet[28]: 读写其他格式的 XML 文件
- Ext.Net ASP.NET
- Java 中正确获取中文字符串长度
- 产品经理懂技术=流氓会武术(zz)
- java、sqlserver复习
- 铺磁砖,给定M*N的格子,用u*v的瓷砖去铺满,有多少种铺法
- 迅为4412开发板开发笔记--开发板与主机和ubuntu的ping通
- Elasticsearch实践(四):IK分词
- SI5341驱动(verilog)
- 常用的jar包下载网站(自用)
- gopup是疫情经济生活搜索指数数据接口
- 不用爬虫,也能写一个聚合搜索引擎
- 为什么越来越多的人放弃欧美市场,转做Starday日本市场?
- 关于SES2000找管线定位问题的实验
- android核心版本特性(官网)
- hadoop - hadoop2.6 伪分布式 示例 wordcount 分词 和 hdfs常用操作命令
- 【算力网络】算力网络的发展愿景及目标
- 《自拍教程44》Python adb一键截取Logcat日志
- 软电话 开源的和免费的大全