WebRTC之P2P

WebRTC之P2P

文章目录

    • SDP/STUN/TURN/ICE
      • SDP
      • STUN
        • 服务端实现
        • 客户端实现
        • NAT类型判断
      • TURN
        • TurnServer(TURN服务端)
        • TurnPort(TURN客户端)
      • ICE
    • NAT类型
      • 完全圆锥形NAT(Full cone NAT)
      • 受限圆锥形NAT(Address-Restricted cone NAT)
      • 端口受限圆锥形NAT(Port-Restricted cone NAT)
      • 对称NAT(Symmetric NAT)

SDP/STUN/TURN/ICE

对这几种名称进行简单介绍如下:

  • SDP是一种用于描述媒体信息的标准协议,例如分辨率、编码器、加密等
  • Offer/Answer,我们要和对端交换的描述信息就称为Offer,对端发给我们的描述信息就称为Answer,不同客户端支持的编解码类型是不一样的,所以需要协商
  • STUN是一种获取NAT公网IP,以及NAT类型的协议
  • TURN是在STUN基础上增加转发功能的协议
  • ICE就是把STUN和TURN的结合

SDP

以下SDP内容完全来自维基-Session Description Protocol,阅读原文获取更全面的信息。

SDP是用于描述流媒体通信参数的格式。IETF在1998年4月发布了原始规范作为拟议标,随后在2006年7月发布了修订的规范RFC4566。
SDP用于会话描述通告,会话邀请和参数协商等多媒体通信会话。SDP本身并不传递任何媒体,而是在端点之间用于协商媒体类型,格式和所有相关属性。属性和参数的集合通常称为会话配置文件。
SDP被设计为可扩展的,以支持新的媒体类型和格式。SDP最初是作为会话公告协议(SAP)的组成部分,但发现它与实时传输协议(RTP),实时流协议(RTSP),会话发起协议(SIP)和即使是用于描述多播会话的独立格式。

STUN

以下STUN内容完全来自维基-STUN,阅读原文获取更全面的信息。

STUN(Session Traversal Utilities for NAT,NAT会话穿越应用程序)是一种网络协议,它允许位于NAT(或多重NAT)后的客户端找出自己的公网地址,查出自己位于哪种类型的NAT之后以及NAT为某一个本地端口所绑定的Internet端端口。这些信息被用来在两个同时处于NAT路由器之后的主机之间创建UDP通信。该协议由RFC 5389定义。

一旦客户端得知了Internet端的UDP端口,通信就可以开始了。如果NAT是完全圆锥型的,那么双方中的任何一方都可以发起通信。如果NAT是受限圆锥型或端口受限圆锥型,双方必须一起开始传输。

STUN使用下列的算法(取自RFC 3489)来发现NAT gateways类型以及防火墙(firewalls)
WebRTC之P2P_第1张图片

一旦路经通过红色箱子的终点时,UDP的沟通是没有可能性的。一旦通过黄色或是绿色的箱子,就有连线的可能。

  • 1.STUN客户端向STUN服务器发送请求,要求得到自身经NAT映射后的地址:

    • a. 收不到服务器回复,则认为UDP被防火墙阻断,不能通信,网络类型:Blocked.
    • b. 收到服务器回复,对比本地地址,如果相同,则认为无NAT设备,进入第2步,否则认为有NAT设备,进入3步.
  • 2.(已确认无NAT设备)STUN客户端向STUN服务器发送请求,要求服务器从其他IP和PORT向客户端回复包:

    • a. 收不到服务器从其他IP地址的回复,认为包被前置防火墙阻断,网络类型:Symmetric UDP Firewall.
    • b. 收到则认为客户端处在一个开放的网络上,网络类型:Opened.
  • 3.(已确认存在NAT设备)STUN客户端向STUN服务器发送请求,要求服务器从其他IP和PORT向客户端回复包:

    • a. 收不到服务器从其他IP地址的回复,认为包被前置NAT设备阻断,进入第4步.
    • b. 收到则认为NAT设备类型为Full Cone,即网络类型:Full Cone NAT.
  • 4.STUN客户端向STUN服务器的另外一个IP地址发送请求,要求得到自身经NAT映射后的地址,并对比之:

    • a. 地址不相同,则网络类型:Symmetric NAT.
    • b. 相同则认为是Restricted NAT,进入第5步,进一步确认类型.
  • 5.(已确认Restricted NAT设备)STUN客户端向STUN服务器发送请求,要求服务器从相同IP的其他PORT向客户端回复包:

    • a. 收不到服务器从其他PORT地址的回复,认为包被前置NAT设备阻断,网络类型:Port Restricted cone NAT.
    • b. 收到则认为网络类型: Restricted cone NAT.

