SRS 代碼分析【HTTP-FLV傳輸實現】

http-flv技術實現:

HTTP協議中有個約定:content-length字段,http的body部分的長度
服務器回覆http請求的時候如果有這個字段,客戶端就接收這個長度的數據然後就認爲數據傳輸完成了,
如果服務器回覆http請求中沒有這個字段,客戶端就一直接收數據,直到服務器跟客戶端的socket連接斷開。

http-flv直播就是利用了這個原理,服務器回覆客戶端請求的時候不加content-length字段,在回覆了http
內容之後,緊接着發送flv數據,客戶端就一直接收數據了。

請求SRS返回的是:
HTTP/1.1 200 OK
Connection: Keep-Alive
Content-Type: video/x-flv
Server: SRS/2.0.205
Transfer-Encoding: chunked

實現代碼:

服務器啓動時http端口的監聽過程如下:

run_master()–>SrsServer::listen()—>SrsServer::listen_http_stream()

listen_http_stream服務端口監聽流程如下:

srs_error_t SrsServer::listen_http_stream()
{
    srs_error_t err = srs_success;
    
    close_listeners(SrsListenerHttpStream);
    if (_srs_config->get_http_stream_enabled()) {
        SrsListener* listener = new SrsBufferListener(this, SrsListenerHttpStream);
        listeners.push_back(listener);
        
        std::string ep = _srs_config->get_http_stream_listen();
        
        std::string ip;
        int port;
        srs_parse_endpoint(ep, ip, port);
        
        if ((err = listener->listen(ip, port)) != srs_success) {
            return srs_error_wrap(err, "http stream listen %s:%d", ip.c_str(), port);
        }
    }
    
    return err;
}

1).判斷是否開啓HttpStream功能 _srs_config->get_http_stream_enabled()

2).創建SrsListener實例根據獲取的ip的port啓動監聽listener->listen(ip, port)

3).SrsBufferListener::listen()中創建SrsTcpListener的實例並啓動監聽listener->listen()

[html] view plain copy
srs_error_t SrsBufferListener::listen(string i, int p)  
{  
    srs_error_t err = srs_success;  
      
    ip = i;  
    port = p;  
      
    srs_freep(listener);  
    listener = new SrsTcpListener(this, ip, port);  
      
    if ((err = listener->listen()) != srs_success) {  
        return srs_error_wrap(err, "buffered tcp listen");  
    }  
      
    string v = srs_listener_type2string(type);  
    srs_trace("%s listen at tcp://%s:%d, fd=%d", v.c_str(), ip.c_str(), port, listener->fd());  
      
    return err;  
}  

4).SrsTcpListener::listen()中啓動監聽,創建SrsSTCoroutine協程實例,開啓協程。

