go p2p udp 打洞 实现数据不经过服务器 所述,ipv4 p2p 打洞,ipv6只需做简单修改
ps:服务端和客户端需支持ipv6,测试前建议ping通
服务端
package main import ( "fmt" "log" "net" "time" ) func main() { listener, err := net.ListenUDP("udp6", &net.UDPAddr{IP: net.IPv6zero, Port: 9982}) if err != nil { fmt.Println(err) return } log.Printf("lcoal addr : <%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 hole,establish %s <--> %s Connection\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("transit server exit and peers can still communicate.") //清空切片等待下一次连接 peers = peers[0:0] return } } }
客户端:
package main import ( "fmt" "log" "math/rand" "net" "strconv" "strings" "time" ) var tag string const HAND_SHAKE_MSG = "我是打洞消息" func main() { // 当前进程标记字符串,便于显示 tag = strconv.Itoa(rand.Int()) srcAddr, _ := net.ResolveUDPAddr("udp", "") // srcAddr := &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 9983} // 注意端口必须固定 listener, err := net.ListenUDP("udp", srcAddr) fmt.Println("listen on:", listener.LocalAddr()) dstAddr := &net.UDPAddr{IP: net.ParseIP("【服务端IPV6地址】"), Port: 9982} // conn, err := net.DialUDP("udp", srcAddr, dstAddr) if err != nil { fmt.Println(err) } if _, err = listener.WriteToUDP([]byte("hello, I'm new peer:"+tag), dstAddr); err != nil { log.Panic(err) } data := make([]byte, 1024) n, remoteAddr, err := listener.ReadFromUDP(data) if err != nil { fmt.Printf("error during read: %s", err) } anotherPeer := parseAddr(string(data[:n])) fmt.Printf("local:%s server:%s another:%s\n", srcAddr, remoteAddr, anotherPeer.String()) // 开始打洞 bidirectionHole(srcAddr, &anotherPeer, listener) } func parseAddr(addr string) net.UDPAddr { t := strings.Split(addr, "[") t = strings.Split(t[1], "]:") fmt.Println("对端IPV6地址:", t[0]) fmt.Println("对端端口:", t[1]) port, _ := strconv.Atoi(t[1]) return net.UDPAddr{ IP: net.ParseIP(t[0]), Port: port, } } func bidirectionHole(srcAddr *net.UDPAddr, anotherAddr *net.UDPAddr, conn *net.UDPConn) { defer conn.Close() // 向另一个peer发送一条udp消息(对方peer的nat设备会丢弃该消息,非法来源),用意是在自身的nat设备打开一条可进入的通道,这样对方peer就可以发过来udp消息 for i := 0; i < 5; i++ { if _, err := conn.WriteToUDP([]byte(HAND_SHAKE_MSG), anotherAddr); err != nil { log.Println("send handshake:", err) } data := make([]byte, 1024) n, anotherAddr_t, err := conn.ReadFromUDP(data) if n > 0 { anotherAddr = anotherAddr_t fmt.Println("ip地址变更") break } else { fmt.Println("read from server err :", err) } time.Sleep(time.Second * 1) } go func() { for { time.Sleep(10 * time.Second) if _, err := conn.WriteToUDP([]byte("from ["+tag+"]"), anotherAddr); 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]) } } }