zoukankan      html  css  js  c++  java
  • [置顶] 搭建一个流媒体服务器引子

    在家休息一段时间了,中间有小公司老板邀请,也有企鹅的几个事业部的邀请,说实话要是放在以前应该都挺有兴趣,不过现在看的淡了。

    和企鹅几个HR聊过,人家总是自我感觉良好,以为薪水高一点就可以搞定原则了,其实在南山住的人有哪个不知道那个半夜12点还开着灯的公司,那个号称不夜城的公司。签企鹅说不定就是在出卖健康,出卖生命,只是过程看起来比较华丽。

    现在互联网公司都有一个迹象,加班都朝着无底线的方向走。

    曾经一直觉得这都很正常,估计这样想的人不在少数。直到前段时间遇到一位外籍同事,人家每天都是朝9晚6,尽管其他人都搞到晚上9点多。有次我不解,就直接问他为什么这么早走,结果他说这一点都不奇怪,他回家还要做饭,还有家务要做。我靠,这答案多么简洁啊,震晕了我那肽合金的狗脑,居然还有天天回家做饭的同事。

    记得两年前把爸妈接深圳来的时候,前一两个星期,老妈总在帮我洗衣服,洗被子,老爸则是在屋里抹抹扫扫,二老还不停的说我懒,不知道大家有没有这样的经历。

    现在想想,也许真不能怪我,每天下班都9点了,到家都10点多了,睡觉起码也要到11点多以后了,要是再有心思在家务上,那我真成圣男了。

    其实,有时候做家务也是一种奢侈,人家老外的生活咱还只是停留在想想上面,而且也只能想想。

    不过衣服还是要常洗的哈,保持新鲜干净总是对的,后来我想到了一个好方法,就是一直带爸妈在身边,至少我可以每天换一次衣服,连洗衣机都省了。

    朋友因为业务需要,要做一个流媒体服务器,问我要不要帮忙,我想反正闲着也是闲着,还不如给他支点招,不过这哥们还真懒,一看我帮他,就干脆整个让我写了。

    记得以前在迅雷的时候,迅雷的流媒体服务器有基于RTMP/HLS的,能够适应各个网络环境。但是现在这个应用场景是P-S模式,用RTSP也就够了。

    市面上现在的流媒体服务器有live555,darwin等。

    拿live555来看,它是一个用C++开发的基于select 模型的RTSP游戏流媒体服务器。

    说实话,代码的可读性和代码风格不能推崇,随处可见的注释,拿其中的解析函数为例:

    unsigned j = i+1;
      while (j < reqStrSize && (reqStr[j] == ' ' || reqStr[j] == '\t')) ++j; // skip over any additional white space
      for (; (int)j < (int)(reqStrSize-8); ++j) {
        if ((reqStr[j] == 'r' || reqStr[j] == 'R')
    	&& (reqStr[j+1] == 't' || reqStr[j+1] == 'T')
    	&& (reqStr[j+2] == 's' || reqStr[j+2] == 'S')
    	&& (reqStr[j+3] == 'p' || reqStr[j+3] == 'P')
    	&& reqStr[j+4] == ':' && reqStr[j+5] == '/') {
          j += 6;
          if (reqStr[j] == '/') {
    	// This is a "rtsp://" URL; skip over the host:port part that follows:
    	++j;
    	while (j < reqStrSize && reqStr[j] != '/' && reqStr[j] != ' ') ++j;
          } else {
    	// This is a "rtsp:/" URL; back up to the "/":
    	--j;
          }
          i = j;
          break;
        }
      }
    
      // Look for the URL suffix (before the following "RTSP/"):
      parseSucceeded = False;
      for (unsigned k = i+1; (int)k < (int)(reqStrSize-5); ++k) {
        if (reqStr[k] == 'R' && reqStr[k+1] == 'T' &&
    	reqStr[k+2] == 'S' && reqStr[k+3] == 'P' && reqStr[k+4] == '/') {
          while (--k >= i && reqStr[k] == ' ') {} // go back over all spaces before "RTSP/"
          unsigned k1 = k;
          while (k1 > i && reqStr[k1] != '/') --k1;
    
          // ASSERT: At this point
          //   i: first space or slash after "host" or "host:port"
          //   k: last non-space before "RTSP/"
          //   k1: last slash in the range [i,k]
    
          // The URL suffix comes from [k1+1,k]
          // Copy "resultURLSuffix":
          unsigned n = 0, k2 = k1+1;
          if (i <= k) { // There's a slash after "host" or "host:port"
            if (k - k1 + 1 > resultURLSuffixMaxSize) return False; // there's no room
            while (k2 <= k) resultURLSuffix[n++] = reqStr[k2++];
          }
          resultURLSuffix[n] = '\0';
    
          // The URL 'pre-suffix' comes from [i+1,k1-1]
          // Copy "resultURLPreSuffix":
          n = 0; k2 = i + 1;
          if (i <= k) { // There's a slash after "host" or "host:port"
            if (k1 - i > resultURLPreSuffixMaxSize) return False; // there's no room
            while (k2 <= k1 - 1) resultURLPreSuffix[n++] = reqStr[k2++];
          }
          resultURLPreSuffix[n] = '\0';
          decodeURL(resultURLPreSuffix);
    
          i = k + 7; // to go past " RTSP/"
          parseSucceeded = True;
          break;
        }
      }
    



    这段代码的主要功能是解析RTSP头:

    比较字符串的时候,其实可以将 ‘RTSP'转换成整型再进行比较。

    另外从CPU运算上来看,第25行和51行都可优化。


    live555 将事件与事务放在一线程中进行处理,事件模型又是基于古老的select 模型,而事务基本都是有IO的,因此当并发量达到一定程度,服务器就全在跑IO了。

    实际上这样的业务需要和事件分隔开,否则就成了“红管子”。

    不过live555的确可以完成RTSP协议,这一点是无可厚非的。

    我的流媒体服务器要从性能上超越live555是完全可能的,由于是基于新的高效事件模型epoll,又是基于多线程的架构,目前市面上还缺这样的产品。为了方便大家研究,我准备对服务器的开发过程进行连载。

    如果将整个系列博文看成一桌菜的话,那今天先上一道开胃菜,流媒体请求的数据结构,大家可以先从请求入手,看看一个请求会涉及到哪些参数。

    struct yumei_rtsp_s{
        long long                client_ip;
        int                      udp_port;
        yumei_rtsp_media_t      *media;
        yumei_rtsp_track_t      *track;
        
    };
    
    struct yumei_rtsp_req_s
    {
        int                            version;
        int                            req_id;
        int                            req_type;
        long long                      session_id;
        char                           header[ 4 ];
        void                          *media_name;
        void                          *track_name;
        void                          *req_cmd;
        void                          *user_agent;
        void                          *transport;
        void                          *accept;
        void                          *range;
    };
    


    业务定义与处理函数:

    typedef int (*yumei_rtsp_busi_handle )( yumei_rtsp_busi_t *busi );
    
    struct yumei_rtsp_busi_s
    {
    
        void                          *conn;
        void                          *pool;
        void                          *rtsp;
    
        
        yumei_rtsp_req_t               req;
        yumei_rtsp_busi_handle         handle;
        
    };
    
    int yumei_rtsp_busi_handle_options( yumei_rtsp_busi_t  *busi );
    int yumei_rtsp_busi_handle_describe( yumei_rtsp_busi_t *busi );
    int yumei_rtsp_busi_handle_setup( yumei_rtsp_busi_t    *busi );
    int yumei_rtsp_busi_handle_play( yumei_rtsp_busi_t     *busi );
    int yumei_rtsp_busi_handle_pause( yumei_rtsp_busi_t    *busi );
    int yumei_rtsp_busi_handle_teardown( yumei_rtsp_busi_t    *busi );
    int yumei_rtsp_busi_handle_get_parameter( yumei_rtsp_busi_t    *busi );
    int yumei_rtsp_busi_handle_set_parameter( yumei_rtsp_busi_t    *busi );
    



    再上一个简单的业务函数:

    int yumei_rtsp_busi_header( yumei_rtsp_busi_t  *busi )
    {
        yumei_conn_t        *conn;
        yumei_mem_buf_t     *sb;
        int                  len;
        const char*          date;
        
        conn = busi->conn;
        sb = conn->send_buf;
        
        date = yumei_time_get_current_buf();
        
        len = sprintf( sb->data, rtsp_rsp_header, busi->req_id, date );
        
        sb->pos = len;
        
        return YUMEI_RTSP_OK;
    }
    
    
    int yumei_rtsp_busi_handle_options( yumei_rtsp_busi_t  *busi )
    {
     
        yumei_conn_t        *conn;
        yumei_mem_buf_t     *sb;
        int                  len, len2;
        
        conn = busi->conn;
        sb = conn->send_buf;
        
        yumei_rtsp_busi_header( busi );
        
        len = sb->pos - 1;
        
        len2 = strcpy( sb->data + len, rtsp_rsp_options );
        
        sb->pos = len + len2;
        
        return YUMEI_RTSP_OK;
        
        
    }


    首先至少从代码已经做到清晰简洁了。



  • 相关阅读:
    Intellij Idea 创建Web项目入门
    使用swagger作为restful api的doc文档生成
    SpringMVC中的参数绑定总结
    Spring注解@Resource和@Autowired区别对比
    java的(PO,VO,TO,BO,DAO,POJO)解释
    java有几种对象(PO,VO,DAO,BO,POJO)
    clone项目到本地
    修改虚拟机上Linux系统的IP地址
    虚拟机下CentOS 6.5配置IP地址的三种方法
    系统重启
  • 原文地址:https://www.cnblogs.com/dyllove98/p/3127533.html
Copyright © 2011-2022 走看看