[plain] view plain copy
srs_error_t SrsTcpListener::listen()  
{  
    srs_error_t err = srs_success;  
      
    if ((_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {  
        return srs_error_new(ERROR_SOCKET_CREATE, "create socket");  
    }  
      
    srs_fd_close_exec(_fd);  
    srs_socket_reuse_addr(_fd);  
      
    sockaddr_in addr;  
    addr.sin_family = AF_INET;  
    addr.sin_port = htons(port);  
    addr.sin_addr.s_addr = inet_addr(ip.c_str());  
    if (bind(_fd, (const sockaddr*)&addr, sizeof(sockaddr_in)) == -1) {  
        return srs_error_new(ERROR_SOCKET_BIND, "bind socket");  
    }  
      
    if (::listen(_fd, SERVER_LISTEN_BACKLOG) == -1) {  
        return srs_error_new(ERROR_SOCKET_LISTEN, "listen socket");  
    }  
      
    if ((_stfd = srs_netfd_open_socket(_fd)) == NULL){  
        return srs_error_new(ERROR_ST_OPEN_SOCKET, "st open socket");  
    }  
      
    srs_freep(trd);  
    trd = new SrsSTCoroutine("tcp", this);  
    if ((err = trd->start()) != srs_success) {  
        return srs_error_wrap(err, "start coroutine");  
    }  
      
    return err;  
}  

5).調用trd->start()函數後,最終會執行SrsTcpListener::cycle()。

具體調用過程請參照

http://blog.csdn.net/weixin_39799839/article/details/78579278

在cycle函數中會調用srs_accept阻塞等待客戶端http請求的到來,

SrsTcpListener::cycle函數定義如下

[html] view plain copy
srs_error_t SrsTcpListener::cycle()  
{  
    srs_error_t err = srs_success;  
      
    while (true) {  
        if ((err = trd->pull()) != srs_success) {  
            return srs_error_wrap(err, "tcp listener");  
        }  
          
        srs_netfd_t cstfd = srs_accept(_stfd, NULL, NULL, SRS_UTIME_NO_TIMEOUT);  
        if(cstfd == NULL){  
            return srs_error_new(ERROR_SOCKET_CREATE, "accept failed");  
        }  
          
        int cfd = srs_netfd_fileno(cstfd);  
        srs_fd_close_exec(cfd);  
          
        if ((err = handler->on_tcp_client(cstfd)) != srs_success) {  
            return srs_error_wrap(err, "handle fd=%d", cfd);  
        }  
    }  
      
    return err;  
}  

當有http請求到來時srs_accept()返回連接的fd,接着調用handler->on_tcp_client()處理連理連接;

on_tcp_client接下來執行順序爲:handler->on_tcp_client()–>SrsBufferListener::on_tcp_client()–>SrsServer::accept_client。

最終由SrsServer::accept_client處理連接請求,具體代碼如下:

[plain] view plain copy
srs_error_t SrsServer::accept_client(SrsListenerType type, srs_netfd_t stfd)  
{  
    srs_error_t err = srs_success;  
      
    SrsConnection* conn = NULL;  
      
    if ((err = fd2conn(type, stfd, &conn)) != srs_success) {  
        return srs_error_wrap(err, "fd2conn");  
    }  
    srs_assert(conn);  
      
    // directly enqueue, the cycle thread will remove the client.  
    conns.push_back(conn);  
      
    // cycle will start process thread and when finished remove the client.  
    // @remark never use the conn, for it maybe destroyed.  
    if ((err = conn->start()) != srs_success) {  
        return srs_error_wrap(err, "start conn coroutine");  
    }  
      
    return err;  
}  

SrsServer::accept_client調用fd2conn(type, stfd, &conn)創建一個連接對象SrsResponseOnlyHttpConn,SrsResponseOnlyHttpConn 繼承者自 SrsHttpConn

srs_error_t SrsServer::fd2conn(SrsListenerType type, srs_netfd_t stfd, SrsConnection** pconn)
{
	......
    if (type == SrsListenerRtmpStream) {
        *pconn = new SrsRtmpConn(this, stfd, ip);
    } else if (type == SrsListenerHttpApi) {
        *pconn = new SrsHttpApi(this, stfd, http_api_mux, ip);
    } else if (type == SrsListenerHttpStream) {
        *pconn = new SrsResponseOnlyHttpConn(this, stfd, http_server, ip);
    } else {
        srs_warn("close for no service handler. fd=%d, ip=%s", fd, ip.c_str());
        srs_close_stfd(stfd);
        return err;
    }
    
    return err;
}

SrsHttpConn對應一個協程,conn.start()會啓動該協程,進入到SrsHttpConn::do_cycle()循環來處理http請求:

調用流程如下:

SrsServer::accept_client—>SrsConnection::start—>SrsSTCoroutine::start–>SrsConnection::cycle—>SrsHttpConn::do_cycle

srs_error_t SrsHttpConn::do_cycle()
{
    int ret = ERROR_SUCCESS;
    srs_error_t err = srs_success;
    
    srs_trace("HTTP client ip=%s", ip.c_str());
    
    // initialize parser
    if ((ret = parser->initialize(HTTP_REQUEST, false)) != ERROR_SUCCESS) {
        return srs_error_new(ret, "init parser");
    }
    
    // set the recv timeout, for some clients never disconnect the connection.
    // @see https://github.com/ossrs/srs/issues/398
    skt->set_recv_timeout(SRS_HTTP_RECV_TMMS);
    
    SrsRequest* last_req = NULL;
    SrsAutoFree(SrsRequest, last_req);
    
    // initialize the cors, which will proxy to mux.
    bool crossdomain_enabled = _srs_config->get_http_stream_crossdomain();
    if ((err = cors->initialize(http_mux, crossdomain_enabled)) != srs_success) {
        return srs_error_wrap(err, "init cors");
    }
    
    // process http messages.
    while ((err = trd->pull()) == srs_success) {
        ISrsHttpMessage* req = NULL;
        
        // get a http message
        if ((ret = parser->parse_message(skt, this, &req)) != ERROR_SUCCESS) {
            break;
        }
        
        // if SUCCESS, always NOT-NULL.
        srs_assert(req);
        
        // always free it in this scope.
        SrsAutoFree(ISrsHttpMessage, req);
        
        // copy request to last request object.
        srs_freep(last_req);
        SrsHttpMessage* hreq = dynamic_cast<SrsHttpMessage*>(req);
        last_req = hreq->to_request(hreq->host());
        
        // may should discard the body.
        if ((err = on_got_http_message(req)) != srs_success) {
            break;
        }
        
        // ok, handle http request.
        SrsHttpResponseWriter writer(skt);
        if ((err = process_request(&writer, req)) != srs_success) {
            break;
        }
        
        // donot keep alive, disconnect it.
        // @see https://github.com/ossrs/srs/issues/399
        if (!req->is_keep_alive()) {
            break;
        }
    }
    
    srs_error_t r0 = srs_success;
    if ((r0 = on_disconnect(last_req)) != srs_success) {
        err = srs_error_wrap(err, "on disconnect %s", srs_error_desc(r0).c_str());
        srs_freep(r0);
    }
    
    return err;
}

SrsHttpConn::do_cycle中調用on_got_http_message,該函數爲純虛函數,實際實現它的是SrsResponseOnlyHttpConn
,函數SrsResponseOnlyHttpConn::on_got_http_message主要是用來讀取第一次http請求中的消息內容。後面該連接的消息處理會交給SrsHttpRecvThread。

srs_error_t SrsResponseOnlyHttpConn::on_got_http_message(ISrsHttpMessage* msg)
{
    int ret = ERROR_SUCCESS;
    srs_error_t err = srs_success;
    
    ISrsHttpResponseReader* br = msg->body_reader();
    
    // when not specified the content length, ignore.
    if (msg->content_length() == -1) {
        return err;
    }
    
    // drop all request body.
    while (!br->eof()) {
        char body[4096];
        if ((ret = br->read(body, 4096, NULL)) != ERROR_SUCCESS) {
            return srs_error_new(ret, "read response");
        }
    }
    
    return err;
}

SrsHttpConn::do_cycle中讀取完http請求後,接着調用請求處理函數process_request

srs_error_t SrsHttpConn::process_request(ISrsHttpResponseWriter* w, ISrsHttpMessage* r)
{
    srs_error_t err = srs_success;
    
    srs_trace("HTTP %s %s, content-length=%" PRId64 "",
        r->method_str().c_str(), r->url().c_str(), r->content_length());
    
    // use cors server mux to serve http request, which will proxy to http_remux.
    if ((err = cors->serve_http(w, r)) != srs_success) {
        return srs_error_wrap(err, "mux serve");
    }
    
    return err;
}

函數SrsHttpConn::process_request調用cors->serve_http(w, r)對請求處理。cors是SrsHttpConn的成員變量,類型爲SrsHttpCorsMux

srs_error_t SrsHttpCorsMux::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r)
{
    int ret = ERROR_SUCCESS;
    
    // If CORS enabled, and there is a "Origin" header, it's CORS.
    if (enabled) {
        for (int i = 0; i < r->request_header_count(); i++) {
            string k = r->request_header_key_at(i);
            if (k == "Origin" || k == "origin") {
                required = true;
                break;
            }
        }
    }
    
    // When CORS required, set the CORS headers.
    if (required) {
        SrsHttpHeader* h = w->header();
        h->set("Access-Control-Allow-Origin", "*");
        h->set("Access-Control-Allow-Methods", "GET, POST, HEAD, PUT, DELETE, OPTIONS");
        h->set("Access-Control-Expose-Headers", "Server,range,Content-Length,Content-Range");
        h->set("Access-Control-Allow-Headers", "origin,range,accept-encoding,referer,Cache-Control,X-Proxy-Authorization,X-Requested-With,Content-Type");
    }
    
    // handle the http options.
    if (r->is_http_options()) {
        w->header()->set_content_length(0);
        if (enabled) {
            w->write_header(SRS_CONSTS_HTTP_OK);
        } else {
            w->write_header(SRS_CONSTS_HTTP_MethodNotAllowed);
        }
        if ((ret = w->final_request()) != ERROR_SUCCESS) {
            return srs_error_new(ret, "final request");
        }
    }
    
    srs_assert(next);
    return next->serve_http(w, r);
}