有了上面的理论以后,我们来看看WebRTC的代码。WebRTC实现了STUN的功能,包括了客户端和服务端以及NAT探测,它使用的是RFC 5389协议。
WebRTC之P2P_第2张图片

服务端实现

WebRTC的STUN实现stunserver.cc很简单,收到stun客户端的请求,然后把客户端的最外层地址返回给用户。

// 判断是不是stun格式,如果是拍判断消息类型,目前仅仅支持STUN_BINDING_REQUEST消息
void StunServer::OnPacket(
    rtc::AsyncPacketSocket* socket, const char* buf, size_t size,
    const rtc::SocketAddress& remote_addr,
    const rtc::PacketTime& packet_time) {
  // Parse the STUN message; eat any messages that fail to parse.
  rtc::ByteBufferReader bbuf(buf, size);
  StunMessage msg;
  if (!msg.Read(&bbuf)) {
    return;
  }
  // Send the message to the appropriate handler function.
  switch (msg.type()) {
    case STUN_BINDING_REQUEST:
      OnBindingRequest(&msg, remote_addr);
      break;
    default:
      SendErrorResponse(msg, remote_addr, 600, "Operation Not Supported");
  }
}
// 构建一个stun的response消息,并写入stun客户的外网地址
void StunServer::OnBindingRequest(
    StunMessage* msg, const rtc::SocketAddress& remote_addr) {
  StunMessage response;
  GetStunBindReqponse(msg, remote_addr, &response);
  SendResponse(response, remote_addr);
}
// 返回response数据给客户端
void StunServer::SendResponse(
    const StunMessage& msg, const rtc::SocketAddress& addr) {
  rtc::ByteBufferWriter buf;
  msg.Write(&buf);
  rtc::PacketOptions options;
  if (socket_->SendTo(buf.Data(), buf.Length(), addr, options) < 0)
    LOG_ERR(LS_ERROR) << "sendto";
}

     
     
       
       
       
       
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
客户端实现

客户端的实现简单来说就是构建一个标准的STUN消息请求并发送给不同的STUN服务器(或许存在多个不同的STUN服务器,我还不能明白存在多个STUN服务器的意义),并收集STUN服务器返回的自身的外网IP,并放到candidates中。虽然代码看着不少,因为代码要考虑健壮性。
CreateStunPorts -> StunPort::Create -> new StunPort -> new UDPPort -> UDPPort::Init -> UDPPort::OnLocalAddressReady -> StunRequestManager::SendDelayed -> UDPPort::OnSendPacket -> UDPPort::OnReadPacket -> StunBindingRequest::OnResponse -> StunRequestManager::CheckResponse -> UDPPort::OnStunBindingRequestSucceeded

