Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

搭建P2P网络的原理 #166

Open
axetroy opened this issue Jan 20, 2018 · 15 comments
Open

搭建P2P网络的原理 #166

axetroy opened this issue Jan 20, 2018 · 15 comments
Labels

Comments

@axetroy
Copy link
Owner

axetroy commented Jan 20, 2018

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

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

技术要点

  • udp协议
  • 节点之间的建立,连接和广播
  • 内网穿透,如何能让两个处在内网的节点,相互发现自己的存在,并且建立通信

原理

首先解决的是内网穿透的问题,常见的底层协议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的聊天应用,但是精力有限,就这样了。代码(写的丑,轻拍)

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

@axetroy axetroy added the 学习 label Jan 20, 2018
@XadillaX
Copy link

亲,有兴趣来我厂吗?

@axetroy
Copy link
Owner Author

axetroy commented Jan 26, 2018

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

@XadillaX
Copy link

你是哪的哇

@axetroy
Copy link
Owner Author

axetroy commented Jan 27, 2018

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

@plh97
Copy link

plh97 commented Jan 28, 2018

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

@Zephylaci
Copy link

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

@axetroy
Copy link
Owner Author

axetroy commented Jul 1, 2018

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

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

@Zephylaci
Copy link

Zephylaci commented Jul 1, 2018

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

@axetroy
Copy link
Owner Author

axetroy commented Jul 2, 2018

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

@Zephylaci
Copy link

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

@axetroy
Copy link
Owner Author

axetroy commented Jul 2, 2018

@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
Copy link

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

@jianglin-wu
Copy link

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

@plh97
Copy link

plh97 commented Nov 21, 2018

流弊

@alsotang
Copy link

@XadillaX 哪个厂啊?我有兴趣

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants