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

但是随着live555十几年的不断迭代,很多开发者反复向作者Ross提到的多线程和IPv6的功能,作者也一直都没有去尝试,可能是这样会对live555的架构产生比较大的改动和影响,作者为了稳妥,选择了小改动、稳定、逐步迭代的方式, 虽然是性能稳定,但支持的路数有限,不能多线程工作始终是个坎; 网上找到几篇live555多线程的博客, 基本上大同小异,就是创建独立的UsageEnvironment和TaskSchedule, 由独立的线程分工协作; 本人也是这个思路,创建多个工作线程,每个工作线程内创建UsageEnvironment和TaskSchedule,然后各自开启EventLoop;

今天我们抛砖引玉,先大概聊一下主体思路,在后续的博客中将尽力完整地汇总这些思路和开发的过程:

目标

将live555修改为多线程, 每个通道对应一个工作线程,由工作线程对该通道进行独立处理;

大体修改点

修改支持多线程, 主要涉及到以下类的修改

  • GenericMediaServer
  • RTSPServer

GenericMediaServer.cpp
在GenericMediaServer的构造函数中, 创建工作线程个数,即最大支持的通道数;

  1. GenericMediaServer
  2. ::GenericMediaServer(UsageEnvironment& env, int ourSocketV4, int ourSocketV6, Port ourPort,
  3. unsigned reclamationSeconds)
  4. : Medium(env),
  5. fServerSocket4(ourSocketV4), fServerSocket6(ourSocketV6),
  6. fServerPort(ourPort), fReclamationSeconds(reclamationSeconds),
  7. fServerMediaSessions(HashTable::create(STRING_HASH_KEYS)),
  8. fClientConnections(HashTable::create(ONE_WORD_HASH_KEYS)),
  9. fClientSessions(HashTable::create(STRING_HASH_KEYS)) {
  10. ignoreSigPipeOnSocket(fServerSocket4); // so that clients on the same host that are killed don't also kill us
  11. ignoreSigPipeOnSocket(fServerSocket6); // so that clients on the same host that are killed don't also kill us
  12. #ifdef LIVE_MULTI_THREAD_ENABLE
  13. InitMutex(&mutexClientConnection);
  14. memset(&multiThreadCore, 0x00, sizeof(MultiThread_CORE_T));
  15. multiThreadCore.threadNum = MAX_DEFAULT_MULTI_THREAD_NUM;
  16. multiThreadCore.threadTask = new LIVE_THREAD_TASK_T[multiThreadCore.threadNum];
  17. memset(&multiThreadCore.threadTask[0], 0x00, sizeof(LIVE_THREAD_TASK_T) * multiThreadCore.threadNum);
  18. for (int i=0; i<multiThreadCore.threadNum; i++)
  19. {
  20. char szName[36] = {0};
  21. sprintf(szName, "worker thread %d", i);
  22. multiThreadCore.threadTask[i].id = i;
  23. multiThreadCore.threadTask[i].extPtr = this;
  24. multiThreadCore.threadTask[i].pSubScheduler = BasicTaskScheduler::createNew();
  25. multiThreadCore.threadTask[i].pSubEnv = BasicUsageEnvironment::createNew(*multiThreadCore.threadTask[i].pSubScheduler, i+1, szName);
  26. CreateOSThread( &multiThreadCore.threadTask[i].osThread, __OSThread_Proc, (void *)&multiThreadCore.threadTask[i] );
  27. }
  28. #endif
  29. // Arrange to handle connections from others:
  30. env.taskScheduler().turnOnBackgroundReadHandling(fServerSocket4, incomingConnectionHandler4, this);
  31. env.taskScheduler().turnOnBackgroundReadHandling(fServerSocket6, incomingConnectionHandler6, this);
  32. }

接受客户端连接

按原有流程接受客户端连接;

分配客户端请求

在收到客户端发送的DESCRIBE命令后,
在通道列表中找出空闲的通道,将该客户端关联到该通道, 然后从主线程中移除该socket, 由工作线程接管该socket的操作;
后续有客户端如访问已经存在的通道,则主线程会将该请求直接分配给对应的工作线程处理;

注意: 主线程的工作到此结束,不要执行lookupServerMediaSession的操作;

在工作线程中, 接管客户端的socket后, 马上执行lookupServerMediaSession, 在该函数中,将后缀回调给上层调用程序, 由上层调用程序判断是否存在该通道,如不存在则返回失败,如存在则向前端取流,然后填充媒体信息返回成功, 库内部则创建相应的mediasession, 再回应客户端, 后续的则完成整个rtsp流程的交互;