// 构建一个StunPort,StunPort是对见Port的简单封装
void AllocationSequence::CreateStunPorts() {
  ...
  StunPort* port = StunPort::Create(
      session_->network_thread(), session_->socket_factory(), network_,
      session_->allocator()->min_port(), session_->allocator()->max_port(),
      session_->username(), session_->password(), config_->StunServers(),
      session_->allocator()->origin());
   ...
}
// 初始化StunPort
static StunPort* Create(rtc::Thread* thread, rtc::PacketSocketFactory* factory,  rtc::Network* network, ...) {
    StunPort* port = new StunPort(thread, factory, network, min_port, max_port, username, password, servers, origin);
    if (!port->Init()) { delete port; port = NULL;  }
}
// StunPort是对UDPPort的子类
StunPort(rtc::Thread* thread, rtc::PacketSocketFactory* factory,  rtc::Network* network, uint16_t min_port,  uint16_t max_port,  ...)
      : UDPPort(thread, factory, network, min_port, max_port, username, password, origin, false) {
    // UDPPort will set these to local udp, updating these to STUN.
    set_type(STUN_PORT_TYPE);
    set_server_addresses(servers);
}
// 创建一个UDP Socket,这个Socket也是WebRTC的封装,不细说,当Socket的状态发生变化的时候会通过Socket的`Signal`信号槽回调出来
bool UDPPort::Init() {
     ...
    socket_ = socket_factory()->CreateUdpSocket(rtc::SocketAddress(Network()->GetBestIP(), 0), min_port(), max_port());
    socket_->SignalReadPacket.connect(this, &UDPPort::OnReadPacket);
    socket_->SignalSentPacket.connect(this, &UDPPort::OnSentPacket);
    socket_->SignalReadyToSend.connect(this, &UDPPort::OnReadyToSend);
    socket_->SignalAddressReady.connect(this, &UDPPort::OnLocalAddressReady);
    requests_.SignalSendPacket.connect(this, &UDPPort::OnSendPacket);
   return true;
}
// Socket地址可用回调此函数,在这里会先收集local candiate,然后调用MaybePrepareStunCandidate获取本机的外网地址
void UDPPort::OnLocalAddressReady(rtc::AsyncPacketSocket* socket, const rtc::SocketAddress& address) {
  rtc::SocketAddress addr = address;
  MaybeSetDefaultLocalAddress(&addr);
  AddAddress(addr, addr, rtc::SocketAddress(), UDP_PROTOCOL_NAME, "", "", LOCAL_PORT_TYPE, ICE_TYPE_PREFERENCE_HOST, 0, "", false);
  MaybePrepareStunCandidate();
}
// 判断是否需要向STUN服务器请求本机外网地址或者判断是否完成了获取外网地址的请求
void UDPPort::MaybePrepareStunCandidate() {
  if (!server_addresses_.empty()) {
    SendStunBindingRequests();
  } else {
    MaybeSetPortCompleteOrError();
  }
}
// 依次向不同的服务器请求外网地址
void UDPPort::SendStunBindingRequests() {
  for (ServerAddresses::const_iterator it = server_addresses_.begin(); it != server_addresses_.end(); ++it) {
    SendStunBindingRequest(*it);
  }
}
// 如果STUN服务器地址可用,那么向此服务器发送一个binding请求
void UDPPort::SendStunBindingRequest(const rtc::SocketAddress& stun_addr) {
  if (stun_addr.IsUnresolvedIP()) {
    ResolveStunAddress(stun_addr);
  } else if (socket_->GetState() == rtc::AsyncPacketSocket::STATE_BOUND) {
    if (IsCompatibleAddress(stun_addr)) {
      requests_.Send(new StunBindingRequest(this, stun_addr, rtc::TimeMillis()));
    } else {
      OnStunBindingOrResolveRequestFailed(stun_addr);
    }
  }
}
// 进一步完成StunRequest的设定,并把此数据发送给stun服务器
void StunRequestManager::SendDelayed(StunRequest* request, int delay) {
  request->set_manager(this);
  RTC_DCHECK(requests_.find(request->id()) == requests_.end());
  request->set_origin(origin_);
  request->Construct();
  requests_[request->id()] = request;
  if (delay > 0) {
    thread_->PostDelayed(RTC_FROM_HERE, delay, request, MSG_STUN_SEND, NULL);
  } else {
    thread_->Send(RTC_FROM_HERE, request, MSG_STUN_SEND, NULL);
  }
}
// 通过SignalSendPacket发送数据,紧接着判断已经发送的次数,以及超时情况
void StunRequest::OnMessage(rtc::Message* pmsg) {
  tstamp_ = rtc::TimeMillis();
  rtc::ByteBufferWriter buf;
  msg_->Write(&buf);
  manager_->SignalSendPacket(buf.Data(), buf.Length(), this);
  OnSent();
  manager_->thread_->PostDelayed(RTC_FROM_HERE, resend_delay(), this, MSG_STUN_SEND, NULL);
}
// 调用udp socket把数据发出去
void UDPPort::OnSendPacket(const void* data, size_t size, StunRequest* req) {
  StunBindingRequest* sreq = static_cast(req);
  rtc::PacketOptions options(DefaultDscpValue());
  if (socket_->SendTo(data, size, sreq->server_addr(), options) < 0)
    PLOG(LERROR, socket_->GetError()) << "sendto";
}
// 如果是stun服务器返回的消息,则调用CheckResponse检查是啥消息
void UDPPort::OnReadPacket(rtc::AsyncPacketSocket* socket, const char* data,  size_t size, const rtc::SocketAddress& remote_addr,  const rtc::PacketTime& packet_time) {
  if (server_addresses_.find(remote_addr) != server_addresses_.end()) {
    requests_.CheckResponse(data, size);
    return;
  }

if (Connection* conn = GetConnection(remote_addr)) {
conn->OnReadPacket(data, size, packet_time);
} else {
Port::OnReadPacket(data, size, remote_addr, PROTO_UDP);
}
}

virtual void StunBindingRequest::OnResponse(StunMessage* response) override {
const StunAddressAttribute* addr_attr = response->GetAddress(STUN_ATTR_MAPPED_ADDRESS);
rtc::SocketAddress addr(addr_attr->ipaddr(), addr_attr->port());
port_->OnStunBindingRequestSucceeded(server_addr_, addr);
if (WithinLifetime(rtc::TimeMillis())) {
port_->requests_.SendDelayed(new StunBindingRequest(port_, server_addr_, start_time_), port_->stun_keepalive_delay());
}
}

