SRS之RTMP的TCP线程 分析可知,SRS 接受客户端的连接后创建了一个线程:conn,用于处理与客户端的 RTMP 连接。

本文的分析是基于该配置文件的:

listen              1935;
max_connections 1000;
daemon off;
srs_log_tank console;
vhost __defaultVhost__ {
}

该配置文件仅使能 rtmp 直播推流功能。

1. 关系图

2. RTMP 连接处理线程 conn 之主循环

2.1 conn 的线程函数: SrsThread::thread_fun

void* SrsThread::thread_fun(void* arg)
{
SrsThread* obj = (SrsThread*)arg;
srs_assert(obj); /* 这里进入线程的主循环开始处理与客户端的请求 */
obj->thread_cycle(); // for valgrind to detect.
SrsThreadContext* ctx = dynamic_cast<SrsThreadContext*>(_srs_context);
if (ctx) {
ctx->clear_cid();
} st_thread_exit(NULL); return NULL;
}

2.2 线程主循环:SrsThread::thread_cycle

void SrsThread::thread_cycle()
{
int ret = ERROR_SUCCESS; _srs_context->generate_id();
srs_info("thread %s cycle start", _name); _cid = _srs_context->get_id(); srs_assert(handler);
/* handler 是指向 ISrsThreadHandler 类对象的指针,由图 1 知,
* handler 指向子类 SrsOneCycleThread 对象的 this 指针,因此,
* 这里调用的是 SrsOneCycleThread 的 on_thread_start 函数 */
handler->on_thread_start(); // thread is running now.
really_terminated = false; // wait for cid to ready, for parent thread to get the cid.
while (!can_run && loop) {
st_usleep(10 * 1000);
} while (loop) {
/* 调用子类 SrsOneCycleThread 的 on_before_cycle 函数 */
if ((ret = handler->on_before_cycle()) != ERROR_SUCCESS) {
srs_warn("thread %s on before cycle failed, ignored and retry, ret=%d",
_name, ret);
goto failed;
}
srs_info("thread %s on before cycle success", _name); /* 这里真正开始处理与客户端的 RTMP 通信 */
if ((ret = handler->cycle()) != ERROR_SUCCESS) {
if (!srs_is_client_gracefully_close(ret) && !srs_is_system_control_error(ret))
{
srs_warn("thread %s cycle failed, ignored and retry, ret=%d", _name, ret);
}
goto failed;
}
srs_info("thread %s cycle success", _name); if ((ret = handler->on_end_cycle()) != ERROR_SUCCESS) {
srs_warn("thread %s on end cycle failed, ignored and retry, ret=%d",
_name, ret);
goto failed;
}
srs_info("thread %s on end cycle success", _name); failed:
if (!loop) {
break;
} // to improve performance, donot sleep when interval is zero.
// @see: https://github.com/ossrs/srs/issues/237
if (cycle_interval_us != 0) {
st_usleep(cycle_interval_us);
}
} // readly terminated now.
really_terminated = true; handler->on_thread_stop();
srs_info("thread %s cycle finished", _name);
}

2.3 SrsOneCycleThread::on_thread_start

void SrsOneCycleThread::on_thread_start()
{
/* handler 是 ISrsOneCycleThreadHandler 类的指针,该类的子类为
* SrsConnection,但是该类没有实现 on_thread_start 函数,因此
* 仍然调用父类的 on_thread_start */
handler->on_thread_start();
} void ISrsOneCycleThreadHandler::on_thread_start()
{
}

2.4 SrsOneCycleThread::on_before_cycle

int SrsOneCycleThread::on_before_cycle()
{
/* 子类 SrsConnection 没有实现 on_before_cycle 函数,因此
* 调用父类 ISrsOneCycleThreadHandler 的 on_before_cycle */
return handler->on_before_cycle();
} int ISrsOneCycleThreadHandler::on_before_cycle()
{
return ERROR_SUCCESS;
}

3. RTMP 事件处理循环

3.1 SrsOneCycleThread::cycle

