美文网首页
用libnice库做tcp连接(voip应用)

用libnice库做tcp连接(voip应用)

作者: myxu_bin | 来源:发表于2020-02-24 12:05 被阅读0次

    为什么

    在开源licode中用到libnice作为udp建连和传输, 同webrtc通信, 而且网上有较多libnice udp连接的资源, 却很少有libnice tcp连接的资料, 而我需要用libnice做两路传输连接, 一路做udp传输通道(实时), 一路做tcp传输通道(可靠), 因此调研并实测了libnice tcp的连接方式, 以下作为记录。

    传输组件

    传输模块

    服务端用的开源mediasoup, 在Transport模块同时开启udp, tcp两个服务监听, 无论tcp还是udp数据流都流向同一个transport。
    在客户端, 一次创建两路NetworkAgent, 而NetworkAgent的实现采用的libnice, 一个启用libnice udp(reliable = false, ice-udp=true), 一个启用libnice tcp(reliable = true, ice-tcp=true)。

    libnice tcp建连过程

    建连过程

    注意, libnice中有一个bug, 会导致tcp建连大概率失败, 如图所示, 可在connect和stun send之间加一个短暂的时延来解决。

    libnice tcp配置和测试

    • libnice debug模式开启
    1. 编译libnice.a 时在CFlag中增加 NDBUG宏定义
    2. 开启详细日志:
    nice_debug_enable(true);
    setenv("NICE_DEBUG", "all", true);
    setenv("G_MESSAGES_DEBUG", "all", true);
    
    • libnice tcp初始化
        //开启libnice debug, 可以看到详细的建连日志
        nice_debug_enable(true);
        setenv("NICE_DEBUG", "all", true);
        setenv("G_MESSAGES_DEBUG", "all", true);
        
        //创建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);
        //启动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);
        
        //设置连通性检查的最次数
        //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);
        }
    
        // 一些回调
        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);
    
      //创建一个stream, 且这个stream里只有一个conponent, 如果需要将rtp, rtcp分开发送,可以用两个conponent。
        _streamId = nice_agent_add_stream(_agent, 1);
        nice_agent_set_stream_name(_agent, _streamId, "video");
        
    // 配置turn服务, 这里不需要
        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);
        }
    
        // 设置端口号范围
        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);
        }
        
        // 产生本地的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);
    
        // 绑定接收数据的回调
        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模式, 一般在服务端使用,监听tcp连接, tcp-act即tcp active模式,一般客户端使用,主动发起连接。
            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);
            }
        }
    
        // 有了远端的candidates, 而本地的candidates在上一步已经拿到, 可以进行连通性检查了。
        if (nice_agent_parse_remote_sdp(_agent, sdp.c_str()) <= 0) {
            return false;
        }
    
    
    
    • mediasoup服务
    1. 在transport同时enableUdp, enableTcp
    2. IceServer中合存reliableTransportTuple和transportTuple
    3. 在transport中, 不同media kind的数据,走不同的transportTuple.

    参考

    https://tools.ietf.org/html/rfc5245 ice建连过程

    相关文章

      网友评论

          本文标题:用libnice库做tcp连接(voip应用)

          本文链接:https://www.haomeiwen.com/subject/bwimqhtx.html