Coturn集成了stun+turn协议,实现NAT检测,穿透就需要通过stun协议, NAT检测无法进行穿越时就需要通过turn服务进行流媒体的转发了,而coturn就是将两者协议进行整合并进行择优优化。
STUN 协议(Simple Traversal of UDP Through NATs) 即用 UDP简单的穿透 NAT 作为一个完整的 NAT 穿透解决 方案。
网络穿透,即 NAT 穿透, 能够让公网机器找到私网机器,并提高下载速度。穿透的本质是给一个 NAT 路由器的公网 IP 地址与端口发送报文数据,对应私网机器能够收到报文数据。
TURN,英文全称是Traversal Using Relays around NAT:Relay Extensions to Session Traversal Utilities for NAT,即使用中继穿透NAT:STUN的中继扩展。
coturn服务通常搭建在公网环境,拥有可访问的公网IP,穿透成功可以获取客户端的公网IP和端口。
局域网也可以搭建coturn服务,但只能获取局域网的IP和端口,意义不大。
本章使用阿里云实验室搭建coturn,每次可用2个小时,做个demo实验足够了:阿里云实验室
git clone https://github.com/coturn/coturn.git#克隆源码
cd coturn
#安装依赖
sudo apt install pkg-config#ERROR: pkg-config
sudo apt install libssl-dev#ERROR: OpenSSL Crypto development libraries are not installed properly in required location
sudo apt install libevent-dev#ERROR: Libevent2 development libraries are not installed properly in required location
#编译
./configure
make
sudo make install
#配置环境
turnadmin -a -u 用户名 -p 密码 -r 域名(给自己的域名)
turnadmin -a -u test -p 123456 -r chuwei
/usr/local/bin#turnserver安装目录
cd /usr/local/etc#配置文件所在目录
sudo cp turnserver.conf.default turnserver.conf
sudo vim turnserver.conf
#添加如下内容
listening-port=3478
external-ip=139.196.187.17
user=test:123456
realm=chuwei
turnserver -o -a -f -v -r chuwei#启动turnserver
netstat -ntpl#查看活跃端口,能看到启动的turnserver
lsof -i:3478#验证是否在监听
访问https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/
输入coturn服务器IP和端口,用户名和密码,点击Add Server,再点击Gather candidatesm,可以看到已经获取了udp公网IP和端口。
使用的火狐浏览器,谷歌浏览器报701错误。
大致包含的内容:
本机 IP 地址
本机用于WebRTC通信的端口号
候选者类型,包括 host、srflx 和 relay
优先级
传输协议
candidate事件type字段取值分别为host、srflx、relay:
host(Host candidate):从本地网卡上获取的地址
srflx(Server reflexive candidate):STUN 返回的该客户端的地址
relay(Relay reflexive candidate)::TURN 服务器为该客户端分配的中继地址
本地的candidate与远端candidate构成的每一对都有一定的优先级,按优先级排序进行连通性检查。最后从有效的candidate组合中选择优先级最高的作为传输地址,用于建立P2P连接。
qt实现coturn客户端,穿透成功返回公网IP和端口
#include "ccmainwindow.h"
#include "ui_ccmainwindow.h"
ccMainWindow::ccMainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::ccMainWindow)
{
ui->setupUi(this);
pUdp = new QUdpSocket(this);
session = (chwRtcSession*)calloc(sizeof(chwRtcSession),1);
session->context.avinfo = (chwAVInfo*)calloc(sizeof(chwAVInfo),1);
session->context.streamConfig = (chwStreamConfig*)calloc(sizeof(chwStreamConfig),1);
strcpy(session->context.avinfo->rtc.iceServerIP,"101.133.145.11");
session->context.avinfo->rtc.iceStunPort=3478;
strcpy(session->ice.server.serverIp , "101.133.145.11");//穿透服务器IP
session->ice.server.serverPort = 3478;//穿透服务器端口
session->context.avinfo->rtc.hasIceServer=1;
session->context.streamConfig->localPort = 9099;
int err = 0;
if(session->context.avinfo->rtc.hasIceServer&&session->ice.server.stunPort==0)
{
err=chw_ice_request(&session->ice.server,session->context.streamConfig->localPort);
if(err != 0)
printf("stun request fail!");
}
}
ccMainWindow::~ccMainWindow()
{
delete ui;
}
void ccMainWindow::on_pushButton_initudp_clicked()
{
pUdp->bind(QHostAddress::Any,ui->lineEdit_localport->text().toUInt());
connect(pUdp, SIGNAL(readyRead()),this, SLOT(readPendingDatagrams()));
}
void ccMainWindow::on_pushButton_initudp_send_clicked()
{
pUdp->writeDatagram(ui->lineEdit_sendMsg->text().toLatin1().data(),ui->lineEdit_sendMsg->text().size(),
QHostAddress(ui->lineEdit_remoteIP->text()),ui->lineEdit_remotePort->text().toUInt());
pUdp->waitForBytesWritten();
}
void ccMainWindow::readPendingDatagrams()
{
QByteArray datagram;
while (pUdp->hasPendingDatagrams()) {
datagram.resize(pUdp->pendingDatagramSize());
QHostAddress sender;
quint16 senderPort;
pUdp->readDatagram(datagram.data(), datagram.size(),&sender, &senderPort);
ui->textBrowser->append(datagram.data());
}
}
打印输出
remoteIp=101.133.145.11,port=3478
udp server is starting,localPort=9099
stun ip=180.111.104.1,stunport=7343