注意: 创建MediaSession时,必须将工作线程中的UsageEnvironment传进去, 不能使用主线程中的envir();

  1. int RTSPServer::RTSPClientConnection
  2. ::handleCmd_DESCRIBE(UsageEnvironment *pEnv, char const* urlPreSuffix, char const* urlSuffix, char const* fullRequestStr, LIVE_THREAD_TASK_T **pThreadTask)
  3. {
  4. int handleCmdRet = 0;
  5. ServerMediaSession* session = NULL;
  6. char* sdpDescription = NULL;
  7. char* rtspURL = NULL;
  8. do {
  9. char urlTotalSuffix[2*RTSP_PARAM_STRING_MAX];
  10. // enough space for urlPreSuffix/urlSuffix'\0'
  11. urlTotalSuffix[0] = '\0';
  12. if (urlPreSuffix[0] != '\0') {
  13. strcat(urlTotalSuffix, urlPreSuffix);
  14. strcat(urlTotalSuffix, "/");
  15. }
  16. strcat(urlTotalSuffix, urlSuffix);
  17. if (!authenticationOK("DESCRIBE", urlTotalSuffix, fullRequestStr)) break;
  18. // We should really check that the request contains an "Accept:" #####
  19. // for "application/sdp", because that's what we're sending back #####
  20. _TRACE(TRACE_LOG_DEBUG, "handleCmd_DESCRIBE socket[%d]\n", this->fOurSocket);
  21. #ifdef LIVE_MULTI_THREAD_ENABLE
  22. //如果当前是主线程,则进入到查找通道流程
  23. if (pEnv->GetEnvirId() == 1000)
  24. {
  25. fOurServer.LockClientConnection(); //Lock
  26. UsageEnvironment *pChEnv = fOurServer.GetEnvBySuffix(urlSuffix, this, pThreadTask);
  27. if (NULL == pChEnv)
  28. {
  29. fOurServer.UnlockClientConnection(); //Unlock
  30. handleCmdRet = -1;
  31. this->assignSink = False;
  32. this->pEnv = NULL;
  33. handleCmd_notFound();
  34. break;
  35. }
  36. else
  37. {
  38. _TRACE(TRACE_LOG_DEBUG, "将socket[%d] 关联到[%s]\n", this->fOurSocket, pChEnv->GetEnvirName());
  39. //将socket从主线程移到工作线程中
  40. UsageEnvironment *pMainEnv = &envir();
  41. envir().taskScheduler().disableBackgroundHandling(fOurSocket);
  42. fOurServer.UnlockClientConnection(); //Unlock
  43. return 1000;
  44. }
  45. break;
  46. }
  47. #endif
  48. // Begin by looking up the "ServerMediaSession" object for the specified "urlTotalSuffix":
  49. //在工作线程中执行 lookupServerMediaSession
  50. session = fOurServer.lookupServerMediaSession(pEnv, 1, this, urlTotalSuffix);
  51. if (session == NULL) {
  52. //pChEnv->taskScheduler().disableBackgroundHandling(fOurSocket);
  53. _TRACE(TRACE_LOG_DEBUG, "socket[%d] 在[%s]中, 源未就绪[%s]\n", this->fOurSocket, pEnv->GetEnvirName(), urlTotalSuffix);
  54. this->assignSink = False;
  55. this->pEnv = NULL;
  56. handleCmdRet = -1;
  57. //envir().taskScheduler().disableBackgroundHandling(fOurSocket);
  58. //fOurServer.ResetEnvBySuffix(urlSuffix, this);
  59. handleCmd_notFound();
  60. break;
  61. }
  62. session->incrementReferenceCount();
  63. // Then, assemble a SDP description for this session:
  64. sdpDescription = session->generateSDPDescription(fOurIPVer);
  65. if (sdpDescription == NULL) {
  66. // This usually means that a file name that was specified for a
  67. // "ServerMediaSubsession" does not exist.
  68. setRTSPResponse("404 File Not Found, Or In Incorrect Format");
  69. break;
  70. }
  71. unsigned sdpDescriptionSize = strlen(sdpDescription);
  72. // Also, generate our RTSP URL, for the "Content-Base:" header
  73. // (which is necessary to ensure that the correct URL gets used in subsequent "SETUP" requests).
  74. rtspURL = fOurRTSPServer.rtspURL(session, fOurIPVer, fClientInputSocket);
  75. snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
  76. "RTSP/1.0 200 OK\r\nCSeq: %s\r\n"
  77. "%s"
  78. "Content-Base: %s/\r\n"
  79. "Content-Type: application/sdp\r\n"
  80. "Content-Length: %d\r\n\r\n"
  81. "%s",
  82. fCurrentCSeq,
  83. dateHeader(),
  84. rtspURL,
  85. sdpDescriptionSize,
  86. sdpDescription);
  87. } while (0);
  88. if (session != NULL) {
  89. // Decrement its reference count, now that we're done using it:
  90. session->decrementReferenceCount();
  91. if (session->referenceCount() == 0 && session->deleteWhenUnreferenced()) {
  92. fOurServer.removeServerMediaSession(pEnv, session, True);
  93. }
  94. session->SetStreamStatus(1); //置标志,让后续访问该通道的客户端可以得到迅速响应
  95. }
  96. delete[] sdpDescription;
  97. delete[] rtspURL;
  98. return handleCmdRet;
  99. }