bool StunRequestManager::CheckResponse(StunMessage* msg) {

if (msg->type() == GetStunSuccessResponseType(request->type())) {
request->OnResponse(msg);
} else if (msg->type() == GetStunErrorResponseType(request->type())) {
request->OnErrorResponse(msg);
}
}

void UDPPort::OnStunBindingRequestSucceeded(const rtc::SocketAddress& stun_server_addr, const rtc::SocketAddress& stun_reflected_addr) {

std::ostringstream url;
AddAddress(stun_reflected_addr, socket_->GetLocalAddress(), related_address, UDP_PROTOCOL_NAME, “”, “”, STUN_PORT_TYPE, ICE_TYPE_PREFERENCE_SRFLX, 0, url.str(), false);
}
MaybeSetPortCompleteOrError();
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
NAT类型判断

通过刚刚的代码,我们已经拿到了Peer在NAT内外的ip地址了,那么此时我们就应该把这些candidates发送给对端。

//...

 
 
   
   
   
   
  • 1

TURN

以下TURN内容完全来自维基-TURN,阅读原文获取更全面的信息。

TURN(全名Traversal Using Relay NAT),是一种数据传输协议(data-transfer protocol)。允许在TCP或UDP的连在线跨越NAT或防火墙。
TURN是一个client-server协议。TURN的NAT穿透方法与STUN类似,都是通过获取应用层中的公有地址达到NAT穿透。但实现TURN client的终端必须在通信开始前与TURN server进行交互,并要求TURN server产生"relay port",也就是relayed-transport-address。这时TURN server会创建peer,即远程端点(remote endpoints),开始进行中继(relay)的动作,TURN client利用relay port将数据发送至peer,再由peer转传到另一方的TURN client。

名词 说明
realm 域名,例如boyaa.com
SOFTWARE 代理所使用的软件的文本描述,例如boyaa media, version 1.01
TurnServer(TURN服务端)

简单来说TURN服务要做的事情就是把Peer A的数据通过服务器转发给Peer B。这里我们要引入TURN协议RFC5766,协议存在的意义是为了标准化,要不只能自己玩了。协议规定了通信的格式以及他们交互流程。

  • TurnServer的工作流程如下:
    • TurnClient向TurnServer发送一个STUN_ALLOCATE_REQUEST请求,TurnServer会在服务端上生成一个对应的TurnServerAllocation,并返回TurnClient的外网地址和TurnServerAllocation的转发地址,TurnServerAllocation包含了一个UDP Socket,用于转发TurnClient的非STUN/TURN数据给Peer,也用于监听Peer发过来的数据
    • TurnClient向TurnServer发送一个TURN_CHANNEL_BIND_REQUEST请求,会在TurnServerAllocation上生成一个Channel,此Channel保护了TurnClient的channel_id和Peer的IP Address
    • TurnClient向TurnServer发送channel message时,TurnServerAllocation会找到对应的Peer地址,并通过TurnServerAllocation内部的UDP Socket转发给Peer
    • Peer向TurnServer发送数据,TurnServerAllocation会通过Peer地址找到对应的TurnClient,并通过TurnClient的Socket发送给TurnClient
  • TurnServer支持TCP/UDP协议的数据输入,但是内部都会通过UDP转发给Peer;同样的Peer只能通过UDP发送给TurnServer,然后TurnServer会通过TurnClient原本的协议转发给TurnClient
  • TurnClient如果要同时发送数据给Peer A和Peer B,TurnClient需要向TurnServerAllocation请求两次TURN_CHANNEL_BIND_REQUEST,用不同的channel_id分别对应Peer A和Peer B的IP AddreTurnClient需要向TurnServer发送两次数据,一份用channel_id_for_peer_a发送给Peer A,一份用channel_id_for_peer_b发送给Peer B,也就是说TurnServer真的只是负责转发而已
  • Channel和Indication的区别:发送Channel是TurnClient和Peer存在隐射关系,TurnClient可以和Peer可以相互发送数据;Indication是只有Peer发送数据给TurnClient。因为TurnClient发送的数据格式只有ChannelMessage一种。
  • TurnServer存在一个比较严重的问题:每一个TurnClient都需要一个在TurnServer对应一个Relay Transport(UDP Socket)
    WebRTC之P2P_第3张图片
