用libnice庫做tcp連接(voip應(yīng)用)

為什么

在開源licode中用到libnice作為udp建連和傳輸, 同webrtc通信, 而且網(wǎng)上有較多l(xiāng)ibnice udp連接的資源, 卻很少有l(wèi)ibnice tcp連接的資料, 而我需要用libnice做兩路傳輸連接, 一路做udp傳輸通道(實(shí)時(shí)), 一路做tcp傳輸通道(可靠), 因此調(diào)研并實(shí)測(cè)了libnice tcp的連接方式, 以下作為記錄。

傳輸組件

傳輸模塊

服務(wù)端用的開源mediasoup, 在Transport模塊同時(shí)開啟udp, tcp兩個(gè)服務(wù)監(jiān)聽, 無論tcp還是udp數(shù)據(jù)流都流向同一個(gè)transport。
在客戶端, 一次創(chuàng)建兩路NetworkAgent, 而NetworkAgent的實(shí)現(xiàn)采用的libnice, 一個(gè)啟用libnice udp(reliable = false, ice-udp=true), 一個(gè)啟用libnice tcp(reliable = true, ice-tcp=true)。

libnice tcp建連過程

建連過程

注意, libnice中有一個(gè)bug, 會(huì)導(dǎo)致tcp建連大概率失敗, 如圖所示, 可在connect和stun send之間加一個(gè)短暫的時(shí)延來解決。

libnice tcp配置和測(cè)試

  • libnice debug模式開啟
  1. 編譯libnice.a 時(shí)在CFlag中增加 NDBUG宏定義
  2. 開啟詳細(xì)日志:
nice_debug_enable(true);
setenv("NICE_DEBUG", "all", true);
setenv("G_MESSAGES_DEBUG", "all", true);
  • libnice tcp初始化
    //開啟libnice debug, 可以看到詳細(xì)的建連日志
    nice_debug_enable(true);
    setenv("NICE_DEBUG", "all", true);
    setenv("G_MESSAGES_DEBUG", "all", true);
    
    //創(chuàng)建NiceAgent
    _context = g_main_context_new();
    g_networking_init();
    _agent = (NiceAgent*) g_object_new(NICE_TYPE_AGENT,
                                       "compatibility", NICE_COMPATIBILITY_RFC5245,
                                       "main-context", _context,
                                       "reliable", _config.reliable ? TRUE : FALSE,  // 選擇tcp 則reliable = true
                                       "full-mode", !_config.ice_lite,
                                       "ice-tcp", _config.reliable ? TRUE : FALSE, // tcp
                                       "ice-udp", _config.reliable ? FALSE : TRUE, // udp
                                       NULL);
    //啟動(dòng)nice main thread
    _loop = g_main_loop_new(_context, FALSE);
    g_main_loop_run(_loop);
    
    //這里mediaserver是lite模式,因此客戶端固定做control
    //set controlling mode
    GValue controllingMode = { 0 };
    g_value_init(&controllingMode, G_TYPE_BOOLEAN);
    g_value_set_boolean(&controllingMode, _config.ice_lite);
    g_object_set_property(G_OBJECT(_agent), "controlling-mode", &controllingMode);
    
    //設(shè)置連通性檢查的最次數(shù)
    //set max checks
    GValue checks = { 0 };
    g_value_init(&checks, G_TYPE_UINT);
    g_value_set_uint(&checks, 100);
    g_object_set_property(G_OBJECT(_agent), "max-connectivity-checks", &checks);
    _candidates.clear();
    
    // 配置stun server
    if (!_config.stun_server.empty() && _config.stun_port != 0) {
        GValue val = { 0 }, val2 = { 0 };
        g_value_init(&val, G_TYPE_STRING);
        g_value_set_string(&val, _config.stun_server.c_str());
        g_object_set_property(G_OBJECT(_agent), "stun-server", &val);
        g_value_init(&val2, G_TYPE_UINT);
        g_value_set_uint(&val2, _config.stun_port);
        g_object_set_property(G_OBJECT(_agent), "stun-server-port", &val2);
    }

    // 一些回調(diào)
    g_signal_connect(G_OBJECT(_agent), "candidate-gathering-done", G_CALLBACK(cb_candidate_gathering_done), this);
    g_signal_connect(G_OBJECT(_agent), "component-state-changed", G_CALLBACK(cb_component_state_changed), this);
    g_signal_connect(G_OBJECT(_agent), "new-selected-pair", G_CALLBACK(cb_new_selected_pair), this);
    g_signal_connect(G_OBJECT(_agent), "new-candidate", G_CALLBACK(cb_new_candidate), this);

  //創(chuàng)建一個(gè)stream, 且這個(gè)stream里只有一個(gè)conponent, 如果需要將rtp, rtcp分開發(fā)送,可以用兩個(gè)conponent。
    _streamId = nice_agent_add_stream(_agent, 1);
    nice_agent_set_stream_name(_agent, _streamId, "video");
    