int SrsOneCycleThread::cycle()
{
/* 调用子类 SrsConnection 实现的 cycle() 函数 */
int ret = handler->cycle();
pthread->stop_loop();
return ret;
}

3.2 SrsConnection::cycle

int SrsConnection::cycle()
{
int ret = ERROR_SUCCESS; _srs_context->generate_id();
id = _srs_context->get_id(); /* 获取客户端的 ip 地址 */
ip = srs_get_peer_ip(st_netfd_fileno(stfd)); /* 调用子类的 SrsRtmpConn 实现的 do_cycle 函数开始进行 RTMP 连接请求处理 */
ret = do_cycle(); // if socket io error, set to closed.
if (srs_is_client_gracefully_close(ret)) {
ret = ERROR_SOCKET_CLOSED;
} // sucess.
if (ret == ERROR_SUCCESS) {
srs_trace("client finished.");
} // client close peer.
if (ret == ERROR_SOCKET_CLOSED) {
srs_warn("client disconnect peer. ret=%d", ret);
} return ERROR_SUCCESS;
}

SrsRtmpConn、SrsRtmpServer 和 SrsProtocol 之间的关系图

3.3 SrsRtmpConn::do_cycle

位于 srs_app_rtmp_conn.cpp。

// the timeout to send data to client,
// if timeout, close the connection.
#define SRS_CONSTS_RTMP_SEND_TIMEOUT_US (int64_t)(30*1000*1000LL) // the timeout to wait client data,
// if timeout, close the connection.
#define SRS_CONSTS_RTMP_RECV_TIMEOUT_US (int64_t)(30*1000*1000LL) // TODO: return detail message when error for client.
int SrsRtmpConn::do_cycle()
{
int ret = ERROR_SUCCESS; srs_trace("RTMP client ip=%s", ip.c_str()); /* 设置 rtmp 发送/接收数据时的超时时间 */
rtmp->set_recv_timeout(SRS_CONSTS_RTMP_RECV_TIMEOUT_US);
rtmp->set_send_timeout(SRS_CONSTS_RTMP_SEND_TIMEOUT_US); /* 开始进行 RTMP 连接的 handshake 过程 */
if ((ret = rtmp->handshake()) != ERROR_SUCCESS) {
srs_error("rtmp handshake failed. ret=%d", ret);
return ret;
}
srs_verbose("rtmp handshake success"); /* handshake 成功后,开始接收并解析客户端发送的第一个 RTMP 消息:connect,该消息中
* 指明了客户端想要连接的 app,解析信息保存在 req 中 */
if ((ret = rtmp->connect_app(req)) != ERROR_SUCCESS) {
srs_error("rtmp connect vhost/app failed. ret=%d", ret);
return ret;
}
srs_verbose("rtmp connect app success"); // set client ip to request.
req->ip = ip; /* 如果在配置中没有找到与 req->vhost 相同的,则使用默认的 vhost,
* 即 __defaultVhost__ */
// discovery vhost, resolve the vhost from config
SrsConfDirective* parsed_vhost = _srs_config->get_vhost(req->vhost);
if (parsed_vhost) {
req->vhost = parsed_vhost->arg0();
} srs_info("discovery app success. schema=%s, vhost=%s, port=%s, app=%s",
req->schema.c_str(), req->vhost.c_str(), req->port.c_str(), req->app.c_str()); if (req->schema.empty() || req->vhost.empty() || req->port.empty() || req->app.empty())
{
ret = ERROR_RTMP_REQ_TCUTL;
srs_error("discovery tcUrl failed. "
"tcUrl=%s, schema=%s, vhost=%s, port=%s, app=%s, ret=%d",
req->tcUrl.c_str(), req->schema.c_str(), req->vhost.c_str(),
req->port.c_str(), req->app.c_str(), ret);
return ret;
} // check vhost
if ((ret = check_vhost()) != ERROR_SUCCESS) {
srs_error("check vhost failed. ret=%d", ret);
return ret;
}
srs_verbose("check vhost success."); srs_trace("connect app, "
"tcUrl=%s, pageUrl=%s, swfUrl=%s, schema=%s, vhost=%s, port=%s, app=%s, args=%s",
req->tcUrl.c_str(), req->pageUrl.c_str(), req->swfUrl.c_str(),
req->schema.c_str(), req->vhost.c_str(), req->port.c_str(),
req->app.c_str(), (req->args? "(obj)":"null")); // show client identity
if (req->args) {
std::string srs_version;
std::string srs_server_ip;
int srs_pid = 0;
int srs_id = 0; SrsAmf0Any* prop = NULL;
if ((prop = req->args->ensure_property_string("srs_version")) != NULL) {
srs_version = prop->to_str();
}
if ((prop = req->args->ensure_property_string("srs_server_ip")) != NULL) {
srs_server_ip = prop->to_str();
}
if ((prop = req->args->ensure_property_number("srs_pid")) != NULL) {
srs_pid = (int)prop->to_number();
}
if ((prop = req->args->ensure_property_number("srs_id")) != NULL) {
srs_id = (int)prop->to_number();
} srs_info("edge-srs ip=%s, version=%s, pid=%d, id=%d",
srs_server_ip.c_str(), srs_version.c_str(), srs_pid, srs_id);
if (srs_pid > 0) {
srs_trace("edge-srs ip=%s, version=%s, pid=%d, id=%d",
srs_server_ip.c_str(), srs_version.c_str(), srs_pid, srs_id);
}
} ret = service_cycle(); http_hooks_on_close(); return ret;
}

