经过两个多月的攻关,终于搞定了live555多线程并稳定压测通过
live555已经发展了十几年了,不得不钦佩作者坚持不懈的奉献和国外的开源生态环境,live555可以说是大部分的安防从业者的入门之选,尤其是在嵌入式或者Linux系统上,其应用还是蛮广泛的,主要是其兼容性和稳定性;
但是随着live555十几年的不断迭代,很多开发者反复向作者Ross提到的多线程和IPv6的功能,作者也一直都没有去尝试,可能是这样会对live555的架构产生比较大的改动和影响,作者为了稳妥,选择了小改动、稳定、逐步迭代的方式, 虽然是性能稳定,但支持的路数有限,不能多线程工作始终是个坎; 网上找到几篇live555多线程的博客, 基本上大同小异,就是创建独立的UsageEnvironment和TaskSchedule, 由独立的线程分工协作; 本人也是这个思路,创建多个工作线程,每个工作线程内创建UsageEnvironment和TaskSchedule,然后各自开启EventLoop;
今天我们抛砖引玉,先大概聊一下主体思路,在后续的博客中将尽力完整地汇总这些思路和开发的过程:
目标
将live555修改为多线程, 每个通道对应一个工作线程,由工作线程对该通道进行独立处理;
大体修改点
修改支持多线程, 主要涉及到以下类的修改
- GenericMediaServer
- RTSPServer
GenericMediaServer.cpp
在GenericMediaServer的构造函数中, 创建工作线程个数,即最大支持的通道数;
GenericMediaServer
::GenericMediaServer(UsageEnvironment& env, int ourSocketV4, int ourSocketV6, Port ourPort,
unsigned reclamationSeconds)
: Medium(env),
fServerSocket4(ourSocketV4), fServerSocket6(ourSocketV6),
fServerPort(ourPort), fReclamationSeconds(reclamationSeconds),
fServerMediaSessions(HashTable::create(STRING_HASH_KEYS)),
fClientConnections(HashTable::create(ONE_WORD_HASH_KEYS)),
fClientSessions(HashTable::create(STRING_HASH_KEYS)) {
ignoreSigPipeOnSocket(fServerSocket4); // so that clients on the same host that are killed don't also kill us
ignoreSigPipeOnSocket(fServerSocket6); // so that clients on the same host that are killed don't also kill us
#ifdef LIVE_MULTI_THREAD_ENABLE
InitMutex(&mutexClientConnection);
memset(&multiThreadCore, 0x00, sizeof(MultiThread_CORE_T));
multiThreadCore.threadNum = MAX_DEFAULT_MULTI_THREAD_NUM;
multiThreadCore.threadTask = new LIVE_THREAD_TASK_T[multiThreadCore.threadNum];
memset(&multiThreadCore.threadTask[0], 0x00, sizeof(LIVE_THREAD_TASK_T) * multiThreadCore.threadNum);
for (int i=0; i<multiThreadCore.threadNum; i++)
{
char szName[36] = {0};
sprintf(szName, "worker thread %d", i);
multiThreadCore.threadTask[i].id = i;
multiThreadCore.threadTask[i].extPtr = this;
multiThreadCore.threadTask[i].pSubScheduler = BasicTaskScheduler::createNew();
multiThreadCore.threadTask[i].pSubEnv = BasicUsageEnvironment::createNew(*multiThreadCore.threadTask[i].pSubScheduler, i+1, szName);
CreateOSThread( &multiThreadCore.threadTask[i].osThread, __OSThread_Proc, (void *)&multiThreadCore.threadTask[i] );
}
#endif
// Arrange to handle connections from others:
env.taskScheduler().turnOnBackgroundReadHandling(fServerSocket4, incomingConnectionHandler4, this);
env.taskScheduler().turnOnBackgroundReadHandling(fServerSocket6, incomingConnectionHandler6, this);
}
接受客户端连接
按原有流程接受客户端连接;
分配客户端请求
在收到客户端发送的DESCRIBE命令后,
在通道列表中找出空闲的通道,将该客户端关联到该通道, 然后从主线程中移除该socket, 由工作线程接管该socket的操作;
后续有客户端如访问已经存在的通道,则主线程会将该请求直接分配给对应的工作线程处理;
注意: 主线程的工作到此结束,不要执行lookupServerMediaSession的操作;
在工作线程中, 接管客户端的socket后, 马上执行lookupServerMediaSession, 在该函数中,将后缀回调给上层调用程序, 由上层调用程序判断是否存在该通道,如不存在则返回失败,如存在则向前端取流,然后填充媒体信息返回成功, 库内部则创建相应的mediasession, 再回应客户端, 后续的则完成整个rtsp流程的交互;
注意: 创建MediaSession时,必须将工作线程中的UsageEnvironment传进去, 不能使用主线程中的envir();
int RTSPServer::RTSPClientConnection
::handleCmd_DESCRIBE(UsageEnvironment *pEnv, char const* urlPreSuffix, char const* urlSuffix, char const* fullRequestStr, LIVE_THREAD_TASK_T **pThreadTask)
{
int handleCmdRet = 0;
ServerMediaSession* session = NULL;
char* sdpDescription = NULL;
char* rtspURL = NULL;
do {
char urlTotalSuffix[2*RTSP_PARAM_STRING_MAX];
// enough space for urlPreSuffix/urlSuffix'\0'
urlTotalSuffix[0] = '\0';
if (urlPreSuffix[0] != '\0') {
strcat(urlTotalSuffix, urlPreSuffix);
strcat(urlTotalSuffix, "/");
}
strcat(urlTotalSuffix, urlSuffix);
if (!authenticationOK("DESCRIBE", urlTotalSuffix, fullRequestStr)) break;
// We should really check that the request contains an "Accept:" #####
// for "application/sdp", because that's what we're sending back #####
_TRACE(TRACE_LOG_DEBUG, "handleCmd_DESCRIBE socket[%d]\n", this->fOurSocket);
#ifdef LIVE_MULTI_THREAD_ENABLE
//如果当前是主线程,则进入到查找通道流程
if (pEnv->GetEnvirId() == 1000)
{
fOurServer.LockClientConnection(); //Lock
UsageEnvironment *pChEnv = fOurServer.GetEnvBySuffix(urlSuffix, this, pThreadTask);
if (NULL == pChEnv)
{
fOurServer.UnlockClientConnection(); //Unlock
handleCmdRet = -1;
this->assignSink = False;
this->pEnv = NULL;
handleCmd_notFound();
break;
}
else
{
_TRACE(TRACE_LOG_DEBUG, "将socket[%d] 关联到[%s]\n", this->fOurSocket, pChEnv->GetEnvirName());
//将socket从主线程移到工作线程中
UsageEnvironment *pMainEnv = &envir();
envir().taskScheduler().disableBackgroundHandling(fOurSocket);
fOurServer.UnlockClientConnection(); //Unlock
return 1000;
}
break;
}
#endif
// Begin by looking up the "ServerMediaSession" object for the specified "urlTotalSuffix":
//在工作线程中执行 lookupServerMediaSession
session = fOurServer.lookupServerMediaSession(pEnv, 1, this, urlTotalSuffix);
if (session == NULL) {
//pChEnv->taskScheduler().disableBackgroundHandling(fOurSocket);
_TRACE(TRACE_LOG_DEBUG, "socket[%d] 在[%s]中, 源未就绪[%s]\n", this->fOurSocket, pEnv->GetEnvirName(), urlTotalSuffix);
this->assignSink = False;
this->pEnv = NULL;
handleCmdRet = -1;
//envir().taskScheduler().disableBackgroundHandling(fOurSocket);
//fOurServer.ResetEnvBySuffix(urlSuffix, this);
handleCmd_notFound();
break;
}
session->incrementReferenceCount();
// Then, assemble a SDP description for this session:
sdpDescription = session->generateSDPDescription(fOurIPVer);
if (sdpDescription == NULL) {
// This usually means that a file name that was specified for a
// "ServerMediaSubsession" does not exist.
setRTSPResponse("404 File Not Found, Or In Incorrect Format");
break;
}
unsigned sdpDescriptionSize = strlen(sdpDescription);
// Also, generate our RTSP URL, for the "Content-Base:" header
// (which is necessary to ensure that the correct URL gets used in subsequent "SETUP" requests).
rtspURL = fOurRTSPServer.rtspURL(session, fOurIPVer, fClientInputSocket);
snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
"RTSP/1.0 200 OK\r\nCSeq: %s\r\n"
"%s"
"Content-Base: %s/\r\n"
"Content-Type: application/sdp\r\n"
"Content-Length: %d\r\n\r\n"
"%s",
fCurrentCSeq,
dateHeader(),
rtspURL,
sdpDescriptionSize,
sdpDescription);
} while (0);
if (session != NULL) {
// Decrement its reference count, now that we're done using it:
session->decrementReferenceCount();
if (session->referenceCount() == 0 && session->deleteWhenUnreferenced()) {
fOurServer.removeServerMediaSession(pEnv, session, True);
}
session->SetStreamStatus(1); //置标志,让后续访问该通道的客户端可以得到迅速响应
}
delete[] sdpDescription;
delete[] rtspURL;
return handleCmdRet;
}
历经2个多月,终于将多线程问题搞定. 在此记录一下, 欢迎探讨;
live555技术交流
邮件:289042893@qq.com
live555技术交流群:475947825
经过两个多月的攻关,终于搞定了live555多线程并稳定压测通过的更多相关文章
- 终于搞定了cxgrid的多行表头(转终于搞定了cxgrid的多行表头 )
终于搞定了cxgrid的多行表头 转自:http://mycreature.blog.163.com/blog/static/556317200772524226400/ 这一周都在处理dbg ...
- 终于搞定Fastreport2.x PDF输出,相信其他版本也差不多
这个版本有powerpdf可以支持,但有bug, 经过反复摸索,终于搞定. 基本可用. 主要是中英文混合在一起,如果按中文输出,会有英文宽度也是中文的宽度了,格式变化,不可 接受. 而按英文输出,又是 ...
- 模拟摄像头,AV视频信号线解码,PAL制 NTSC,输入解码显示,终于搞定,记录下!
模拟摄像头,AV视频信号线解码,PAL制 NTSC,输入解码显示,终于搞定,记录下! 咱们常用的摄像头,监控等,大多数都是AV信号,国内制式都是PAL,采用同轴,传输,这样的好处在于,传输距离可以很长 ...
- 折腾了半天,终于搞定了apache的rewrite功能
基本步骤和网上其它文章说得基本一样.只是在具体操作的时候或多或少存在些问题 一 打开 apache 的配置文件 httpd.conf . 二 将#loadmodule rewrite_module m ...
- express 配置 https 服务 ( 以阿里云服务器为例), 探索一周终于搞定
首先最重要的是 你要明白 https接口的接收或者发送 的形式 是 https://域名:端口号 而不是 https://ip:端口号 一,首先,去阿里云注册免费ssl证书 1,在搜索框 ...
- 终于搞定office 2013中文双引号无法匹配问题啦!!!
设计>>字体>>自定义字体>>所有字体改为宋体>>保存>>点击字体确认当前字体是自己刚新建的>>点击旁边设为默认值>> ...
- 安装graphlab伤透了心,终于搞定了
为了方便研究各种机器学习算法,我想用graphlab来辅助我对后续算法的研究.所以我的目标就是安装graphlab到我的windows笔记本中.而基于python的graphlab的安装最好是采用如下 ...
- 静态dll的问题终于搞定了
导入plugin,构建qapplicationhttps://forum.qt.io/topic/60940/qt-static-dll-x64-using-qapplication-issues/2 ...
- 终于搞定在VS2010中将CString转换为const char*
最近碰到了CString 转 const char *的问题. 以前只要简单的一个强制转换就OK了,可现在是不行了,搜索了很多资料,终于搞定,主要是Unicode和ANSI的问题,只要做一个转换就可以 ...
随机推荐
- 2017.6.29 java读取.properties配置文件的几种方法
参考来自:http://www.cnblogs.com/s3189454231s/p/5626557.html 关于路径的解释:http://blog.csdn.net/bluishglc/artic ...
- fmt标签的格式化日期使用
声明: <%@ taglib prefix="fmt" uri="/WEB-INF/fmt.tld" %> or <%@ taglib pre ...
- iOS音乐后台播放及锁屏信息显示
实现音乐的后台播放.以及播放时,能够控制其暂停,下一首等操作,以及锁屏图片歌曲名等的显示 此实例须要真机调试.效果图例如以下: project下载:githubproject下载 实现步骤: 1.首先 ...
- python中MySQLdb模块用法实例
篇文章主要介绍了python中MySQLdb模块用法,以实例形式详细讲述了MySQLdb模块针对MySQL数据库的各种常见操作方法,非常具有实用价值,需要的朋友可以参考下 本文实例讲述了python中 ...
- 标准库Allocator的使用(一)
上一篇我们提到了new运算符以及它的工作步骤,其实无非是把两项工作独立出来: 1.申请原始内存 2.执行构造函数 delete也涉及了两个工作: 1.执行析构函数 2.释放原始内存 其实标准库提供了另 ...
- VS中c++文件调用c 函数 ,fatal error C1853 预编译头文件来自编译器的早期版本号,或者预编译头为 C++ 而在 C 中使用它(或相反)
出现错误:error C1853: "Debug\ConsoleApplication1.pch"预编译头文件来自编译器的早期版本号.或者预编译头为 C++ 而在 C 中使用它(或 ...
- DNS检测
dig @58.241.41.152 6900255264940.barcode.cniotroot.cn naptr 没有naptr 好像有点异常 select count(*) as total ...
- 《Android源代码设计模式解析与实战》读书笔记
1.定义 将对象组合成树形结构以表示"部分-总体"的层次结构,使得用户对单个对象和组合对象的使用具有一致性. 2.使用场景 (1)表示对象的部分-总体层次结构时. (2)从一个总体 ...
- MySQL性能优化的最佳20+条经验(转)
今天,数据库的操作越来越成为整个应用的性能瓶颈了,这点对于Web应用尤其明显.关于数据库的性能,这并不只是DBA才需要担心的事,而这更是我们程序 员需要去关注的事情.当我们去设计数据库表结构,对操作数 ...
- php 处理 form 表单提交多个 name 属性值相同的 input 标签
一 问题 在公司的开发过程中,遇到了一个问题:如何处理 form 表单提交了多个 name 属性值相同的 input 标签?源码如下(源码是在 form 表单之中的): <!--{loop $a ...