前言: 窮人指沒錢或者不肯花太多錢(我既屬於前者也屬於後者TT),分佈式網絡主要指主機網絡環境分佈在不一樣的地理環境好比不一樣省或者不一樣國家(誰尚未一個比較便宜的國外vps不是\~\~)html
既然沒有辦法改變世界就改變本身吧.git
注意:我是指儘量的改善當前的網絡環境,你固然能夠說出一堆的極端環境,不過那真不是我想要解決的.github
第一種解決方案能夠解決主機之間的連通性,可是並無試圖改善網絡環境不佳的局面,即,基於當前網絡環境.再者各類***的安裝部署都太負載且裝一堆有的沒的.golang
其實我不太肯定是否是全部ipsec或者其餘***方式會不會經過一些機制解決網絡環境不佳的狀況,若有錯誤,還望指正.算法
第二種方案中的kcp介紹以下編程
KCP是一個快速可靠協議,能以比 TCP浪費10%-20%的帶寬的代價,換取平均延遲下降 30%-40%,且最大延遲下降三倍的傳輸效果。純算法實現,並不負責底層協議(如UDP)的收發,須要使用者本身定義下層數據包的發送方式,以 callback的方式提供給 KCP。 連時鐘都須要外部傳遞進來,內部不會有任何一次系統調用。json
而kcptun則是在kcp協議的基礎上,創建隧道,但只是映射某個端口,實在是讓人難過,一個一個的端口映射很難維護.網絡
其餘方案指代理或者其餘***socket
我暫時沒有在找到比較好的解決方案,因此我打算本身擼一個,可是當下只是寫了一個原型(可能沒時間寫),有興趣且碼代碼速度快的參考一下個人設計開發一下,開發好了能夠給我用用^_^tcp
golang
google出品必屬精品.
kcp
用於這裏想解決的問題是網絡環境不佳的問題,因此純udp或者tcp都是不太適合的.
原本個人願景要再大一些的,即不僅是在有公網IP的境況下打通網絡,且能穿透nat(指必定概率,有一些nat環境的確打不穿.)
因爲大部分時間是在研究怎麼經過kcp在綁定特定端口的狀況下既能充當client發起請求又能做爲server接受請求,可是kcp雖然是基於udp,可是把本身包裝成了類tcp,因此監聽的境況下就不能使用相似WriteToUDP的方法發送了(當下的確沒看到相似接口),因此若是使用kcp的方式穿透nat,那麼心跳的流程以及存活狀態的邏輯就變得比較複雜了,上面的設計方案主要指都擁有公網IP或者兩端至少一段擁有公網IP的狀況.
因此原型中並無設計kcp的代碼.
hubserver
// fast***_hub.go package main import ( "log" "net" "os" "syscall" "time" // reuse "github.com/kavu/go_reuseport" kcp "github.com/xtaci/kcp-go" ) func main() { var err error var conn *net.UDPConn if len(os.Args) < 2 { os.Exit(-1) } raddr := os.Args[1] localAddr, _ := net.ResolveUDPAddr("udp", ":8887") serverAddr, _ := net.ResolveUDPAddr("udp", raddr) conn, err = net.DialUDP("udp", laddr, raddr) conn.W if err != nil { log.Println("dial udp err") log.Println(err) } _, err = conn.Write([]byte("hello")) if err != nil { log.Println("udp write error") log.Println(err) } buf := make([]byte, 1024) n, _, err := conn.ReadFromUDP(buf) log.Println("udp read data: ", string(buf[:n])) conn.Close() heartbeat(localAddr, serverAddr) klisten, err := kcp.Listen(":8887") if err != nil { log.Println("listen error") log.Println(err) } log.Println("accept start") for { conn, err := klisten.Accept() if err != nil { log.Println("accpet error") log.Println(err) continue } go func() { for { buf := make([]byte, 1024) n, err := conn.Read(buf) if err != nil { log.Println("read error.") log.Println(err) } log.Println("recieve data: ", string(buf[:n])) _, err = conn.Write([]byte("hello")) if err != nil { log.Println("kcp write error") log.Println(err) } log.Println("remote addr: ", conn.RemoteAddr()) time.Sleep(3 * time.Second) } }() } }
peerclient.go
package main import ( "encoding/json" "flag" "fmt" "log" "net" "os" "os/exec" "strings" // "fast***/common" "github.com/songgao/water" "golang.org/x/net/ipv4" ) const ( BUFFERSIZE = 1500 MTU = "1300" ) var ( hubServer = flag.String("hub", "", "server addr like 192.168.11.100:8796") local = flag.String("local", "", "local ip like 172.16.97.101") listen = flag.String("listen", ":6222", "udp for bind") port = flag.String("port", "9999", "local port like 9999, default 9999, if you want to run multi clinet in same machine,change the port") peerMap = make(map[string]string) ) func checkFatalErr(err error, msg string) { if err != nil { log.Println(msg) log.Fatal(err) } } func runIP(args ...string) { cmd := exec.Command("/sbin/ip", args...) cmd.Stderr = os.Stderr cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout err := cmd.Run() if err != nil { log.Fatal("Error runing /sbin/ip:", err) } } func main() { flag.Parse() // parse args if *hubServer == "" { flag.Usage() log.Fatal("\nhub Server is not specified") } if *local == "" { flag.Usage() log.Fatal("\nlocal ip is not specified") } if *port == "" { *port = fmt.Sprintf(":%s", *port) } // 將地址字符串解析成*net.UDPAddr hubAddr, err := net.ResolveUDPAddr("udp", *hubServer) checkFatalErr(err, "Unable to resolve server UDP socket") listenAddr, err := net.ResolveUDPAddr("udp", *listen) checkFatalErr(err, "Unable to resolve local UDP socket") // 初始化建立的tun設備的配置文件 config := water.Config{ DeviceType: water.TUN, } // 建立一個tun設備 iface, err := water.New(config) checkFatalErr(err, "Unable to allocate TUN interface: ") log.Println("Interface allocated: ", iface.Name()) // 設置ip地址並啓動設備 runIP("link", "set", "dev", iface.Name(), "mtu", MTU) runIP("addr", "add", *local, "dev", iface.Name()) runIP("link", "set", "dev", iface.Name(), "up") // 監聽一個udp socket,經過listenUDP建立的socket,既能發送到指定的ip地址也能接受 conn, err := net.ListenUDP("udp", listenAddr) checkFatalErr(err, "Unable to connect server") defer conn.Close() privateIP := strings.Split(*local, "/") // 將本身的內網IP發送給hubserver conn.WriteToUDP([]byte(privateIP[0]), hubAddr) go func() { buf := make([]byte, BUFFERSIZE) // 不停的接受信息 for { n, addr, err := conn.ReadFromUDP(buf) if addr.String() == hubAddr.String() { log.Println("recieve data from server:") // 解析hubserver發送過來的peermap err = json.Unmarshal(buf[:n], &peerMap) if err != nil { log.Println("peermap unmarshal error") log.Println(err) } } else { log.Println("recive data from peer:") } if err != nil || n == 0 { fmt.Println("Error: ", err) continue } log.Println(string(buf[:n])) // 將對端發送過來的數據寫到本地的tun設備 iface.Write(buf[:n]) } }() packet := make([]byte, BUFFERSIZE) // 不停的讀取本地tun設備接受到的數據包 for { plen, err := iface.Read(packet) if err != nil { break } // 解析數據包頭部 header, _ := ipv4.ParseHeader(packet[:plen]) dstIP := header.Dst.String() // 若是數據發送的目標地址在peermap內則經過udp發送到對端 realDest, ok := peerMap[dstIP] if ok { realDestAddr, err := net.ResolveUDPAddr("udp", realDest) if err != nil { log.Println("resolve real dest ip error") log.Println(err) continue } fmt.Printf("Sending to remote: %+v (%=v)\n", header, err) conn.WriteTo(packet[:plen], realDestAddr) } else { continue } } }
依次啓動hubserver,peer1,peer2, 效果以下
試試網絡是不是通的.
這個原型沒有驗證,也沒有kcp,也沒有數據加密...僅是最基礎的功能被實現了,數據傳遞.
源代碼:https://github.com/youerning/blog/tree/master/fast***
主要靈感來自這篇文章:https://nsl.cz/using-tun-tap-in-go-or-how-to-write-***/
可是我作了一下修改,將數據包轉發到指定的對端.加了個hubserver.
參考鏈接:
https://github.com/skywind3000/kcp