3.5 rtmp->handshake()

成功 accept 客户端的连接请求,开始与客户端进行 handshake 过程。具体可以参考 SRS之RTMP handshake

3.6 rtmp->connect_app

handshake 成功后,开始接收并解析客户端发送的第一个 RTMP 命令消息:connect。该消息指明了客户端想要连接服务器的某个 application。具体分析过程如 SRS之SrsRtmpServer::connect_app详解

3.7 SrsRtmpConn::check_vhost

int SrsRtmpConn::check_vhost()
{
int ret = ERROR_SUCCESS; srs_assert(req != NULL); /* 获取配置文件中 vhost 配置块的内容 */
SrsConfDirective* vhost = _srs_config->get_vhost(req->vhost);
if (vhost == NULL) {
ret = ERROR_RTMP_VHOST_NOT_FOUND;
srs_error("vhost %s not found. ret=%d", req->vhost.c_str(), ret);
return ret;
} /* 若 vhost 配置块为空,则默认使能 */
if (!_srs_config->get_vhost_enabled(req->vhost)) {
ret = ERROR_RTMP_VHOST_NOT_FOUND;
srs_error("vhost %s disabled. ret=%d", req->vhost.c_str(), ret);
return ret;
} if (req->vhost != vhost->arg0()) {
srs_trace("vhost change from %s to %s", req->vhost.c_str(), vhost->arg0().c_str());
req->vhost = vhost->arg0();
} if ((ret = refer->check(req->pageUrl, _srs_config->get_refer(req->vhost))) != ERROR_SUCCESS) {
srs_error("check refer failed. ret=%d", ret);
return ret;
}
srs_verbose("check refer success."); if ((ret = http_hooks_on_connect()) != ERROR_SUCCESS) {
return ret;
} return ret;
}

这里暂不具体分析,因为配置文件 vhost 为 defaultVhost,即没有使能相关功能。

3.8 SrsRtmpConn::service_cycle

当检测客户端发送的 connect 有效的时候,开始调用该函数服务 client。具体分析过程见 SrsRtmpConn::service_cycle