SrsHttpCorsMux::serve_http首選寫入一些響應頭部,接着調用next->serve_http,該方法爲ISrsHttpServeMux類的
純虛函數,next實際上是SrsHttpServer* SrsServer::http_server。賦值過程如下:

fd2conn()–>new SrsResponseOnlyHttpConn(this, stfd, http_server, ip)–>SrsHttpConn(this, stdfd, http_server, ip)

–>SrsHttpConn(this, stdfd, http_server, ip)–>SrsHttpConn::http_mux = http_server;

SrsHttpConn::do_cycle()調用cors->initialize(http_mux, crossdomain_enabled)完成初始化。

cors->initialize函數中會給next賦值,next的實際就是SrsServer::http_server

srs_error_t SrsHttpCorsMux::initialize(ISrsHttpServeMux* worker, bool cros_enabled)
{
    next = worker;
    enabled = cros_enabled;
    
    return srs_success;
}

調用cors->serve_http(w, r)實際就是調用SrsHttpServer::serve_http,具體代碼如下

srs_error_t SrsHttpServer::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r)
{
    srs_error_t err = srs_success;
    
    // try http stream first.
    ISrsHttpHandler* h = NULL;
    if ((err = http_stream->mux.find_handler(r, &h)) != srs_success) {
        return srs_error_wrap(err, "find handler");
    }
    if (!h->is_not_found()) {
        return http_stream->mux.serve_http(w, r);
    }
    
    return http_static->mux.serve_http(w, r);
}

