临渊阁

战战兢兢,如临深渊,如履薄冰

0%

webRTC

上个星期,同学让我帮他找资源(大小6G)并发给千里之外的另一个人,于是就有了下文

当今这么发达的互联网,上有星链,下有5g,大到太阳系,小到物联网,竟然在不同设备间传个文件都这么麻烦,非要用什么QQ,微信,甚至稍大一点的只能用“稳定”到几十kb的某度云。我只想单纯的发几个零和一从这台机器到另一台机器就这么难吗?

所以我想找找有什么技术能改变一下点对点传输的现状,可惜因为内网的存在,让不依靠服务器的点对点十分困难,其实QQ的传输也算的上点对点了(普通用户2G限制),但是我希望尽量少的依赖软件(尤其是会扫你浏览器记录的软件)

终于逛了一圈偌大的互联网后,我发现了一个叫webRTC的东西(Web Real-Time Communication:网页即时通信)大概是2011年左右诞生的,主要负责网页间音视频通信,所以可能在直播,视频聊天等方面比较火,这项技术现在主流浏览器基本都支持(但不同浏览器之间有些差距)。里面的DataChannel可以实现数据的传输

它可以简单的实现内网机器之间打洞传输,整个过程只有两次需要服务器,之后就是浏览器之间直接的P2P连接,所以这篇文章就是围绕应用研究一下webRTC用到的协议,然后看一下能否实现大文件点对点传输(用浏览器)

原理

手动连接

首先尝试在本地的两个浏览器上直接连接

过程:首先一方发送邀请给另一方,另一方收到邀请后根据邀请生成应答再发送回去,于是一个简单的连接就建立成功了

但是怎样才能飘洋过海,穿过NAT,连接的更远呢?

ICE服务器,在生成邀请和应答时把自己在互联网上的位置考虑进去

我写了一个非常简单两个网页,一个负责发送连接邀请,一个回应应答

offer.html

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
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>P2P</title>
<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
<script src="./offer.js"></script>
</head>
<body>
<p>
<button id="send">发送邀请</button>
<h2>你的邀请: 请将此邀请发给对方</h2>
<textarea id="my_icecandidate" rows="10" cols="50"></textarea>
<h2>输入对方应答:</h2>
<textarea id="other_icecandidate" rows="10" cols="50"></textarea><br>
<button id="start">开始聊天</button>
</p>
<p id='mess'></p>
<p>
输入: <input type="text" id="content">
<button id="input">发送</button>
</p>
</body>
</html>

offer.js

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
const lc = new RTCPeerConnection()
const dc = lc.createDataChannel("123123")
dc.onmessage = e => {
$("#mess").append($("<h3>").text('对方: '+e.data))
roll()
console.log("got it :" + e.data)
}
dc.onopen = e => {
$("#mess").append($("<h2>").text('对方接收成功, 开始聊天'))
console.log("connect!!!")
}

var restartConfig = {iceServers:[{urls:"stun:stun.gmx.net"}]}
lc.setConfiguration(restartConfig)

lc.onicecandidate = e => {
$("#my_icecandidate").text(JSON.stringify(lc.localDescription))
console.log("ice" + JSON.stringify(lc.localDescription))
}

$(document).ready(function(){
$("#send").click(function(){
lc.createOffer({"iceRestart": true}).then(o => lc.setLocalDescription(o)).then(a=>console.log("set sucess"))
})
$("#start").click(function(){
lc.setRemoteDescription(JSON.parse($("#other_icecandidate").val()))
})
$("#input").click(function(){
let data = $("#content").val()
$("#mess").append($("<h3>").text('我: '+data))
roll()
dc.send(data)
})
})

function roll(){
$("html, body").animate({
scrollTop: $('html, body').get(0).scrollHeight
}, 100);
}

answer.html

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
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>P2P</title>
<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
<script src="./answer.js"></script>
</head>
<body>
<p>
<h2>输入对方邀请:</h2>
<textarea id="other_icecandidate" rows="10" cols="50"></textarea><br>
<button id="receive">接受聊天邀请</button>
<h2>你的应答: 请将此应答发给对方</h2>
<textarea id="my_icecandidate" rows="10" cols="50"></textarea>
<h2>对方接受后就可以开始聊天了</h2>
</p>
<p id='mess'>

</p>
<p>
输入: <input type="text" name="content" id="content">
<button id="input">发送</button>
</p>
</body>
</html>

answer.js

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
const rc = new RTCPeerConnection()
var restartConfig = {iceServers:[{urls:"stun:stun.gmx.net"}]}
rc.setConfiguration(restartConfig)

