经过两个多月的攻关,终于搞定了live555多线程并稳定压测通过

2023-10-23 19:10

本文主要是介绍经过两个多月的攻关,终于搞定了live555多线程并稳定压测通过,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

live555已经发展了十几年了,不得不钦佩作者坚持不懈的奉献和国外的开源生态环境,live555可以说是大部分的安防从业者的入门之选,尤其是在嵌入式或者Linux系统上,其应用还是蛮广泛的,主要是其兼容性和稳定性;

live555多线程

但是随着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 usignoreSigPipeOnSocket(fServerSocket6); // so that clients on the same host that are killed don't also kill us#ifdef LIVE_MULTI_THREAD_ENABLEInitMutex(&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();  //LockUsageEnvironment  *pChEnv = fOurServer.GetEnvBySuffix(urlSuffix, this, pThreadTask);if (NULL == pChEnv){fOurServer.UnlockClientConnection();        //UnlockhandleCmdRet = -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();        //Unlockreturn 1000;}break;}#endif// Begin by looking up the "ServerMediaSession" object for the specified "urlTotalSuffix"://在工作线程中执行 lookupServerMediaSessionsession = 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个多月,终于将多线程问题搞定. 在此记录一下, 欢迎探讨;

目前用户测试反馈的情况是:

  • 连续压测8天时间;
  • 接入摄像机30台;
  • 客户端反复启动/停止播放;
  • 内存、CPU、程序均非常稳定!

live555技术交流

邮件:289042893@qq.com

live555技术交流群:475947825

这篇关于经过两个多月的攻关,终于搞定了live555多线程并稳定压测通过的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/269886

相关文章

Python中多线程和多进程的基本用法详解

《Python中多线程和多进程的基本用法详解》这篇文章介绍了Python中多线程和多进程的相关知识,包括并发编程的优势,多线程和多进程的概念、适用场景、示例代码,线程池和进程池的使用,以及如何选择合适... 目录引言一、并发编程的主要优势二、python的多线程(Threading)1. 什么是多线程?2.

Python如何计算两个不同类型列表的相似度

《Python如何计算两个不同类型列表的相似度》在编程中,经常需要比较两个列表的相似度,尤其是当这两个列表包含不同类型的元素时,下面小编就来讲讲如何使用Python计算两个不同类型列表的相似度吧... 目录摘要引言数字类型相似度欧几里得距离曼哈顿距离字符串类型相似度Levenshtein距离Jaccard相

SpringBoot中使用 ThreadLocal 进行多线程上下文管理及注意事项小结

《SpringBoot中使用ThreadLocal进行多线程上下文管理及注意事项小结》本文详细介绍了ThreadLocal的原理、使用场景和示例代码,并在SpringBoot中使用ThreadLo... 目录前言技术积累1.什么是 ThreadLocal2. ThreadLocal 的原理2.1 线程隔离2

Java多线程父线程向子线程传值问题及解决

《Java多线程父线程向子线程传值问题及解决》文章总结了5种解决父子之间数据传递困扰的解决方案,包括ThreadLocal+TaskDecorator、UserUtils、CustomTaskDeco... 目录1 背景2 ThreadLocal+TaskDecorator3 RequestContextH

使用Navicat工具比对两个数据库所有表结构的差异案例详解

《使用Navicat工具比对两个数据库所有表结构的差异案例详解》:本文主要介绍如何使用Navicat工具对比两个数据库test_old和test_new,并生成相应的DDLSQL语句,以便将te... 目录概要案例一、如图两个数据库test_old和test_new进行比较:二、开始比较总结概要公司存在多

C#比较两个List集合内容是否相同的几种方法

《C#比较两个List集合内容是否相同的几种方法》本文详细介绍了在C#中比较两个List集合内容是否相同的方法,包括非自定义类和自定义类的元素比较,对于非自定义类,可以使用SequenceEqual、... 目录 一、非自定义类的元素比较1. 使用 SequenceEqual 方法(顺序和内容都相等)2.

C#多线程编程中导致死锁的常见陷阱和避免方法

《C#多线程编程中导致死锁的常见陷阱和避免方法》在C#多线程编程中,死锁(Deadlock)是一种常见的、令人头疼的错误,死锁通常发生在多个线程试图获取多个资源的锁时,导致相互等待对方释放资源,最终形... 目录引言1. 什么是死锁?死锁的典型条件:2. 导致死锁的常见原因2.1 锁的顺序问题错误示例:不同

基于Go语言实现一个压测工具

《基于Go语言实现一个压测工具》这篇文章主要为大家详细介绍了基于Go语言实现一个简单的压测工具,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录整体架构通用数据处理模块Http请求响应数据处理Curl参数解析处理客户端模块Http客户端处理Grpc客户端处理Websocket客户端

浅析Rust多线程中如何安全的使用变量

《浅析Rust多线程中如何安全的使用变量》这篇文章主要为大家详细介绍了Rust如何在线程的闭包中安全的使用变量,包括共享变量和修改变量,文中的示例代码讲解详细,有需要的小伙伴可以参考下... 目录1. 向线程传递变量2. 多线程共享变量引用3. 多线程中修改变量4. 总结在Rust语言中,一个既引人入胜又可

Springboot的ThreadPoolTaskScheduler线程池轻松搞定15分钟不操作自动取消订单

《Springboot的ThreadPoolTaskScheduler线程池轻松搞定15分钟不操作自动取消订单》:本文主要介绍Springboot的ThreadPoolTaskScheduler线... 目录ThreadPoolTaskScheduler线程池实现15分钟不操作自动取消订单概要1,创建订单后