历经2个多月,终于将多线程问题搞定. 在此记录一下, 欢迎探讨;

live555技术交流

邮件:289042893@qq.com

live555技术交流群:475947825

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

  1. 终于搞定了cxgrid的多行表头(转终于搞定了cxgrid的多行表头 )

    终于搞定了cxgrid的多行表头 转自:http://mycreature.blog.163.com/blog/static/556317200772524226400/     这一周都在处理dbg ...

  2. 终于搞定Fastreport2.x PDF输出,相信其他版本也差不多

    这个版本有powerpdf可以支持,但有bug, 经过反复摸索,终于搞定. 基本可用. 主要是中英文混合在一起,如果按中文输出,会有英文宽度也是中文的宽度了,格式变化,不可 接受. 而按英文输出,又是 ...

  3. 模拟摄像头,AV视频信号线解码,PAL制 NTSC,输入解码显示,终于搞定,记录下!

    模拟摄像头,AV视频信号线解码,PAL制 NTSC,输入解码显示,终于搞定,记录下! 咱们常用的摄像头,监控等,大多数都是AV信号,国内制式都是PAL,采用同轴,传输,这样的好处在于,传输距离可以很长 ...

  4. 折腾了半天,终于搞定了apache的rewrite功能

    基本步骤和网上其它文章说得基本一样.只是在具体操作的时候或多或少存在些问题 一 打开 apache 的配置文件 httpd.conf . 二 将#loadmodule rewrite_module m ...

  5. express 配置 https 服务 ( 以阿里云服务器为例), 探索一周终于搞定

    首先最重要的是 你要明白 https接口的接收或者发送 的形式 是  https://域名:端口号   而不是 https://ip:端口号   一,首先,去阿里云注册免费ssl证书   1,在搜索框 ...

  6. 终于搞定office 2013中文双引号无法匹配问题啦!!!

    设计>>字体>>自定义字体>>所有字体改为宋体>>保存>>点击字体确认当前字体是自己刚新建的>>点击旁边设为默认值>> ...

  7. 安装graphlab伤透了心,终于搞定了

    为了方便研究各种机器学习算法,我想用graphlab来辅助我对后续算法的研究.所以我的目标就是安装graphlab到我的windows笔记本中.而基于python的graphlab的安装最好是采用如下 ...

  8. 静态dll的问题终于搞定了

    导入plugin,构建qapplicationhttps://forum.qt.io/topic/60940/qt-static-dll-x64-using-qapplication-issues/2 ...

  9. 终于搞定在VS2010中将CString转换为const char*

    最近碰到了CString 转 const char *的问题. 以前只要简单的一个强制转换就OK了,可现在是不行了,搜索了很多资料,终于搞定,主要是Unicode和ANSI的问题,只要做一个转换就可以 ...

随机推荐

  1. SQL语言中的COMMENT添加字段的注释

  2. Java源码阅读ArrayList

    1简介 public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAc ...

  3. Linux Shell常用技巧

    转载自http://www.cnblogs.com/stephen-liu74/ 一.    特殊文件: /dev/null和/dev/tty Linux系统提供了两个对Shell编程非常有用的特殊文 ...

  4. Cocos2d-x 3.2 大富翁游戏项目开发-第七部分 获取角色路径_2

    在编写获取路径方法前,我们先把角色须要的动画文件载入进来,角色的文件为png 和 plist格式. player1_anim.png.plist             player1_anim.pn ...

  5. Git版本管理

    1.显示当前工作目录 pwd 2.把当前目录初始化为git可以管理的仓库 git init 3.把文件添加到仓库 git add xxx.txt 4.告诉git,把文件提交到仓库 .-m后面输入的是本 ...

  6. 创建你的第一个ionic+cordova应用(1)

    前面我们安装了前端的神器webstorm11,体验到了强大的开发体验,接着我们来安装ionic 必备: Node.js (npm安装工具) 百度下载 官网下载  注:如果官网新版不能安装请用百度下载0 ...

  7. JavaScript 取数组最值的方法

    1.用Math的max,min函数 var array = [10,2,3,4,5,6,30,8,9]; Math.max.apply(null,array); Math.min.apply(null ...

  8. 操作REDIES

    import redis r=redis.Redis(host='118.XX.XX.XXX',password='XXXXXXX9*',db=1,port=6379) # 增删改查r.set('jd ...

  9. mongoDB 高级查询语法

    http://www.cnblogs.com/ITAres/articles/2084794.html本文参考自官方的手册:http://www.mongodb.org/display/DOCS/Ad ...

  10. 关于Linux网络配置

    Linux网络配置 一:什么是网络接口卡以及如何查看网络接口的网络信息:在Linux系统中,主机的网络接口卡通常称为“网络接口”,我们可以使用ifconfig命令来查看网络 接口的信息(普通用户使用/ ...