axetroy / blog

:open_book:基于Github API 的动态博客
https://axetroy.xyz
216 stars 36 forks source link

搭建P2P网络的原理 #166

Open axetroy opened 6 years ago

axetroy commented 6 years ago

最近在研究P2P技术,奈何相关资料不多,自己琢磨了一下,分享一下学习P2P的一些原理, 以及如何打造一个P2P聊天应用。

这里指的P2P是指peer to peer, 点对点的技术, 每个客户端都是服务端,没有中心服务器,不是websocket针对某个connection推送消息。

技术要点

原理

首先解决的是内网穿透的问题,常见的底层协议tcp,udp,他们各自有优缺点,简单说明一下。 tcp:需要处理粘包问题,双工流通道,是可靠的链接。 udp: 每次发送的都是数据包,没有粘包问题,但是连接不可靠,只能传输少量数据

更加详细的请Google

这里选择udp协议,简单一些。

再下来是内网穿透,先说结论: 两个处于不同内部网络的节点,永远无法发现他们之间的相互存在,你就算是想顺着网线过去打他都不行。

所有的内网穿透原理无外乎需要一个有公网ip的中介服务器,包括虚拟货币像比特币之类的,所以首先要有一个创世节点

在NodeJS中,创建udp服务也很简单

const dgram = require("dgram");
const udp = dgram.createSocket("udp4");
udp.bind(1090, callback)

把服务部署要公网,那么其他所有的节点都能访问,通过中转服务器,能够使得两个节点可以建立连接

default

我们是要建立这样的P2P网络

default

假如现在只有3个节点: 创世节点, B节点, C节点, 创世节点有公网IP

我用对话的形式,阐述他们建立链接的过程:

B节点: hey,创世节点,我要加入到P2P网络里面,告诉其他兄弟,我来了 创世节点: 兄弟们,刚刚有个叫做B的节点加入网络了,你们也去告诉其他节点 其他节点: 刚刚收到来自 "创世节点"的通知,有个fresh meet加入网络了,叫做 "B"

... 至此,所有人都知道了B节点加入了网络,里面记载着B节点的相关信息,包括IP地址,包括udp端口号

此时C节点也要加入网络,并且想要和B节点对话:

C节点: hey,创世节点,我要加入到P2P网络里面,并且我要和B对话 创世节点: 兄弟们,刚刚有个叫做B的节点加入网络了,你们也去告诉其他节点,顺便看看有没有B这个节点 其他节点: 刚刚收到来自 "创世节点"的通知,有个fresh meet加入网络了,叫做 "C",你们也看看有没有B这个节点 其他节点2: 收到通知,听说一个叫做C的节点在找一个B节点,我这里有它的信息,ip是xxxx.xxxx.xxx.xxxx, 端口10086 B节点: 有个C的家伙(ip: xxxx.xxxx.xxxx.xxxx, 端口1000)要找我

到这里,B获取到了C的信息,包括IP和端口,C也拿到了B的信息.

于是,他们两个就可以建立通信。消息流: B <----> C. 中间不经过任何服务器

用一张图来形容:

new

总结

在设计中,每个节点的功能都是一样的。如果需要加入到网络中,不一定跟创世节点链接

假设已存在的节点: 创世节点,A、B、C节点,此时有个D节点想要加入到网络。

那么D节点不一定非得链接到创世节点,可以链接到A、B、C中的任意一个节点,然后该节点再广播给其他节点说"Hey, 有个新人叫做D的加入了网络"。

这样所有人都知道,有个叫做D的节点存在,你可以和它通信,同时D节点和会同步已存在的节点。这样D节点也知道了其他节点的存在了。

最后

基于这一原理,可以打造出一个P2P的聊天应用,没有中间商赚差价。

这只是一些基本原理,离实际应用还差很多,有很多坑,比如D节点退出网络之后,要广播 “D节点退出网络了,把这个节点注销了吧,这波没他",还有消息加密,通信的双向验证(A节点想要B节点通信,但是不需要B节点的同意)等等,坑太多,填不完

原计划是搭建这么一个网络,然后写个electron的聊天应用,但是精力有限,就这样了。代码(写的丑,轻拍)

文字功底有点差,表述不清楚,见谅,如文中有误,欢迎指正与交流。

XadillaX commented 6 years ago

亲,有兴趣来我厂吗?

axetroy commented 6 years ago

@XadillaX 肯定有呀,不过杭州对我太远了