// 配置turn服務(wù), 這里不需要
    if (!_config.ice_lite) {
        nice_agent_set_relay_info(_agent, _streamId, _config.ice_components, _config.turn_server.c_str(), _config.turn_port,
                                  _config.turn_username.c_str(), _config.turn_pass.c_str(), NICE_RELAY_TYPE_TURN_UDP);
    }

    // 設(shè)置端口號(hào)范圍
    if (_config.min_port != 0 && _config.max_port != 0) {
             _config.min_port, _config.max_port);
        nice_agent_set_port_range(_agent, _streamId, _componentId, (guint) _config.min_port,
                                  (guint) _config.max_port);
    }
    
    // 產(chǎn)生本地的ice name, passwd
    gchar* ufrag = nullptr, *upass = nullptr;
    nice_agent_get_local_credentials(_agent, _streamId, &ufrag, &upass);
    _ufrag = std::string(ufrag);
    g_free(ufrag);
    _upass = std::string(upass);
    g_free(upass);

    // 綁定接收數(shù)據(jù)的回調(diào)
    auto casted_function = reinterpret_cast<NiceAgentRecvFunc>(cb_nice_recv);
    nice_agent_attach_recv(_agent, _streamId, _componentId, _context, casted_function, this);

   
    // 開始收集本地的local candidates
    if (nice_agent_gather_candidates(_agent, _streamId) != TRUE) {
        return false;
    }

  • libnice tcp 連接

/*
    m=video 64499 ICE/SDP                                   
    c=IN IP4 0.0.0.0                                                                        
    a=ice-ufrag:tr06wupyrgrvzmsr                                                            
    a=ice-pwd:q8x7lzyngtwwkzl4z5i82kjw9ath9gex                                              
    a=candidate:0 1 udp 0 10.224.17.136 15444 typ host                                      
    a=candidate:1 1 tcp 0 10.224.17.136 15939 typ host 
 */
    std::string sdp = remoteSDP;

    if (_config.reliable) {
        // 可靠傳輸, 支持tcp-pass, tcp-act兩種transport type, tcp-pass即tcp passive模式, 一般在服務(wù)端使用,監(jiān)聽tcp連接, tcp-act即tcp active模式,一般客戶端使用,主動(dòng)發(fā)起連接。
        std::string str1 = "tcp";
        std::string str2 = "tcp-pass";

        if (sdp.find(str1) != string::npos) {
            sdp = sdp.replace(sdp.find(str1), str1.length(), str2);
        }

        // 去掉udp candidate
        std::string eraseUdp = "a=candidate:0 1 udp";

        if (sdp.find(eraseUdp) != string::npos) {
            auto pos1 = sdp.find(eraseUdp);
            auto pos2 = sdp.find("typ host", pos1);
            sdp = sdp.replace(pos1, pos2 - pos1 + 8, "");
        }
    } else {
       // 去掉 tcp candidate
        std::string eraseTcp = "a=candidate:1 1 tcp";

        if (sdp.find(eraseTcp) != string::npos) {
            auto pos1 = sdp.find(eraseTcp);
            auto pos2 = sdp.find("typ host", pos1);
            sdp = sdp.replace(pos1, pos2 - pos1 + 8, "");
        }
    }

    // Because we send & recv audio/video packet on a single port,
    // So we only reserve "m=audio" or "m=video" in the sdp
    size_t audioline = sdp.find("m=audio");
    size_t videoline = sdp.find("m=video");

    if (audioline != string::npos && videoline != string::npos) {
        if (videoline < audioline) {
            sdp.erase(audioline, sdp.length() - audioline);
        } else {
            sdp.erase(audioline, videoline - audioline);
        }
    }

    // 有了遠(yuǎn)端的candidates, 而本地的candidates在上一步已經(jīng)拿到, 可以進(jìn)行連通性檢查了。
    if (nice_agent_parse_remote_sdp(_agent, sdp.c_str()) <= 0) {
        return false;
    }


  • mediasoup服務(wù)
  1. 在transport同時(shí)enableUdp, enableTcp
  2. IceServer中合存reliableTransportTuple和transportTuple
  3. 在transport中, 不同media kind的數(shù)據(jù),走不同的transportTuple.

參考

https://tools.ietf.org/html/rfc5245 ice建連過程

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容