p2p 打洞专场(转)
就像1000个人眼中有1000个哈姆雷特一样,每个人眼中的区块链也是不一样的!作为技术人员眼中的区块链就是将各种技术的融合,包括密码学,p2p网络,分布式共识机制以及博弈论等。我们今天就来讨论一下区块链技术中的p2p网络,这是一种点到点的通信技术。
说到p2p通信,它并没有名字看上去那样简单,在网络世界里实现p2p还是需要一些手段的!很多朋友可能会说,实现一个c/s模式的点到点通信很简单呀,但是前提是彼此可以看见,比如服务器在公网,或者服务器和客户端都在同一个局域网内,我们要探讨的p2p通信是指通信的双方分别在两个局域网内部!
由于在两个局域网内部,两台设备并没有公网IP,彼此要通信需要借助路由器,但是路由器又会对不识别的ip进行过滤,也就是路由器有个陌生人排除机制!怎么办呢?类似于我们去一个安保较为严格的场所时,需要内部的工作人员接引才可入内,在网络编程中也是这样的原理!但和现实中不同的是,假设设备A想和另一个局域网的设备B通信,设备B是并不认识设备A的,设备A通过路由器NAT(Network Address Translation,网络地址转换)技术获得了一个公网映射IP,但是设备B并不认识,那么怎么样能让两者通信呢?所以这个时候需要一个介绍人,此时需要有一个公网的服务器作为媒介,介绍两个人介绍,当B设备对应路由器添加了A设备对应的公网IP后,A设备就可以与B设备建立连接了,这个时候就可以顺畅的通信了!
- Server S
- 10.47.58.139:
- |
- |
- +----------------------|----------------------+
- | |
- NAT A NAT B
- 122.27.219.161: 123.29.210.131:
- | |
- | |
- Client A Client B
- 192.168.1.126: 192.168.1.102:
如上图所示,CLientA与ClientB想要通信,因为两个客户端都在各自的局域网内,都是通过NAT技术生成公网映射IP的,想要彼此访问必须通过一个中间服务器进行中介介绍,这样两个客户端才能彼此认识并建立连接,否则双方直接通信都会被路由器丢弃。那么为什么非要用NAT呢?直接为每个Client分配一个公网IP不可以吗?这是由于IPv4的限制,公网IP数量是有限的,我们国家拿到的公网IP段更是有限,甚至不及美国一所大学的IP段数量多。这样也就不可能为每个机器都分配一个公网IP,正因为此NAT技术才非常重要,它可以很好的帮我们解决公网IP不足的问题。
接下来我们还是介绍一下NAT的原理和类型:
NAT主要可以分为两类:
- 基本NAT,这种要求NAT有多个公网IP,这样可以将公网IP和内网设备静态绑定
- NAPT(Network Address Port Translation),更为常见的NAT,内网设备的网络请求通过不同端口加以映射
针对NAPT端口的映射方式,又可以分为四种形式:
- 完全圆锥型NAT( Full Cone NAT ),将从一个内部IP地址和端口来的所有请求,都映射到相同的外部IP地址和端口。并且,任何外部主机通过向映射的外部地址发送报文,都可以实现和内部主机进行通信。
- 地址限制圆锥型NAT( Address Restricted Cone NAT ),将从相同的内部IP地址和端口来的所有请求映射到相同的公网IP地址和端口。但是与完全圆锥型NAT不同,当且仅当内部主机之前已经向公网主机发送过报文,此时公网主机才能向内网主机发送报文。
- 端口限制圆锥型NAT( Port Restricted Cone NAT ),端口受限圆锥型NAT增加了端口号的限制,当前仅当内网主机之前已经向公网主机发送了报文,公网主机才能和此内网主机通信。
- 对称型NAT( Symmetric NAT),把从同一内网地址和端口到相同目的地址和端口的所有请求,都映射到同一个公网地址和端口。如果同一个内网主机,用相同的内网地址和端口向另外一个目的地址发送报文,则会用不同的映射。
本人经过检测发现,本机的NAT类型为上述第四种:Symmetric NAT
知识点普及后,我们继续来实践我们之前所说的p2p技术,也就是两个设备之间的通信问题,由于两个设备分别在各自的网络内部,我们也称这种行为为打洞!
接下来我们用go语言来实现这个打洞技术,主要使用UDP来实现,具体流程如下:
- 建立UDP服务器 S
- 建立A、B客户端,分别与S建立会话,SA与SB
- S当好中介人,将A的ip+端口通过SB告诉B,将B的ip+端口通过SA告诉A
- A向B公网地址发送一个UDP包,代表握手,打通A-B的路径
- B向A公网地址发送一个UDP包,A-B的会话建立成功
代码如下:
- //server.go
- "log"
- "net"
- "time"
- )
- func main() {
- listener, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: })
- if err != nil {
- fmt.Println(err)
- return
- }
- log.Printf("本地地址: <%s> \n", listener.LocalAddr().String())
- peers := make([]net.UDPAddr, , )
- data := make([]byte, )
- for {
- n, remoteAddr, err := listener.ReadFromUDP(data)
- if err != nil {
- fmt.Printf("error during read: %s", err)
- }
- log.Printf("<%s> %s\n", remoteAddr.String(), data[:n])
- peers = append(peers, *remoteAddr)
- if len(peers) == {
- log.Printf("进行UDP打洞,建立 %s <--> %s 的连接\n", peers[].String(), peers[].String())
- listener.WriteToUDP([]byte(peers[].String()), &peers[])
- listener.WriteToUDP([]byte(peers[].String()), &peers[])
- time.Sleep(time.Second * )
- log.Println("中转服务器退出,仍不影响peers间通信")
- return
- }
- }
- }
服务端显示如下:
- ykdeMac-mini:study yekai$ ./server
- 2019/04/03 14:50:13 本地地址: <[::]:9527>
- 2019/04/03 14:51:48 <192.168.1.102:9901> hello, I'm new peer:yekai1
- 2019/04/03 14:52:57 <192.168.1.126:9902> hello, I'm new peer:yekai2
- 2019/04/03 14:52:57 进行UDP打洞,建立 192.168.1.102:9901 <--> 192.168.1.126:9902 的连接
- 2019/04/03 14:53:05 中转服务器退出,仍不影响peers间通信
- //client.go
- package main
- import (
- "fmt"
- "log"
- "net"
- "os"
- "strconv"
- "strings"
- "time"
- )
- var tag string
- const HAND_SHAKE_MSG = "我是打洞消息"
- func main() {
- if len(os.Args) < {
- fmt.Println("请输入一个客户端标志")
- os.Exit()
- }
- // 当前进程标记字符串,便于显示
- tag = os.Args[]
- srcAddr := &net.UDPAddr{IP: net.IPv4zero, Port: } // 注意端口必须固定
- dstAddr := &net.UDPAddr{IP: net.ParseIP("192.168.1.102"), Port: }
- conn, err := net.DialUDP("udp", srcAddr, dstAddr)
- if err != nil {
- fmt.Println(err)
- }
- if _, err = conn.Write([]byte("hello, I'm new peer:" + tag)); err != nil {
- log.Panic(err)
- }
- data := make([]byte, )
- n, remoteAddr, err := conn.ReadFromUDP(data)
- if err != nil {
- fmt.Printf("error during read: %s", err)
- }
- conn.Close()
- anotherPeer := parseAddr(string(data[:n]))
- fmt.Printf("local:%s server:%s another:%s\n", srcAddr, remoteAddr, anotherPeer.String())
- // 开始打洞
- bidirectionHole(srcAddr, &anotherPeer)
- }
- func parseAddr(addr string) net.UDPAddr {
- t := strings.Split(addr, ":")
- port, _ := strconv.Atoi(t[])
- return net.UDPAddr{
- IP: net.ParseIP(t[]),
- Port: port,
- }
- }
- func bidirectionHole(srcAddr *net.UDPAddr, anotherAddr *net.UDPAddr) {
- conn, err := net.DialUDP("udp", srcAddr, anotherAddr)
- if err != nil {
- fmt.Println(err)
- }
- defer conn.Close()
- // 向另一个peer发送一条udp消息(对方peer的nat设备会丢弃该消息,非法来源),用意是在自身的nat设备打开一条可进入的通道,这样对方peer就可以发过来udp消息
- if _, err = conn.Write([]byte(HAND_SHAKE_MSG)); err != nil {
- log.Println("send handshake:", err)
- }
- go func() {
- for {
- time.Sleep( * time.Second)
- if _, err = conn.Write([]byte("from [" + tag + "]")); err != nil {
- log.Println("send msg fail", err)
- }
- }
- }()
- for {
- data := make([]byte, )
- n, _, err := conn.ReadFromUDP(data)
- if err != nil {
- log.Printf("error during read: %s\n", err)
- } else {
- log.Printf("收到数据:%s\n", data[:n])
- }
- }
- }
客户端1显示如下:
- ykdeMac-mini:study yekai$ ./client yekai1
- local:0.0.0.0:9901 server:192.168.1.102:9527 another:192.168.1.126:9902
- 2019/04/03 14:52:57 收到数据:我是打洞消息
- 2019/04/03 14:52:57 error during read: read udp 192.168.1.102:9901->192.168.1.126:9902: recvfrom: connection refused
- 2019/04/03 14:53:07 收到数据:from [yekai2]
- 2019/04/03 14:53:17 收到数据:from [yekai2]
客户端2显示如下:
- localhost:zhuhai yk$ ./client yekai2
- local:0.0.0.0:9902 server:192.168.1.102:9527 another:192.168.1.102:9901
- 2019/04/03 14:53:07 收到数据:from [yekai1]
- 2019/04/03 14:53:17 收到数据:from [yekai1]
备注:本文中的公网服务器使用的是192.168.1.102进行替代,测试时并没有实际走NAT映射,不过其他童鞋可以用代码在公网服务器进行验证!
p2p 打洞专场(转)的更多相关文章
- UDP 构建p2p打洞过程的实现原理(持续更新)
UDP 构建p2p打洞过程的实现原理(持续更新) 发表于7个月前(2015-01-19 10:55) 阅读(433) | 评论(0) 8人收藏此文章, 我要收藏 赞0 8月22日珠海 OSC 源创 ...
- p2p 打洞技术
根据通信双方所处网络环境不同,点对点通信可以划分成以下三类:i> 公网:公网ii>公网:内网iii>内网:内网前两种容易实现,我们这里主要讨论第三种.这其中会涉及到NAT和NAPT的 ...
- NAT详解:基本原理、穿越技术(P2P打洞)、端口老化等
这是一篇介绍NAT技术要点的精华文章,来自华3通信官方资料库,文中对NAT技术原理的介绍很全面也很权威,对网络应用的应用层开发人员而言有很高的参考价值. 学习交流 移动端即时通讯学习交流: 21589 ...
- 【Todo】UDP P2P打洞原理
参考以下两篇文章: https://my.oschina.net/ososchina/blog/369206 http://m.blog.csdn.net/article/details?id=666 ...
- [转]UDP/TCP穿越NAT的P2P通信方法研究(UDP/TCP打洞 Hole Punching)
[转]UDP/TCP穿越NAT的P2P通信方法研究(UDP/TCP打洞 Hole Punching) http://www.360doc.com/content/12/0428/17/6187784 ...
- P2P技术详解(二):P2P中的NAT穿越(打洞)方案详解
1.内容概述 P2P即点对点通信,或称为对等联网,与传统的服务器客户端模式(如下图"P2P结构模型"所示)有着明显的区别,在即时通讯方案中应用广泛(比如IM应用中的实时音视频通信. ...
- P2P技术基础: 关于TCP打洞技术
4 关于TCP打洞技术 建立穿越NAT设备的p2p的 TCP 连接只比UDP复杂一点点,TCP协议的“打洞”从协议层来看是与UDP的“打洞”过程非常相似的.尽管如此,基于TCP协议的打洞至今为止还没有 ...
- P2P中的NAT穿越(打洞)方案详解
一.P2P(点对点技术) 点对点技术(peer-to-peer,简称P2P)又称对等互联网络技术,是一种网络新技术,依赖网络中参与者的计算能力和带宽,而不是把依赖都聚集在较少的几台服务器上. 点对点技 ...
- TCP打洞和UDP打洞的区别 (转)
为什么网上讲到的P2P打洞基本上都是基于UDP协议的打洞?难道TCP不可能打洞?还是TCP打洞难于实现? 假设现在有内网客户端A和内网客户端B,有公网服务端S. 如果A和B想要进行UD ...
随机推荐
- Socket编程(C语言实现):bind()函数英文翻译
本篇翻译的bind()函数,我参考的国外网站是: bind 朋友们可以自由转载我对英文的中文翻译,但是对于"作者注:"的描述,转载时请注明出处和作者,否则视为侵权. 下面是翻译的正 ...
- 20190101.DDD笔记
建立领域模型步骤 根据提供的信息完善主要业务场景和业务流程: 根据业务流程识别领域事件并按照时序排列: 针对领域事件进行命令识别: 针对领域事件和命令进行聚合和子域的初步识别: 在识别的subdoma ...
- 整型,布尔值,字符串详解,for语句 练习
2019 年 7 月 8 日 1.将今天的课上的代码敲一遍,然后整理笔记 已完成 2.有变量name = "aleX leNb" 完成如下操作: 移除 name 变量对应的值两边的 ...
- python 3.5学习笔记(第五章)
本章内容 1.什么是模块 2.模块的导入方法 3.搜索路径 4.重要标准库 一.什么是模块 1.模块本质上是一个以.py 结尾的python文件,包含了python对象定义和python语句. 2.模 ...
- 简单题[期望DP]
也许更好的阅读体验 \(\mathcal{Description}\) 桌面上有R张红牌和B张黑牌,随机打乱顺序后放在桌面上,开始一张一张地翻牌,翻到红牌得到1美元,黑牌则付出1美元.可以随时停止翻牌 ...
- 从0系统学Android-2.3使用 Intent 在 Activity 之间穿梭
2.3 使用 Intent 在 Activity 之间穿梭 在上一节中我们已经学会了如何创建一个 Activity 了.对于一个应用程序来说,肯定不可能只有一个 Activity.下面就来学习多个 A ...
- C语言入门2-程序设计的灵魂—算法及Raptor的应用
一. 什么是算法(5个特性) 算法就是 解决问题的方法和步骤. 算法为解决一个具体问题而采取的确定的 有限的 执行步骤 ,仅指 计算机 能执行的算法. 算法是程序设计的灵魂和核心 ...
- C#3.0新增功能10 表达式树 03 支持表达式树的框架类型
连载目录 [已更新最新开发文章,点击查看详细] 存在可与表达式树配合使用的 .NET Core framework 中的类的大型列表. 可以在 System.Linq.Expressions 查 ...
- 2-1. 基于OpenSSL的传输子系统实现
一. 基本传输子系统程序设计 客户端可上传文件至服务器,或下载服务器上的文件 系统程序构架: 客户端 服务器 TCP建立连接 menu()-> 上传命令.下载命令 close(socket) T ...
- kaptcha谷歌验证码工具
Kaptcha 简介 Kaptcha 是一个可高度配置的实用验证码生成工具,可自由配置的选项如: 验证码的字体 验证码字体的大小 验证码字体的字体颜色 验证码内容的范围(数字,字母,中文汉字!) 验证 ...