欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

WebRTC之P2P

程序员文章站 2022-05-21 14:22:42
...

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

一旦路经通过红色箱子的终点时,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

服务端实现

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";
}

客户端实现

客户端的实现简单来说就是构建一个标准的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<StunBindingRequest*>(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();
}

NAT类型判断

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

//...

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
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;
}
  • 通信第一步: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;
}
  • 通信第二步: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());
}
  • 通信第三步: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<uint16_t>(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<StunXorAddressAttribute>(STUN_ATTR_XOR_PEER_ADDRESS, addr));
    msg.AddAttribute(rtc::MakeUnique<StunByteStringAttribute>(STUN_ATTR_DATA, data, size));
    server_->SendStun(&conn_, &msg);
  } else {
    LOG_J(LS_WARNING, this) << "Received external packet without permission, " << "peer=" << addr;
  }
}

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

受限圆锥形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

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

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

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

对称NAT(Symmetric NAT)

  • 每一个来自相同内部IP与端口,到一个特定目的地地址和端口的请求,都映射到一个独特的外部IP地址和端口。同一内部IP与端口发到不同的目的地和端口的信息包,都使用不同的映射
  • 只有曾经收到过内部主机数据的外部主机,才能够把数据包发回
    WebRTC之P2P