int main(int argc, char **argv) {
  rtc::SocketAddress int_addr;
  if (!int_addr.FromString(argv[1])) {
    return 1;
  }

rtc::IPAddress ext_addr;
if (!IPFromString(argv[2], &ext_addr)) {
return 1;
}
rtc::Thread* main = rtc::Thread::Current();
rtc::AsyncUDPSocket* int_socket = rtc::AsyncUDPSocket::Create(main->socketserver(), int_addr);
if (!int_socket) {
return 1;
}

cricket::TurnServer server(main);
TurnFileAuth auth(argv[4]);
server.set_realm(argv[3]);
server.set_software(kSoftware);
server.set_auth_hook(&auth);
// 这是一个UDP Socket用于接收所有TurnClient发送过来的数据
server.AddInternalSocket(int_socket, cricket::PROTO_UDP);
// 这是一个UDP SocketFactory,每一个TurnServerAllocation都需要一个新的UDP Socket,用于转发数据给Peer和接收Peer发送过来的数据
server.SetExternalSocketFactory(new rtc::BasicPacketSocketFactory(), rtc::SocketAddress(ext_addr, 0));
main->Run();
return 0;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 通信第一步:TURN Client需要先向TURN Server请求分配一个Allocate
void TurnServer::HandleAllocateRequest(TurnServerConnection* conn, const TurnMessage* msg, const std::string& key) {
  TurnServerAllocation* alloc = CreateAllocation(conn, proto, key);
}

TurnServerAllocation* TurnServer::CreateAllocation(TurnServerConnection* conn, int proto, const std::string& key) {
rtc::AsyncPacketSocket* external_socket = (external_socket_factory_) ? external_socket_factory_->CreateUdpSocket(external_addr_, 0, 0) : NULL;
// The Allocation takes ownership of the socket.
TurnServerAllocation* allocation = new TurnServerAllocation(this, thread_, *conn, external_socket, key);
allocation->SignalDestroyed.connect(this, &TurnServer::OnAllocationDestroyed);
allocations_[*conn].reset(allocation);
return allocation;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 通信第二步:TurnClient向TurnServer发送一个TURN_CHANNEL_BIND_REQUEST请求
void TurnServerAllocation::HandleChannelBindRequest(const TurnMessage* msg) {
  // Check mandatory attributes.
  const StunUInt32Attribute* channel_attr = msg->GetUInt32(STUN_ATTR_CHANNEL_NUMBER);
  const StunAddressAttribute* peer_attr = msg->GetAddress(STUN_ATTR_XOR_PEER_ADDRESS);
  // Check that channel id is valid.
  int channel_id = channel_attr->value() >> 16;
  Channel* channel1 = new Channel(thread_, channel_id, peer_attr->GetAddress());
  channel1->SignalDestroyed.connect(this, &TurnServerAllocation::OnChannelDestroyed);
  channels_.push_back(channel1);

// Channel binds also refresh permissions.
AddPermission(peer_attr->GetAddress().ipaddr());
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 通信第三步:TurnClient和Peer进行数据传输
// TURN Client发送数据给Peer
void TurnServerAllocation::HandleChannelData(const char* data, size_t size) {
  // Extract the channel number from the data.
  uint16_t channel_id = rtc::GetBE16(data);
  Channel* channel = FindChannel(channel_id);
  SendExternal(data + TURN_CHANNEL_HEADER_SIZE, size - TURN_CHANNEL_HEADER_SIZE, channel->peer());
}
// Peer发送数据给TurnClient
void TurnServerAllocation::OnExternalPacket(rtc::AsyncPacketSocket* socket, const char* data, size_t size, const rtc::SocketAddress& addr, const rtc::PacketTime& packet_time) {
  RTC_DCHECK(external_socket_.get() == socket);
  Channel* channel = FindChannel(addr);
  if (channel) {
    // There is a channel bound to this address. Send as a channel message.
    rtc::ByteBufferWriter buf;
    buf.WriteUInt16(channel->id());
    buf.WriteUInt16(static_cast(size));
    buf.WriteBytes(data, size);
    server_->Send(&conn_, buf);
  } else if (!server_->enable_permission_checks_ || HasPermission(addr.ipaddr())) {
    // No channel, but a permission exists. Send as a data indication.
    TurnMessage msg;
    msg.SetType(TURN_DATA_INDICATION);
    msg.SetTransactionID(rtc::CreateRandomString(kStunTransactionIdLength));
    msg.AddAttribute(rtc::MakeUnique(STUN_ATTR_XOR_PEER_ADDRESS, addr));
    msg.AddAttribute(rtc::MakeUnique(STUN_ATTR_DATA, data, size));
    server_->SendStun(&conn_, &msg);
  } else {
    LOG_J(LS_WARNING, this) << "Received external packet without permission, " << "peer=" << addr;
  }
}

 
 
   
   
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
TurnPort(TURN客户端)

ICE

以下ICE内容完全来自维基-交互式连接创建,阅读原文获取更全面的信息。

交互式连接创建(Interactive Connectivity Establishment),一种综合性的NAT穿越的技术。
交互式连接创建是由IETF的MMUSIC工作组开发出来的一种framework,可集成各种NAT穿透技术,如STUN、TURN(Traversal Using Relay NAT,中继NAT实现的穿透)、RSIP(Realm Specific IP,特定域IP)等。该framework可以让SIP的客户端利用各种NAT穿透方式打穿远程的防火墙。

NAT类型

以下NAT内容完全来自维基-网络地址转换,阅读原文获取更全面的信息。

网络地址转换(英语:Network Address Translation,缩写:NAT;又称网络掩蔽、IP掩蔽)在计算机网络中是一种在IP数据包通过路由器或防火墙时重写来源IP地址或目的IP地址的技术。这种技术被普遍使用在有多台主机但只通过一个公有IP地址访问互联网的私有网络中。它是一个方便且得到了广泛应用的技术。当然,NAT也让主机之间的通信变得复杂,导致了通信效率的降低。

完全圆锥形NAT(Full cone NAT)

  • 一旦一个内部地址(iAddr:port)映射到外部地址(eAddr:port),所有发自iAddr:port的包都经由eAddr:port向外发送。任意外部主机都能通过给eAddr:port发包到达iAddr:port(注:port不需要一样)
    WebRTC之P2P_第4张图片

受限圆锥形NAT(Address-Restricted cone NAT)

  • 内部客户端必须首先发送数据包到对方(IP=X.X.X.X),然后才能接收来自X.X.X.X的数据包。在限制方面,唯一的要求是数据包是来自X.X.X.X。
  • 内部地址(iAddr:port1)映射到外部地址(eAddr:port2),所有发自iAddr:port1的包都经由eAddr:port2向外发送。外部主机(hostAddr:any)能通过给eAddr:port2发包到达iAddr:port1。(注:any指外部主机源端口不受限制,但是目的端口必须是port2。只有外部主机数据包的目的IP 为 内部客户端的所映射的外部ip,且目的端口为port2时数据包才被放行。
    WebRTC之P2P_第5张图片

端口受限圆锥形NAT(Port-Restricted cone NAT)

类似受限制锥形NAT(Restricted cone NAT),但是还有端口限制。

  • 一旦一个内部地址(iAddr:port1)映射到外部地址(eAddr:port2),所有发自iAddr:port1的包都经由eAddr:port2向外发送。
  • 在受限圆锥型NAT基础上增加了外部主机源端口必须是固定的。
    WebRTC之P2P_第6张图片

对称NAT(Symmetric NAT)

  • 每一个来自相同内部IP与端口,到一个特定目的地地址和端口的请求,都映射到一个独特的外部IP地址和端口。同一内部IP与端口发到不同的目的地和端口的信息包,都使用不同的映射
  • 只有曾经收到过内部主机数据的外部主机,才能够把数据包发回
    WebRTC之P2P_第7张图片
  • 点赞 1
  • 评论 1
  • 分享
    x

    海报分享

    WebRTC之P2P_第8张图片

    扫一扫,分享海报

  • 已收藏 4
  • 打赏

    打赏

    StoneLiu999

    你的鼓励将是我创作的最大动力

    C币 余额
    ¥2 ¥4 ¥6 ¥10 ¥20 ¥50

    您的余额不足,请先充值哦~去充值

  • 举报
  • 关注 关注
  • 一键三连

WebRTC -- P2PNAT穿越技术介绍
while(1) { smile(); }
11-15 8万+
一、 P2P(peer to peer)

P2P是一个“点对点传输技术”,也就是2台计算机间不需要第三台机器作为服务端就能实现数据的传输。每台计算机即是客户端又是服务端。如果每台计算机都有自己的独立的公网IP,那么P2P技术就比较好实现了,但是现实中由于NAT的存在,使得P2P技术最大的难点就在于穿越NAT的限制,俗称“打洞”。

二、 NAT(Network Address Transl…


  • <
  • 1
  • >
相关推荐
WebRtc建立 P2P链接的总体流程_liuweihui521的专栏
5-6
WebRtc建立 P2P链接的总体流程 前言 最近项目需要了解下 webrtc,网上 webrtc的资料层出不穷,大多都是过时的描述。个人在这方面也走了不少弯路,为了方便自己后续的工作,将自己阅读代码的所得进行了总结。鉴于个人水平有限,错误在所难免,...
WebRTC P2P建立过程_ice_ly000的博客
5-8
根据最初的定义, WebRTC被指定为 P2P(peer-to-peer)技术,旨在通过其浏览器(也称为 P2P)在客户端 间直接发送媒体流。在 P2P架构中,客户端建立通信 前,首先需要建立到应用服务器(有时也成为信令服务器)的信令连接。而 WebRTC规范中没有...
webRTC服务器端代码
03-24
这是一个搭建好的 P2P视频通信实例,采用 webrtc和socket.io实现,整体使用nodejs 火狐浏览器测试通过 博客链接:https://blog.csdn.net/weixin_40490238/article/details/88781323
webrtcChat:使用webrtcP2P视频聊天应用程序-源码
02-12
npm install npx budo index.js --ssl
流媒体协议 WebRTC实现 p2p视频通话(二)_AndroidAlvin...
5-17
webRTC中负责呼叫建立、监控(Supervision)、拆除(Teardown)的系统 为什么需要: webRTCp2p连接,那么连接 前如何获得对方信息,有如何将自己的信息发送给对方,这就需要信令服务。 2.SDP 什么是SDP ...
WebRtc建立P2P链接的总体流程
启程
02-08 1万+
初步介绍了 webrtc建立 p2p链接的大致流程!
webrtc 信令交换过程
清澈见底的专栏
06-29 2072
WebRTC是HTML5支持的重要特性 一,有了它,不再需要借助音视频相关的客户端,直接通过浏览器的Web页面就可以实现音视频对聊功能。而且 WebRTC项目是开源的,我们可以借助 WebRTC源码快速构建自己的音视频对聊功能。无论是使用前端JS的 WebRTC API接口,还是在 WebRTC源码上构建自己的对聊框架,都需要遵循以下执行流程: 上述序列中, WebRTC并不提供 Stun服务器和Signal...
webrtc研究及其P2P相关RFC协议文档集
01-17
webrtc研究及其 P2P相关RFC协议文档全集, 欢迎研究RTC的老铁们一起探讨 turnserver, stunserver,iceserver,vp8/9,h264,i420,g711/722,pcm16等!
webrtc-P2P网页音视频
01-13
webrtcP2P使用网页页面进行实时的音视频交流以及文件的传输。可以自己搭建网关进行跨服务器的传输与交流,也可以使用程序内带的网关,但有时会被墙,有些不稳定。同一网络下的传输没有问题,类似于QQ聊天,但无需依赖于软件而可以只用网页页面进入同一房间来执行。内部设计前端设计以及html语言的编写
serverless-webrtc-signaling-server:无服务器的WebRTC信令服务器仅适用于WebRTC P2P-源码
02-05
无服务器 WebRTC信号发送服务器 这是serverless- webrtc-signaling-server的代码和模板。 目录中包含五个功能,并且SAM teamplte将它们连接到DynamoDB表,并提供运行该应用程序所需的最小权限集。 什么是无服务器 WebRTC信令服务器? 无服务器 WebRTC Signaling Server是使用WebSocket并在AWS上运行的 WebRTC的Signaling Server。 该信令服务器仅适用于 WebRTC P2P。 该信令服务器实现了与兼容的会议室功能。 房间功能很简单,因此只有2人可以加入一个房间。 如果您想知道此服务器的工作方式,
使用webrtc实现P2P无摄像头播放远程视频
fengzhiqi1993的博客
10-14 240
一. webrtc解决了 P2P透传 二.解决web端无摄像头的问题 三.解决https的问题,可以使用http查看 流程 1.首先建立WebSocket连接 2. 通过ws发送 open msg 到服务器 3. 获取ws服务返回的 msg中获取摄像头是否在线 4. 如果在线 则发送call 信息到服务器 5. 接收到ws服务返回的 offer, offer中需要返回sdp信息 6. 使用iceServers创建PeerConnection, 设置PeerConnection的回调 PeerConnection
webrtcp2p.zip
07-23
使用 webrtc实现手机端点对点视屏语音通讯,有完整demo,需要修改socket的地址为本地ip
WebRTC安全问题:私有IP与mDNS
全栈空间
10-12 667
大概在去年《 WebRTCP2P技术,IPv6》一文中探讨了互联网 p2p技术的基本原理,从资源守恒和分形结构的角度揭示了 NAT技术的2个本质:物质守恒:利用端口号资源扩充稀缺的IPv4资...
p2p协议(webrtc编译)
daoer_sofu的专栏
10-30 185
p2p优势 多个客户端连接服务端,通过单一的服务端转发数据,服务端压力太大,所以使用 p2p,以服务端为媒介,两个客户端做直连,服务端的压力会减小很多。内网和外网隔离的限制, p2p在内网和外网使用的协议不同, p2p在外网和内网间会更为复杂。网上有很多 p2p的简单实现,大多是针对内网的 webrtc




WebRTC Native源码分析——P2P连接过程详解
paradox_1_0的博客
10-21 251
一年前我初步分析了 WebRTCP2P 连接过程,并总结为了安卓 P2P 连接过程和 DataChannel 使用一文,那会儿我刚接触 WebRTC C++ 的代码,看起来着实头大,而且安卓的代码要调试、测试也很麻烦,所以很多细节就没有展开,今天就让我们在 iOS 的工程里,对 P2P 连接的过程进行一个彻底的剖析。

概览

首先我们从宏观上了解一下 P2P 连接的过程,以及一些关键类间的关系,这样在看代码时就不至于迷失在细节里。此外,没看过安卓 P2P 连接过程和 DataChannel 使用的


实现WebRTC P2P连接
weixin_34097242的博客
09-26 1374
WebRTC是为了解决实时音视频传输问题,致力于提供免安装、免插件、免专利费,人人可用的高效便捷的实时流媒体传输。1. 3种实时流媒体实现比较目前实时流媒体主流有三种实现方式: WebRTC、HLS、RTMP,当你看直播网站的时候会发现很多采用了HLS(HTTP Live Streaming,http直播),它是一种把流媒体拆分成多个独立小文件的技术,按照播放时间请求不同文件,把hls的文件进行解复...
CSDN开发者助手,常用网站自动整合,多种工具一键调用
CSDN开发者助手由CSDN官方开发,集成一键呼出搜索、万能快捷工具、个性化新标签页和官方免广告四大功能。帮助您提升10倍开发效率!
WebRTC-Android P2P 连接过程
chinabinlang的专栏
02-03 5247
本文是 Piasy 原创,发表于 https://blog.piasy.com,请阅读原文支持原创 https://blog.piasy.com/2017/08/30/ WebRTC- P2P-part1/

前面四篇里,我们分别分析了 WebRTC Android 的视频采集、视频渲染和视频硬编码,最后把相关代码剥离出来形成了一个独立的模块:VideoCRE,并对其进行了极大地内存抖动优化。


WEBRTC 如何建立P2P连接
伍意的博客
12-28 5957
WEBRTC 如何建立 P2P连接

媒体通道建立和信令通道建立

搜集候选地址

 

调用顺序

PeerConnection::SetLocalDescription->WebRtcSession::SetLocalDescription->

WebRtcSession::CreateChannels[创建传输通道和媒体通道,以视频为例]


WebRTC P2P互动直播实现方案探讨
后台服务架构师
08-15 387
到目前为止,直播行业继续如预期的那样如火如荼的发展着,在先后竞争完延迟,高清,美颜,秒开等功能后,最近各大直播平台比拼的一个热点就是连麦。什么是连麦? 简单描述就是当主播直播期间,可以与其中某一个粉丝进行互动,并且其他粉丝能够观看到这个互动过程。 这个连麦的操作把主播和粉丝的交互从文字聊天一下升级为音视频互动,这个功能瞬间就提升了粉丝们的参与感;同时,这个互动过程其他粉丝都可以看到,极大满足了连麦粉丝的幸福感。

连麦的流程图如下:

·在主播直播过程中,主播提示进入互动环节,此时用户可以参与互动;
·用.


关于禁止直播网站利用WebRTC进行P2P数据分发
JackLee_CN博客
02-05 6618
随着直播平台的崛起.对于网络带宽要求也越来越高. WebRTC是一个比较的Html5的 P2P流量分发解决方案. 但是很多直播平台都利用这个方案把观众的上行流量进行了压榨. 对于这种压榨观众上行流量的方案.我们坚决说NO! 下面开始正文.如何关闭浏览器的 WebRTC功能. 凡是基于Chromium开发的浏览器方案同步.这里只会针对chrome和firefox进行说明! 本文章纯属技术交流,如涉及任何...
WebRTC实时通信系列教程8 打通P2P连接和信令通信
weixin_30830327的博客
08-07 90
【转载请注明出处:http://blog.csdn.net/leytton/article/details/76836265】

PS:如果本文对您有帮助,请点个赞让我知道哦~

WebRTC实时通信系列教程》翻译自《Real time communication with WebRTC

示例代码下载http://download.csdn.net/detai…


©️2020 CSDN 皮肤主题: 精致技术 设计师:CSDN官方博客 返回首页

你可能感兴趣的:(WebRTC,webrtc,p2p,turn,nat,stun)