1. 首页
  2. 开发
  3. C/C++

go p2p udp 打洞 实现数据不经过服务器

根据golang实现p2p之UDP打洞_IT 哈的博客-CSDN博客_golang p2p 的代码进行修改,对于四种NAT类型,Full Cone NAT、Restricted Cone NAT、Port Restricted Cone NAT、Symmetric NAT,支持在FRP两两之间P2P打洞,支持FR与S类型之间打洞,F指Full Cone NAT,其他类推

代码主要修改监听本地地址,对于P类型的有可能做了一次地址更改

server

package main

import (
  "fmt"
  "log"
  "net"
  "time"
)

func main() {
  listener, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4zero, 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
    }
  }
}

 

client

 

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, err := net.ResolveUDPAddr("udp", "")
  // srcAddr := &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 9983} // 注意端口必须固定
  listener, err := net.ListenUDP("udp", srcAddr)
  dstAddr := &net.UDPAddr{IP: net.ParseIP("服务器地址"), 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, ":")
  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])
    }
  }
}

 

原创文章,作者:admin,如若转载,请注明出处:https://www.huiyingwu.com/4804/

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注