rc.onicecandidate = e => {
$("#my_icecandidate").text(JSON.stringify(rc.localDescription))
console.log("ice" + JSON.stringify(rc.localDescription))
}
rc.ondatachannel = e => {
rc.dc = e.channel
rc.dc.onmessage = e => {
$("#mess").append($("<h3>").text('对方: '+e.data))
roll()
console.log("data:"+e.data)
}
rc.dc.onopen = e => {
console.log("connect!!")
}
}
console.log("over")

$(document).ready(function(){
$("#receive").click(function(){
console.log("...")
rc.setRemoteDescription(JSON.parse($("#other_icecandidate").val()))
rc.createAnswer({"iceRestart": true}).then(a => rc.setLocalDescription(a)).then(a=>{
console.log("ans create")
})
})
$("#input").click(function(){
let data = $("#content").val()
$("#mess").append($("<h3>").text('我: '+data))
roll()
rc.dc.send(data)
})
})

function roll(){
$("html, body").animate({
scrollTop: $('html, body').get(0).scrollHeight
}, 100);
}

屏幕截图 2022-01-24 180402

里面用到了一个ICE服务器stun.gmx.net

有了这个服务器,就可以穿过NAT了

加入中介,自动连接

简单来讲,就是PeerA用户问ICE服务器,“我是谁,我在哪?”,然后得到PeerA在网络上的位置

然后PeerA把连接邀请发给TURN服务器,让TURN等到PeerB连接上之后交给PeerB

PeerB在从ICE服务器得知自己位置之后,询问TURN有没有PeerA的邀请

然后PeerB根据邀请生成应答由TURN转交PeerA

SCTP

webRTC的DataChannel即没有用TCP也没有用UDP,而是使用了一个叫SCTP的协议,这个协议也是一个很老的协议,而且根据网上信息,效率和稳定性都比TCP好上不少(具体怎么样,我还不知道怎么去评估)但是到现在不知道为什么,也没人用

image-20220125162430653

SCTP很有意思的一点是,通过设置即可以变得像TCP也可以像UDP,这点看来挺灵活的

image-20220125161736927

1
2
3
ordered: false // 是否有序传输
maxPacketLifeTime: 3000 // 失败尝试重传时间
maxRetransmits: 10 // 失败尝试重传次数(和上面那个只能设置一个)

假如orderedfalsemaxRetransmits为0,SCTP就变成了UDP

并且RTCDataChannel还使用了DTLS,用SSL保证数据安全(具体怎么用就不知道了,https都还没搞懂)

下面是wireshark抓包,DTLS用来加密发送数据,STUN协议应该是保持连接吧,两个的传输层协议都是UDP

很奇怪的是,明明上面说的用的是SCTP协议,这里却根本找不到SCTP的影子,一种解释是SCTP根本无法通过路由器(大多不支持SCTP协议),所以被转换为UDP,(SCTP over UDP)

image-20220125164421619

传输大文件的可能

内存

首先是DataChannel的send方法,每次发送的大小上限是64KB(道听途说,还未验证),这个没有问题,只要把文件切片,多发几次就行。

但是,发送完之后内容存在了对方浏览器内存里,根本无法实现实时下载,也就是说传输文件大小的上限取决于双方内存大小,更准确一点说是双方的浏览器允许一个标签页占用的内存上限

可以用window.performance.memory.jsHeapSizeLimit检查chrome页面内存上限,我的是4G(我的内存是16G,如果是8G内存,应该是1G)

如果按照大多数人电脑都是8G内存的话,最高能传输的大文件只有1G,而且只能是电脑之间传输,毕竟手机或者平板的内存会更小

NAT

如果传输文件根本不用中介的话,穿越NAT似乎也不是很可靠

我尝试了临近几个宿舍wifi的连接,还有跨越半个中国的连接,得到的结论是大部分wifi都可以成功,远距离连接有些不稳定,有时候会失败

然后是4G网和wifi,4G和4G根本连不上

这个我不太懂4G网络的原理,所以也不知道是什么原因,以后再探究

速度

还未考究

结论

由于浏览器限制,传输大文件还是不行。。。。。

可能的解决方案:

  • 16G内存,最高可传输4G文件
  • 分成多个文件,最后下载cmd脚本等在本地合并
  • 下载端下载exe程序来接收文件(这不就又相当于用其他软件了吗)
  • (上述方案对跨平台及不合适)

有一个叫WebTorrent的项目,就是用webRTC来传输文件的,地址:

https://github.com/webtorrent/webtorrent

传输文件演示网站:

https://instant.io/