srs源码分析3-srs的启动

srs源码分析3-srs的启动先从 main 函数开始分析 分析 srs 的启动过程 intmain intargc charargv intret ERROR SUCCESS 注册 SIGHUB 信号处理函数 用于重载配置文件 signal SIGNAL RELOAD handler 解析命令行参数 解析配置文件 if ret config gt parse options argc argv ERROR SUCCESS returnret

本文分析的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 SIGHUP signal(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协程:用于监听客户端的连接。

主协程:用于更新时间和配置文件的重载。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/212271.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月18日 下午8:25
下一篇 2026年3月18日 下午8:25


相关推荐

  • 订单支付功能测试

    订单支付功能测试支付金额1.小于最小值,如:小于0.012.大于最大值/金额上限3.无实际意义金额,如0元4.格式错误(负数、非数字)5.余额小于实际需要支付的金额6.超过第三方支付接口当日消费/单笔消费金额支付接口第三方接口,微信/支付宝/网银系统/post机终端服务→可以参照:https://mp.csdn.net/postedit/100169648…

    2022年6月6日
    155
  • 计算机命令netstat,电脑netstat命令的使用方法

    计算机命令netstat,电脑netstat命令的使用方法netstat 命令有什么作用 netstat 一般用于显示与 IP TCP UDP 和 ICMP 协议相关的统计数据 一般用于检测本机各端口的网络连接情况 接下去和大家分享一下 windows 电脑 netstat 命令的使用方法 具体方法如下 1 首先我们介绍第一条命令用法 执行命令 netstat a 显示所有连接和监听端口 LISTEN 在监听状态中 ESTABLISHED 已建立连

    2026年3月18日
    2
  • softmax 损失函数与梯度推导「建议收藏」

    softmax 损失函数与梯度推导「建议收藏」softmax与svm很类似,经常用来做对比,svm的lossfunction对wx的输出s使用了hingefunction,即max(0,-),而softmax则是通过softmaxfunction对输出s进行了概率解释,再通过crossentropy计算lossfunction。将score映射到概率的softmaxfunction:,其中,,j指代i-thclass。…

    2022年6月26日
    68
  • 关于cBridge2.0,你不能错过的关键信息(二)!

    关于cBridge2.0,你不能错过的关键信息(二)!我们之前讨论了cBridge2.0的两种流动性模型,还深入探讨了「自管」流动性模型的设计挑战。今天,我们详细聊聊针对该模型设计挑战的解决方案。首先,我们从节点协调和操作问题开始。1/n上篇ELI5短文中我们提到,“cBridge2.0是第一个也是唯一一个允许流动性提供者(LP)在「自管」和「共管」流动性模型之间自由选择的跨链架构。在「自管」模式下,也就是「非托管」模式,LP可以100%地控制其流动性。为此,每个LP须要在服务器中运行一个cBridge节点「程序」…

    2022年6月4日
    35
  • django的orm查询方法_django获取get请求参数

    django的orm查询方法_django获取get请求参数前言查找是数据库操作中一个非常重要的技术。查询一般就是使用filter、exclude以及get三个方法来实现。我们可以在调用这些方法的时候传递不同的参数来实现查询需求。在ORM层面,这些查询条件都

    2022年7月31日
    7
  • 机器学习之决策树原理和sklearn实践

    1.场景描述时间:早上八点,地点:婚介所‘闺女,我有给你找了个合适的对象,今天要不要见一面?’‘多大?’‘26岁’‘长的帅吗?’‘还可以,不算太帅’‘工资高吗?’‘略高于平均水平’

    2021年12月30日
    50

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号