SrsHttpServer::serve_http首先判斷HttpStreamServer中是否註冊了url請求對應的handler。

srs_error_t SrsHttpServeMux::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r)
{
    srs_error_t err = srs_success;
    
    ISrsHttpHandler* h = NULL;
    if ((err = find_handler(r, &h)) != srs_success) {
        return srs_error_wrap(err, "find handler");
    }
    
    srs_assert(h);
    if ((err = h->serve_http(w, r)) != srs_success) {
        return srs_error_wrap(err, "serve http");
    }
    
    return err;
}

這裏分析的是直播流通過http-flv的方式發送給客戶端的場景。對於直播流實際調用的是SrsLiveStream的serve_http。
SrsLiveStream與url的建立關聯過程如下。

serve_http調用find_handler 然後會調用SrsHttpStreamServer::hijack接着調用SrsHttpStreamServer::http_mount,

http_mount函數中首先根據請求調用SrsSource::fetch_or_create創建或者獲取一個SrsSource對象用來從源端獲取流數據,

接着根據url判斷是否已經創建了handler,如果沒有創建一個新的,如果有返回已經存在的。

int SrsHttpStreamServer::http_mount(SrsSource* s, SrsRequest* r)
{
...... 
        // remove the default vhost mount
        mount = srs_string_replace(mount, SRS_CONSTS_RTMP_DEFAULT_VHOST"/", "/");
        
        entry = new SrsLiveEntry(mount);
        
        entry->cache = new SrsBufferCache(s, r);
        entry->stream = new SrsLiveStream(s, r, entry->cache);      
......
}

find_handler調用後會得到handler,接着調用handler的serve_http。在SrsLiveStream::serve_http循環中通過Cosumer從源站不斷的獲取數據,再通過FlvEncoder將數據寫入http響應中。

