admin管理员组文章数量:1547489
WebRTC之P2P
StoneLiu999 2020-11-19 11:35:39 802 已收藏 4 分类专栏: WebRTC 文章标签: webrtc p2p turn nat stun 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接: https://blog.csdn/momo0853/article/details/109806172 版权文章目录
-
- 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)
一旦路经通过红色箱子的终点时,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的STUN实现stunserver很简单,收到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<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;
}
版权声明:本文标题:WebRTC之P2P 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://m.elefans.com/dianzi/1727192584a1101586.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论