日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

WebRTC之STUN、TURN和ICE研究

發布時間:2024/8/1 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 WebRTC之STUN、TURN和ICE研究 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

準備查看WebRTC源碼對應以下這些文章中的協議格式深入研究一下ICE。

這三篇文章是目前我看過的最好的ICE文章:
P2P通信標準協議(一)之STUN
P2P通信標準協議(二)之TURN
P2P通信標準協議(三)之ICE

這個可以做為補充:
P2P技術詳解(三):P2P技術之STUN、TURN、ICE詳解


先學習上面文章的基礎知識,然后開始分析WebRTC創建PeerConnection直到連接Stun和Turn的流程:

bool PeerConnection::InitializePortAllocator_n( ...if (ParseIceServers(configuration.servers, &stun_servers, &turn_servers) != ... port_allocator_->SetConfiguration(stun_servers, turn_servers, configuration.ice_candidate_pool_size,configuration.prune_turn_ports, configuration.turn_customizer,configuration.stun_candidate_keepalive_interval); ... } bool PortAllocator::SetConfiguration( ...stun_servers_ = stun_servers;turn_servers_ = turn_servers; ... // If |candidate_pool_size_| is greater than the number of pooled sessions,// create new sessions.while (static_cast<int>(pooled_sessions_.size()) < candidate_pool_size_) {PortAllocatorSession* pooled_session = CreateSessionInternal("", 0, "", "");pooled_session->StartGettingPorts();pooled_sessions_.push_back(std::unique_ptr<PortAllocatorSession>(pooled_session));}return true; }

PeerConnection在初始化時創建了port_allocator_,同時調用了PortAllocator::SetConfiguration把stun_servers和turn_servers存儲起來。
并且調用了BasicPortAllocatorSession::StartGettingPorts()


void BasicPortAllocatorSession::StartGettingPorts() { ... network_thread_->Post(RTC_FROM_HERE, this, MSG_CONFIG_START); ... } void BasicPortAllocatorSession::OnMessage(rtc::Message *message) {switch (message->message_id) {case MSG_CONFIG_START:RTC_DCHECK(rtc::Thread::Current() == network_thread_);GetPortConfigurations(); ... } void BasicPortAllocatorSession::GetPortConfigurations() {PortConfiguration* config = new PortConfiguration(allocator_->stun_servers(),username(),password());for (const RelayServerConfig& turn_server : allocator_->turn_servers()) {config->AddRelay(turn_server);}ConfigReady(config); }void BasicPortAllocatorSession::ConfigReady(PortConfiguration* config) {network_thread_->Post(RTC_FROM_HERE, this, MSG_CONFIG_READY, config); } void BasicPortAllocatorSession::OnMessage(rtc::Message *message) {switch (message->message_id) { ...case MSG_CONFIG_READY:RTC_DCHECK(rtc::Thread::Current() == network_thread_);OnConfigReady(static_cast<PortConfiguration*>(message->pdata));break; ... } // Adds a configuration to the list. void BasicPortAllocatorSession::OnConfigReady(PortConfiguration* config) {if (config) {configs_.push_back(config);}AllocatePorts(); }

// For each network, see if we have a sequence that covers it already. If not, // create a new sequence to create the appropriate ports. void BasicPortAllocatorSession::DoAllocate(bool disable_equivalent) { ...AllocationSequence* sequence =new AllocationSequence(this, networks[i], config, sequence_flags);sequence->SignalPortAllocationComplete.connect(this, &BasicPortAllocatorSession::OnPortAllocationComplete);sequence->Init();sequence->Start();sequences_.push_back(sequence); ... }

一路把PortConfiguration *config傳進來,創建AllocationSequence* sequence,并且調用了Start()方法


void AllocationSequence::Start() {state_ = kRunning;session_->network_thread()->Post(RTC_FROM_HERE, this, MSG_ALLOCATION_PHASE);// Take a snapshot of the best IP, so that when DisableEquivalentPhases is// called next time, we enable all phases if the best IP has since changed.previous_best_ip_ = network_->GetBestIP(); } void AllocationSequence::OnMessage(rtc::Message* msg) {RTC_DCHECK(rtc::Thread::Current() == session_->network_thread());RTC_DCHECK(msg->message_id == MSG_ALLOCATION_PHASE);const char* const PHASE_NAMES[kNumPhases] = {"Udp", "Relay", "Tcp"};// Perform all of the phases in the current step.RTC_LOG(LS_INFO) << network_->ToString()<< ": Allocation Phase=" << PHASE_NAMES[phase_];switch (phase_) {case PHASE_UDP:CreateUDPPorts();CreateStunPorts();break;case PHASE_RELAY:CreateRelayPorts();break;case PHASE_TCP:CreateTCPPorts();state_ = kCompleted;break;default:RTC_NOTREACHED();}if (state() == kRunning) {++phase_;session_->network_thread()->PostDelayed(RTC_FROM_HERE,session_->allocator()->step_delay(),this, MSG_ALLOCATION_PHASE);} else {// If all phases in AllocationSequence are completed, no allocation// steps needed further. Canceling pending signal.session_->network_thread()->Clear(this, MSG_ALLOCATION_PHASE);SignalPortAllocationComplete(this);} }

phase_默認為0,也就是PHASE_UDP,然后只要state() == kRunning還會++phase不斷發送MSG_ALLOCATION_PHASE消息延遲回調自己,也就是 CreateRelayPorts();和 CreateTCPPorts();都會被調用。延遲的時間為session_->allocator()->step_delay(),被設置成kMinimumStepDelay,也就是說只有50毫秒間隔就執行。

// As per RFC 5245 Appendix B.1, STUN transactions need to be paced at certain // internal. Less than 20ms is not acceptable. We choose 50ms as our default. const uint32_t kMinimumStepDelay = 50;

有人在代碼中注釋說這個間隔太短,導致同時有太多的STUN連接:

// Delay between different candidate gathering phases (UDP, TURN, TCP).// Defaults to 1 second, but PeerConnection sets it to 50ms.// TODO(deadbeef): Get rid of this. Its purpose is to avoid sending too many// STUN transactions at once, but that's already happening if you configure// multiple STUN servers or have multiple network interfaces. We should// implement some global pacing logic instead if that's our goal.uint32_t step_delay() const { return step_delay_; }void set_step_delay(uint32_t delay) { step_delay_ = delay; }

CreateUDPPorts();創建了UDPPort,并且調用session_->AddAllocatedPort(port, this, true);。
CreateStunPorts();創建了StunPort,也調用了session_->AddAllocatedPort(port, this, true);。
StunPort繼承于UDPPort。
UDPPort的type_為LOCAL_PORT_TYPE,而StunPort的type為STUN_PORT_TYPE。

暫時還不知道為什么要創建UDPPort和StunPort,按道理一個UDPPort或者StunPort就可以了。
補充:仔細看了一下CreateStunPorts():

void AllocationSequence::CreateStunPorts() { ...if (IsFlagSet(PORTALLOCATOR_ENABLE_SHARED_SOCKET)) {return;} ...

而PeerConnection初始化時設置了PORTALLOCATOR_ENABLE_SHARED_SOCKET,所以CreateStunPorts()其實不會執行,只有UDPPort是有效的。


void BasicPortAllocatorSession::AddAllocatedPort(Port* port,AllocationSequence * seq,bool prepare_address) {if (!port)return;RTC_LOG(LS_INFO) << "Adding allocated port for " << content_name();port->set_content_name(content_name());port->set_component(component());port->set_generation(generation());if (allocator_->proxy().type != rtc::PROXY_NONE)port->set_proxy(allocator_->user_agent(), allocator_->proxy());port->set_send_retransmit_count_attribute((flags() & PORTALLOCATOR_ENABLE_STUN_RETRANSMIT_ATTRIBUTE) != 0);PortData data(port, seq);ports_.push_back(data);port->SignalCandidateReady.connect(this, &BasicPortAllocatorSession::OnCandidateReady);port->SignalPortComplete.connect(this,&BasicPortAllocatorSession::OnPortComplete);port->SignalDestroyed.connect(this,&BasicPortAllocatorSession::OnPortDestroyed);port->SignalPortError.connect(this, &BasicPortAllocatorSession::OnPortError);RTC_LOG(LS_INFO) << port->ToString()<< ": Added port to allocator";if (prepare_address)port->PrepareAddress(); }

UDPPort和StunPort的PrepareAddress()都調用了UDPPort::SendStunBindingRequests()

void UDPPort::SendStunBindingRequests() {// We will keep pinging the stun server to make sure our NAT pin-hole stays// open until the deadline (specified in SendStunBindingRequest).RTC_DCHECK(requests_.empty());for (ServerAddresses::const_iterator it = server_addresses_.begin();it != server_addresses_.end(); ++it) {SendStunBindingRequest(*it);} } void UDPPort::SendStunBindingRequest(const rtc::SocketAddress& stun_addr) { ...requests_.Send(new StunBindingRequest(this, stun_addr, rtc::TimeMillis())); ... }

StunBindingRequest繼承于StunRequest,其內部有個StunMessage* msg_,
StunMessage封裝了Stun客戶端協議。
源碼對照P2P通信標準協議(一)之STUN所寫的STUN協議格式。

第一條發出的消息的type_為STUN_BINDING_REQUEST,值為0x0001。


關于STUN Message Type分解成以下結構

0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +--+--+-+-+-+-+-+-+-+-+-+-+-+-+ |M |M |M|M|M|C|M|M|M|C|M|M|M|M| |11|10|9|8|7|1|6|5|4|0|3|2|1|0| +--+--+-+-+-+-+-+-+-+-+-+-+-+-+

其中顯示的位為從最高有效位M11到最低有效位M0,M11到M0表示方法的12位編碼。C1和C0兩位表示類的編碼。比如對于binding方法來說,
0b00表示request,0b01表示indication,0b10表示success response,0b11表示error response,每一個method都有可能對應不同的傳輸類別。

以上0b是二進制前綴(對應16進制的0x),所以0b00也就是C1C0都為0,0b11則C1==1,C0==1。
所以最多只有4種類別,查看C1C0就可以知道是哪種類別。

RFC5389文檔說明:
For example, a Binding request has class=0b00 (request) and
method=0b000000000001 (Binding) and is encoded into the first 16 bits
as 0x0001. A Binding response has class=0b10 (success response) and
method=0b000000000001, and is encoded into the first 16 bits as
0x0101.


bool StunMessage::Write(ByteBufferWriter* buf) const {buf->WriteUInt16(type_);buf->WriteUInt16(length_);if (!IsLegacy())buf->WriteUInt32(stun_magic_cookie_);buf->WriteString(transaction_id_);for (const auto& attr : attrs_) {buf->WriteUInt16(attr->type());buf->WriteUInt16(static_cast<uint16_t>(attr->length()));if (!attr->Write(buf)) {return false;}}return true; }

StunMessage::Write封裝了要發出的數據包。

StunRequest::StunRequest(){ ... msg_->SetTransactionID(rtc::CreateRandomString(kStunTransactionIdLength)); ... }

transaction_id_是事務ID, 可以看出transaction_id_的值是個隨機字符串。
寫入transaction_id_后再寫入所有STUN屬性。
STUN屬性的基類為StunAttribute,其派生了好幾個類。
StunAttribute::Create中列出了所有派生類。

StunAttribute* StunAttribute::Create(StunAttributeValueType value_type,uint16_t type,uint16_t length,StunMessage* owner) {switch (value_type) {case STUN_VALUE_ADDRESS:return new StunAddressAttribute(type, length);case STUN_VALUE_XOR_ADDRESS:return new StunXorAddressAttribute(type, length, owner);case STUN_VALUE_UINT32:return new StunUInt32Attribute(type);case STUN_VALUE_UINT64:return new StunUInt64Attribute(type);case STUN_VALUE_BYTE_STRING:return new StunByteStringAttribute(type, length);case STUN_VALUE_ERROR_CODE:return new StunErrorCodeAttribute(type, length);case STUN_VALUE_UINT16_LIST:return new StunUInt16ListAttribute(type, length);default:return NULL;} }

UDPPort::OnReadPacket處理服務器返回的STUN消息。
調用基類方法Port::OnReadPacket驗證如果是有效的STUN回包則解包到IceMessage。
IceMessage派生于StunMessage。


事實上在查看STUN客戶端的代碼過程中可以看出不僅僅有STUN客戶端功能,同時還有ICE功能。當客戶端雙方Offer-Answer得到sdp,并且交換Candidate之后,就會啟用ICE功能,雙方使用STUN協議來打通NAT,此時跟外部的STUN服務器已經沒有關系了。


下面繼續分析TURN客戶端代碼,從CreateRelayPorts()開始:
void AllocationSequence::CreateRelayPorts() { ...for (RelayServerConfig& relay : config_->relays) {if (relay.type == RELAY_GTURN) {CreateGturnPort(relay);} else if (relay.type == RELAY_TURN) {CreateTurnPort(relay);} else {RTC_NOTREACHED();}} }

CreateGturnPort針對的是google自己的TURN服務,CreateTurnPort針對的是標準的TURN服務。以下只分析CreateTurnPort。

void AllocationSequence::CreateTurnPort(const RelayServerConfig& config) {PortList::const_iterator relay_port;for (relay_port = config.ports.begin();relay_port != config.ports.end(); ++relay_port) {// Skip UDP connections to relay servers if it's disallowed.if (IsFlagSet(PORTALLOCATOR_DISABLE_UDP_RELAY) &&relay_port->proto == PROTO_UDP) {continue;}// Do not create a port if the server address family is known and does// not match the local IP address family.int server_ip_family = relay_port->address.ipaddr().family();int local_ip_family = network_->GetBestIP().family();if (server_ip_family != AF_UNSPEC && server_ip_family != local_ip_family) {RTC_LOG(LS_INFO)<< "Server and local address families are not compatible. ""Server address: " << relay_port->address.ipaddr().ToString()<< " Local address: " << network_->GetBestIP().ToString();continue;}CreateRelayPortArgs args;args.network_thread = session_->network_thread();args.socket_factory = session_->socket_factory();args.network = network_;args.username = session_->username();args.password = session_->password();args.server_address = &(*relay_port);args.config = &config;args.origin = session_->allocator()->origin();args.turn_customizer = session_->allocator()->turn_customizer();std::unique_ptr<cricket::Port> port;// Shared socket mode must be enabled only for UDP based ports. Hence// don't pass shared socket for ports which will create TCP sockets.// TODO(mallinath) - Enable shared socket mode for TURN ports. Disabled// due to webrtc bug https://code.google.com/p/webrtc/issues/detail?id=3537if (IsFlagSet(PORTALLOCATOR_ENABLE_SHARED_SOCKET) &&relay_port->proto == PROTO_UDP && udp_socket_) {port = session_->allocator()->relay_port_factory()->Create(args, udp_socket_.get());if (!port) {RTC_LOG(LS_WARNING)<< "Failed to create relay port with "<< args.server_address->address.ToString();continue;}relay_ports_.push_back(port.get());// Listen to the port destroyed signal, to allow AllocationSequence to// remove entrt from it's map.port->SignalDestroyed.connect(this, &AllocationSequence::OnPortDestroyed);} else {port = session_->allocator()->relay_port_factory()->Create(args,session_->allocator()->min_port(),session_->allocator()->max_port());if (!port) {RTC_LOG(LS_WARNING)<< "Failed to create relay port with "<< args.server_address->address.ToString();continue;}}RTC_DCHECK(port != NULL);session_->AddAllocatedPort(port.release(), this, true);} }

主要在于:

port = session_->allocator()->relay_port_factory()->Create(args, udp_socket_.get()); std::unique_ptr<Port> TurnPortFactory::Create(const CreateRelayPortArgs& args,int min_port,int max_port) {TurnPort* port = TurnPort::Create(args.network_thread,args.socket_factory,args.network,min_port,max_port,args.username,args.password,*args.server_address,args.config->credentials,args.config->priority,args.origin,args.config->tls_alpn_protocols,args.config->tls_elliptic_curves,args.turn_customizer);port->SetTlsCertPolicy(args.config->tls_cert_policy);return std::unique_ptr<Port>(port); }

TurnPortFactory創建TurnPort,同樣AddAllocatedPort,然后TurnPort::PrepareAddress()

void TurnPort::PrepareAddress() {if (credentials_.username.empty() ||credentials_.password.empty()) {RTC_LOG(LS_ERROR) << "Allocation can't be started without setting the"" TURN server credentials for the user.";OnAllocateError();return;}if (!server_address_.address.port()) {// We will set default TURN port, if no port is set in the address.server_address_.address.SetPort(TURN_DEFAULT_PORT);}if (server_address_.address.IsUnresolvedIP()) {ResolveTurnAddress(server_address_.address);} else {// If protocol family of server address doesn't match with local, return.if (!IsCompatibleAddress(server_address_.address)) {RTC_LOG(LS_ERROR) << "IP address family does not match. server: "<< server_address_.address.family()<< " local: " << Network()->GetBestIP().family();OnAllocateError();return;}// Insert the current address to prevent redirection pingpong.attempted_server_addresses_.insert(server_address_.address);RTC_LOG(LS_INFO) << ToString()<< ": Trying to connect to TURN server via "<< ProtoToString(server_address_.proto) << " @ "<< server_address_.address.ToSensitiveString();if (!CreateTurnClientSocket()) {RTC_LOG(LS_ERROR) << "Failed to create TURN client socket";OnAllocateError();return;}if (server_address_.proto == PROTO_UDP) {// If its UDP, send AllocateRequest now.// For TCP and TLS AllcateRequest will be sent by OnSocketConnect.SendRequest(new TurnAllocateRequest(this), 0);}} }

可以看出必須要有username和password,這也就是coTurn中所謂的要支持WebRTC必須開啟long-term credentials。

TurnAllocateRequest也是派生于StunRequest,但是其內部的msg_為TurnMessage。
看了半天TurnMessage,原來目前的WebRTC版本還不支持turn oauth驗證,而那 個W3C WebRTC 1.0: Real-time Communication Between Browsers 只是標準草案,并沒有完全實現,WTF。

總結

以上是生活随笔為你收集整理的WebRTC之STUN、TURN和ICE研究的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 国产免费91视频 | 国产一区二区精品丝袜 | 国产乱色精品成人免费视频 | 亚洲黄av | 亚洲91网站| 日本护士╳╳╳hd少妇 | av之家在线 | 日干夜干天天干 | 高h捆绑拘束调教小说 | 都市激情av | 日韩免费成人av | 欧美精品一区二区免费看 | 无遮挡无掩盖网站 | 国产在线欧美在线 | 日本成人社区 | 国产精品久久婷婷六月丁香 | 亚洲天堂精品视频 | 白丝av | 中国精品毛片 | 波多野结衣一区二区三区中文字幕 | 久久99热精品 | 在线看v片 | 国产videos | 高h喷水荡肉少妇爽多p视频 | 日韩三区在线观看 | 精品久久无码中文字幕 | 根深蒂固在线观看 | 91偷拍精品一区二区三区 | 激情偷乱人成视频在线观看 | 91九色在线视频 | 国产伦精品一区二区三区免费迷 | 女女同性高清片免费看 | 激情小说中文字幕 | 日韩av无码一区二区三区不卡 | 黄色香蕉软件 | av在线免费播放网站 | 韩国明星乱淫(高h)小说 | 欧美性大战久久久久久久 | 美女主播福利视频 | 人人九九 | www.成年人| 亚洲一区精品视频在线观看 | 久久免费看| 午夜有码 | 台湾佬av| 老色鬼在线 | 打屁股调教网站 | 色涩综合 | 人人色视频 | 日韩一区二区高清视频 | 久久精品国产亚洲AV无码麻豆 | 永久看看免费大片 | 国产伦精品一区二区三区视频女 | 天天干夜夜爽 | 亚洲片在线观看 | 女人18毛片毛片毛片毛片区二 | 娇小tube性极品娇小 | 在线观看网站av | 好爽又高潮了毛片 | 亚洲啊v在线| 中文字幕欧美亚洲 | 91麻豆一区二区三区 | 黄色一级在线视频 | 欧美专区视频 | 亚洲五月天综合 | 国产免费观看视频 | 无套内谢的新婚少妇国语播放 | 久久无码人妻精品一区二区三区 | 亚洲天堂久久新 | 麻豆国产精品 | 岛国精品一区 | 亚洲国产精品二区 | 风间由美一二三区av片 | 破处视频在线观看 | 免费的黄色av | 欧美混交群体交 | www免费网站在线观看 | 舒淇裸体午夜理伦 | 亚洲深夜福利视频 | 99精品一区二区三区无码吞精 | 男女插插网站 | 操操操操操操操操操 | 国产又粗又硬又黄的视频 | 波多野结衣加勒比 | av2014天堂网 | 日韩欧美一卡二卡 | 国产中文自拍 | 人人爱人人澡 | 丁香花在线影院观看在线播放 | 欧美亚洲二区 | 粉嫩欧美一区二区三区 | 丰满岳乱妇一区二区三区 | 激情六月 | 精品99在线观看 | 日本中文字幕网 | 欧美片免费网站 | 精品人妻一区二区三区蜜桃 | 超碰人人艹 | 加勒比波多野结衣 |