srs_error_t SrsLiveStream::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r)
{
    int ret = ERROR_SUCCESS;
    srs_error_t err = srs_success;
    
    ISrsBufferEncoder* enc = NULL;
    
    srs_assert(entry);
    if (srs_string_ends_with(entry->pattern, ".flv")) {
        w->header()->set_content_type("video/x-flv");
#ifdef SRS_PERF_FAST_FLV_ENCODER
        enc = new SrsFastFlvStreamEncoder();
#else
        enc = new SrsFlvStreamEncoder();
#endif
    } else if (srs_string_ends_with(entry->pattern, ".aac")) {
        w->header()->set_content_type("audio/x-aac");
        enc = new SrsAacStreamEncoder();
    } else if (srs_string_ends_with(entry->pattern, ".mp3")) {
        w->header()->set_content_type("audio/mpeg");
        enc = new SrsMp3StreamEncoder();
    } else if (srs_string_ends_with(entry->pattern, ".ts")) {
        w->header()->set_content_type("video/MP2T");
        enc = new SrsTsStreamEncoder();
    } else {
        return srs_error_new(ERROR_HTTP_LIVE_STREAM_EXT, "invalid pattern=%s", entry->pattern.c_str());
    }
    SrsAutoFree(ISrsBufferEncoder, enc);
    
    // create consumer of souce, ignore gop cache, use the audio gop cache.
    SrsConsumer* consumer = NULL;
    if ((ret = source->create_consumer(NULL, consumer, true, true, !enc->has_cache())) != ERROR_SUCCESS) {
        return srs_error_new(ret, "create consumer");
    }
    SrsAutoFree(SrsConsumer, consumer);
    srs_verbose("http: consumer created success.");
    
    SrsPithyPrint* pprint = SrsPithyPrint::create_http_stream();
    SrsAutoFree(SrsPithyPrint, pprint);
    
    SrsMessageArray msgs(SRS_PERF_MW_MSGS);
    
    // update the statistic when source disconveried.
    SrsStatistic* stat = SrsStatistic::instance();
    if ((ret = stat->on_client(_srs_context->get_id(), req, NULL, SrsRtmpConnPlay)) != ERROR_SUCCESS) {
        return srs_error_new(ret, "stat on client");
    }
    
    // the memory writer.
    SrsBufferWriter writer(w);
    if ((ret = enc->initialize(&writer, cache)) != ERROR_SUCCESS) {
        return srs_error_new(ret, "init encoder");
    }
    
    // if gop cache enabled for encoder, dump to consumer.
    if (enc->has_cache()) {
        if ((ret = enc->dump_cache(consumer, source->jitter())) != ERROR_SUCCESS) {
            return srs_error_new(ret, "encoder dump cache");
        }
    }
    
#ifdef SRS_PERF_FAST_FLV_ENCODER
    SrsFastFlvStreamEncoder* ffe = dynamic_cast<SrsFastFlvStreamEncoder*>(enc);
#endif
    
    // Use receive thread to accept the close event to avoid FD leak.
    // @see https://github.com/ossrs/srs/issues/636#issuecomment-298208427
    SrsHttpMessage* hr = dynamic_cast<SrsHttpMessage*>(r);
    SrsResponseOnlyHttpConn* hc = dynamic_cast<SrsResponseOnlyHttpConn*>(hr->connection());
    
    SrsHttpRecvThread* trd = new SrsHttpRecvThread(hc);
    SrsAutoFree(SrsHttpRecvThread, trd);
    
    if ((err = trd->start()) != srs_success) {
        return srs_error_wrap(err, "start recv thread");
    }

    // TODO: free and erase the disabled entry after all related connections is closed.
    while (entry->enabled) {
        pprint->elapse();
        
        // Whether client closed the FD.
        if ((err = trd->pull()) != srs_success) {
            return srs_error_wrap(err, "recv thread");
        }

        // get messages from consumer.
        // each msg in msgs.msgs must be free, for the SrsMessageArray never free them.
        int count = 0;
        if ((ret = consumer->dump_packets(&msgs, count)) != ERROR_SUCCESS) {
            return srs_error_new(ret, "consumer dump packets");
        }
        
        if (count <= 0) {
            srs_info("http: sleep %dms for no msg", SRS_CONSTS_RTMP_PULSE_TMMS);
            // directly use sleep, donot use consumer wait.
            srs_usleep(SRS_CONSTS_RTMP_PULSE_TMMS * 1000);
            
            // ignore when nothing got.
            continue;
        }
        
        if (pprint->can_print()) {
            srs_info("-> " SRS_CONSTS_LOG_HTTP_STREAM " http: got %d msgs, age=%d, min=%d, mw=%d",
                count, pprint->age(), SRS_PERF_MW_MIN_MSGS, SRS_CONSTS_RTMP_PULSE_TMMS);
        }
        
        // sendout all messages.
#ifdef SRS_PERF_FAST_FLV_ENCODER
        if (ffe) {
            ret = ffe->write_tags(msgs.msgs, count);
        } else {
            ret = streaming_send_messages(enc, msgs.msgs, count);
        }
#else
        ret = streaming_send_messages(enc, msgs.msgs, count);
#endif
        
        // free the messages.
        for (int i = 0; i < count; i++) {
            SrsSharedPtrMessage* msg = msgs.msgs[i];
            srs_freep(msg);
        }
        
        // check send error code.
        if (ret != ERROR_SUCCESS) {
            return srs_error_new(ret, "send messages");
        }
    }
    
    return err;
}

首先該函數會調用source->create_consumer創建一個SrsConsumer對象,用來接收SrsSource中獲取的源站數據。
然後該函數會開啓一個新的協程來處理客戶端的請求。

/**

  • The HTTP receive thread, try to read messages util EOF.
  • For example, the HTTP FLV serving thread will use the receive thread to break
  • when client closed the request, to avoid FD leak.

SrsHttpMessage* hr = dynamic_cast<SrsHttpMessage*>®;
SrsResponseOnlyHttpConn* hc = dynamic_cast<SrsResponseOnlyHttpConn*>(hr->connection());

SrsHttpRecvThread* trd = new SrsHttpRecvThread(hc);

然後程序進入到循環中通過cosumer不斷的從源站獲取數據,再通過SrsFlvStreamEncoder::write_tags將拉取的數據寫入http響應中。

當處理客戶端的協程發現客戶端關閉連接時,該循環退出
————————————————
版權聲明:本文爲CSDN博主「Suk_39799839」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/weixin_39799839/article/details/78641577

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章