标签:连接 没有 exit rect arp 原理 cond fun 设备
就像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:9527 | | +----------------------|----------------------+ | | NAT A NAT B 122.27.219.161:10001 123.29.210.131:10002 | | | | Client A Client B 192.168.1.126:9901 192.168.1.102:9902
如上图所示,CLientA与ClientB想要通信,因为两个客户端都在各自的局域网内,都是通过NAT技术生成公网映射IP的,想要彼此访问必须通过一个中间服务器进行中介介绍,这样两个客户端才能彼此认识并建立连接,否则双方直接通信都会被路由器丢弃。那么为什么非要用NAT呢?直接为每个Client分配一个公网IP不可以吗?这是由于IPv4的限制,公网IP数量是有限的,我们国家拿到的公网IP段更是有限,甚至不及美国一所大学的IP段数量多。这样也就不可能为每个机器都分配一个公网IP,正因为此NAT技术才非常重要,它可以很好的帮我们解决公网IP不足的问题。
接下来我们还是介绍一下NAT的原理和类型:
NAT主要可以分为两类:
针对NAPT端口的映射方式,又可以分为四种形式:
本人经过检测发现,本机的NAT类型为上述第四种:Symmetric NAT
知识点普及后,我们继续来实践我们之前所说的p2p技术,也就是两个设备之间的通信问题,由于两个设备分别在各自的网络内部,我们也称这种行为为打洞!
接下来我们用go语言来实现这个打洞技术,主要使用UDP来实现,具体流程如下:
代码如下:
//server.go "log" "net" "time" ) func main() { listener, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 9527}) if err != nil { fmt.Println(err) return } log.Printf("本地地址: <%s> \n", listener.LocalAddr().String()) peers := make([]net.UDPAddr, 0, 2) data := make([]byte, 1024) 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) == 2 { log.Printf("进行UDP打洞,建立 %s <--> %s 的连接\n", peers[0].String(), peers[1].String()) listener.WriteToUDP([]byte(peers[1].String()), &peers[0]) listener.WriteToUDP([]byte(peers[0].String()), &peers[1]) time.Sleep(time.Second * 8) 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) < 2 { fmt.Println("请输入一个客户端标志") os.Exit(0) } // 当前进程标记字符串,便于显示 tag = os.Args[1] srcAddr := &net.UDPAddr{IP: net.IPv4zero, Port: 9901} // 注意端口必须固定 dstAddr := &net.UDPAddr{IP: net.ParseIP("192.168.1.102"), Port: 9527} 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, 1024) 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[1]) return net.UDPAddr{ IP: net.ParseIP(t[0]), 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(10 * time.Second) if _, err = conn.Write([]byte("from [" + tag + "]")); err != nil { log.Println("send msg fail", err) } } }() for { data := make([]byte, 1024) 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映射,不过其他童鞋可以用代码在公网服务器进行验证!
标签:连接 没有 exit rect arp 原理 cond fun 设备
原文地址:https://www.cnblogs.com/xiaoxuebiye/p/11348798.html