XadillaX commented 6 years ago

你是哪的哇

axetroy commented 6 years ago

@XadillaX 南宁,年后正要下海..哦不,下深圳:joy:

plh97 commented 6 years ago

大神,我又来了,我刚刚才从github 拿到token,现在准备利用token登录,然后和你一样把博客内容写在issue里面,但是又有点问题,我该如何过滤不要显示别人发起的issue,只显示我自己发的 。。issue,,,好吧,我先fork你的blog看看什么源码

Zephylaci commented 6 years ago

我有个疑问... C和B是怎么不通过中间服务器通信得... 只是通过ip和端口没法穿透到两个处于内网得服务器吧.. 举个例子,假设我是B位于天翼网关下面第二层路由下,创始节点能获取到我的公网ip也只是能得到天翼网关得地址,像网关请求显然我是不知道也不会有回应得呀?

axetroy commented 6 years ago

@Tarhyru 一样的,内网穿透的原理都是这样,两个处在内网的服务器 B 和 C,一定需要一个中间服务器才能建立链接

B 和 C 都处在二层路由,但是天翼网关的一层路由的某个端口是映射到二级路由的,所以中间服务器只需要获取一级路由的 IP 和端口就行了。

Zephylaci commented 6 years ago

@axetroy 那如果我想人为复现这个现象应该怎么作呢? 我发现,我在本地ping我得公网ip,有回应,直接连接80端口是天翼网关 但是,我在别的环境,比如阿里云上Ping这个公网ip没有回应...(但是 who 指令看到的ip和我pin的是同一个..)ping都Ping不通就更谈不上连接了... 所以,我只能内网通过SSH 反向代理到阿里云上,再在阿里云上通过正向代理将端口映射出去.. 其它环境正向连接阿里云映射的端口可以连接到我的本地... 然后,问题来了,这个连接会经过阿里云中转,也就是说连接速度受限于阿里云的带宽... 所以我想知道,有没有一种办法..像这个描述一样,通过中间节点建立连接,而不受限于中间节点得带宽?

axetroy commented 6 years ago

@Tarhyru 中间节点仅仅是帮助两个处在内网的节点相互发现对方的存在,之后就没他什么事了,所以,不受限中间节点的带宽

Zephylaci commented 6 years ago

@axetroy 好吧..这个道理我是明白了... 现在我想知道...怎么实现一个只帮助两个内网节点发现对方存在的中间节点...同时这两个相互发现的节点还能通信..... 昨天我telnet上天翼网关发现它就是一台linux服务器....连上发现外面还有一层....所以我从阿里云通过公网ip ping不到,但是telnet指令可以穿透..在网关上,Ping不通内网的机子..但是可以ping通公网的..内网的机子可以ping通网关... 现在我应该去查哪方面的内容..来实现我这个小需求?..我找内网穿透中间代理,一水的SSH.... 但是..通过SSH代理实现互相发现..明显是受限于代理的带宽... 我现在想的是..在网关上用iptables作转发....但是网关连接不到内层的路由或者机子..转发给谁..又是一个问题...

axetroy commented 6 years ago

@Tarhyru

https://github.com/axetroy/p2p-chat/blob/a7153e0115597026c65a6760da38e48edd72fb29/router.js#L135

这是 UDP 协议的实现,只要是 socket 链接,都可以获取到它的 remoteAddress 和 remotePort

A 机器:

remoteAddress: 123.123.123.123 remotePort: 61231

B 机器:

remoteAddress: 321.321.321.321 remotePort: 41231

过程

A --> 中间节点 (此时中间节点知道了 A 机器的 remoteAddress 和 remotePort, 并且给它起个名字叫做 A)

B --> 中间节点 (此时中间节点知道了 B 机器的 remoteAddress 和 remotePort, 此时 B 机器并不知道 A 机器的存在)

B (我要连接A机器)--> 中间节点

B <--(这是它的IP: 123.123.123.123, 端口: 61231, 你们自己玩去,别烦我) 中间节点

B --> A

Zephylaci commented 6 years ago

这么高级的..研究研究.. 总之,谢谢解答

jianglin-wu commented 5 years ago

直接用 WebRTC 应该会好搞一些,使用 STUN 来穿墙,不行就使用 TURN 中继。服务端有开源的 Kurent 和 Licode。

plh97 commented 5 years ago

流弊

alsotang commented 5 years ago

@XadillaX 哪个厂啊?我有兴趣