窮人的分佈式網絡

前言: 窮人指沒錢或者不肯花太多錢(我既屬於前者也屬於後者TT),分佈式網絡主要指主機網絡環境分佈在不一樣的地理環境好比不一樣省或者不一樣國家(誰尚未一個比較便宜的國外vps不是\~\~)html

既然沒有辦法改變世界就改變本身吧.git

需求

  1. 將國內不一樣地區的雲主機以及國外vps網絡層打通,要求儘量少的延遲以及高吞吐.

注意:我是指儘量的改善當前的網絡環境,你固然能夠說出一堆的極端環境,不過那真不是我想要解決的.github

方案調研


  1. kcptun
  2. 其餘

第一種解決方案能夠解決主機之間的連通性,可是並無試圖改善網絡環境不佳的局面,即,基於當前網絡環境.再者各類***的安裝部署都太負載且裝一堆有的沒的.golang

其實我不太肯定是否是全部ipsec或者其餘***方式會不會經過一些機制解決網絡環境不佳的狀況,若有錯誤,還望指正.算法

第二種方案中的kcp介紹以下編程

KCP是一個快速可靠協議,能以比 TCP浪費10%-20%的帶寬的代價,換取平均延遲下降 30%-40%,且最大延遲下降三倍的傳輸效果。純算法實現,並不負責底層協議(如UDP)的收發,須要使用者本身定義下層數據包的發送方式,以 callback的方式提供給 KCP。 連時鐘都須要外部傳遞進來,內部不會有任何一次系統調用。json

而kcptun則是在kcp協議的基礎上,創建隧道,但只是映射某個端口,實在是讓人難過,一個一個的端口映射很難維護.網絡

其餘方案指代理或者其餘***socket

總結

我暫時沒有在找到比較好的解決方案,因此我打算本身擼一個,可是當下只是寫了一個原型(可能沒時間寫),有興趣且碼代碼速度快的參考一下個人設計開發一下,開發好了能夠給我用用^_^tcp

設計方案

編程語言

golang

google出品必屬精品.

傳輸協議

kcp

用於這裏想解決的問題是網絡環境不佳的問題,因此純udp或者tcp都是不太適合的.

功能組件

  • hubserver
  • peerclient

業務邏輯

hubserver

hub啓動流程
  1. 監聽端口
  2. 維護peer列表
心跳流程
  1. 驗證client
  2. 判斷ip及端口是否改變,是則更改peer列表,並推送給全部客戶端
  3. 若是client心跳超時,則該client從peer列表刪除
  4. 若是推送peer列表給某client失敗,則將該client移除

peerclient

peer啓動流程
  1. 向hub註冊本身
  2. 獲取peer列表
  3. 建立tun設備
  4. 創建讀寫管道
peer讀寫流程
  1. 解析數據包頭部信息,獲取目標地址
  2. 查詢目標地址是否在peer列表,是則發送,負責不響應
心跳流程
  1. 每秒向hub發送心跳,失敗後嘗試三次,依次爲1,3,7秒延遲,嘗試失敗則不在響應tun設備,日誌記錄失敗

原型

原本個人願景要再大一些的,即不僅是在有公網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

http://colobu.com/2014/12/02/go-socket-programming-UDP/

http://www.cnblogs.com/GO-NO-1/p/7241556.html

相關文章
相關標籤/搜索