SRS之RTMP连接处理线程conn:接收客户端推流的更多相关文章

  1. SRS之RTMP的TCP线程(即监听线程)

    本文分析的是 SRS 针对 rtmp 的端口建立的 tcp 线程.具体建立过程: SRS之监听端口的管理:RTMP RTMP 的 TCP 线程中各个类之间 handler 的关系图 1. RTMP之T ...

  2. 写一个基于TCP协议套接字,服务端实现接收客户端的连接并发

    ''' 写一个基于TCP协议套接字,服务端实现接收客户端的连接并发 ''' client import socket import time client = socket.socket() clie ...

  3. 基于TCP协议套接字,服务端实现接收客户端的连接并发

    基于TCP协议套接字,服务端实现接收客户端的连接并发 服务端 import socket from multiprocessing import Process server=socket.socke ...

  4. 【第三篇】学习 android 事件总线androidEventbus之发布事件,子线程中接收

    发送和接收消息的方式类似其他的发送和接收消息的事件总线一样,不同的点或者应该注意的地方: 1,比如在子线程构造方法里面进行实现总线的注册操作: 2,要想子线程中接收消息的功能执行,必须启动线程. 3, ...

  5. phoenixframe平台连接socketserver,并接收返回值的演示样例

    package org.phoenix.cases.webservice; import java.util.LinkedList; import org.phoenix.action.WebElem ...

  6. 记录一次排查使用HttpWebRequest发送请求的发生“基础连接已关闭:接收时发生错误”异常问题的过程

    描述:某次更新程序,需要给测试员MM测试,之前都是正常的,更新后给MM测试就报异常System.Net.WebException 基础连接已经关闭:接收时发生错误 -------> System ...

  7. 建立一个可以不停地接收客户端新的连接,但不能处理复杂的业务的C/S网络程序

    在Windows平台上主要有两个版本的Socket Api函数:WinSock 1.1和WinSock 2.2 , 2.2版本默认兼容1.1版本,1.1 winsock.h wsock32.lib w ...

  8. 一次http请求,谁会先断开TCP连接?什么情况下客户端先断,什么情况下服务端先断?

    我们有2台内部http服务(nginx): 201:这台服务器部署的服务是account.api.91160.com,这个服务是供前端页面调用: 202:这台服务器部署的服务是hdbs.api.911 ...

  9. C# 建立UDP服务器并接收客户端数据

    C# 建立UDP服务器并接收客户端数据 2015-02-11 17:20 1218人阅读 评论(0) 收藏 举报  分类: C#开发技术(22)  版权声明:本文为博主原创文章,未经博主允许不得转载. ...

随机推荐

  1. 数组通常在JS中使用

    数组通常在JS中使用,例如具有相同名称的多个输入.如果它们是动态生成的,则需要在提交时确定它们是否是数组.如果(文件).MyList.长度!=“未定义”)此用法不正确.正确的是如果(文件.MyList ...

  2. Vue页面缓存和不缓存的方法

    第一步 在app中设置需要缓存的div //缓存的页面 <keep-alive> <router-view v-if="$route.meta.keepAlive" ...

  3. 用Buildout来构建Python项目

    用Buildout来构建Python项目   什么是Buildout (Remixed by Matt Hamilton, original from http://xkcd.com/303) Bui ...

  4. 构建虚拟工控环境系列 - 罗克韦尔虚拟PLC

    一. 概述 本篇主要介绍罗克韦尔虚拟PLC的搭建,使用的操作系统为Windows7 x86 Ultimate(DEEP_GHOST_WIN7_SP1_X86_V2015_06.iso),虚拟化软件为 ...

  5. Linux 之Ubuntu在VM中安装(桌面版)

    1.安装系统 https://jingyan.baidu.com/article/14bd256e0ca52ebb6d26129c.html 2.安装VM Tools https://jingyan. ...

  6. cubase 的 CC控制器使用

  7. 使用python获得屏幕截图并保存为位图文件

    直接上代码: import win32gui import win32ui from ctypes import windll import Image hwnd = win32gui.FindWin ...

  8. k8s部署ingress-nginx

    ingress-nginx的部署方式有多种,本文介绍nodeport方式和hostnetwork方式: 一.nodeport方式 1.下载mandatory.yaml文件:https://raw.gi ...

  9. 一个3D正方体

    一个小例子,3D的正方体 <!DOCTYPE html> <html oncontextmenu=self.event.returnValue=false onselectstart ...

  10. 使用nodejs进行开发,concurrently模块,使我们同时执行多个命令。

    concurrently模块使用. 安装模块 npm install concurrently 配置concurrently 运行多个服务 当我们使用nodejs和vue混合开发的时候.当要同时启动后 ...