关于golang:100行代码实现一个高性能网络转发小工具

40次阅读

共计 4494 个字符,预计需要花费 12 分钟才能阅读完成。

应用场景就不说了,能够反对任意 TCP 网络数据转发

用 google 搜寻,有很多这样的代码片段,然而作为一个小工具,都不残缺,比方没有 参数解析 ,打印都是 print,而不是标准的 日志 ,有些 异样也没有解决

上面通过 104 行代码,实现一个麻雀虽小,然而五脏六腑都齐全的小工具

命令行解析

对应一个命令行工具,参数解析是第一步, 也可能是用户交你的程序交互的首选路径,不够其余参数如何,总的须要一个-v,打印下程序的版本吧

golang 的命令行有很多弱小的第三方库, 然而定位是小工具,编译的二进制越少约好,所有只用了官网的 flag 实现

var (version string)

func ParseArgs() (string, string) {listenAddr := flag.String("l", ":8080", "listen address")
    forwardAddr := flag.String("f", "","forwarding address")
    flagVersion := flag.Bool("v", false, "print version")

    flag.Parse()

    if *flagVersion {fmt.Println("version:", version)
        os.Exit(0)
    }

    if *forwardAddr == "" {flag.Usage()
        os.Exit(0)
    }

    return *listenAddr, *forwardAddr
}

version是一个全局变量,能够在编译的应用 flag 指定编译版本

这个函数实现了参数定义,参数校验,Usage 打印等,根本满足小工具的应用了

TCP Serve

func ListenAndServe(listenAddr string, forwardAddr string) {ln, err := net.Listen("tcp", listenAddr)
    if err != nil {log.Fatalf("listen addr %s failed: %s", listenAddr, err.Error())
    }

    log.Printf("accept %s to %s\n", listenAddr, forwardAddr)

    for {conn, err := ln.Accept()
        if err != nil {log.Printf("accept %s error: %s\n", listenAddr, err.Error())
        }

        go HandleRequest(conn, forwardAddr)
    }
}

对于承受网络申请,须要启动一个 TCP 服务,这里须要解决下端口抵触异样,启动日志等,最初通过一个死循环,对于每一个申请,启动一个 goroute 解决

golang 实现就是这么简略

申请解决

对于申请,HTTP 服务个别是对象 Request 做解决,返回一个 Response,这里实现也是相似

只是咱们是网络转发,所有先通过 net.Dialer 拨号,连贯到近程服务器再说

这里要留神下拨号超时,搜寻了很多代码片段,清一色的 net.Dial 间接应用,对应是个谬误地址也不晓得,所有留神加下 超时 谬误日志

func HandleRequest(conn net.Conn, forwardAddr string) {d := net.Dialer{Timeout: time.Second * 10}

    proxy, err := d.Dial("tcp", forwardAddr)
    if err != nil {log.Printf("try connect %s -> %s failed: %s\n", conn.RemoteAddr(), forwardAddr, err.Error())
        conn.Close()
        return
    }
    log.Printf("connected: %s -> %s\n", conn.RemoteAddr(), forwardAddr)

    Pipe(conn, proxy)
}

作为通明的数据转发,连贯后,就须要转发数据,这里独自用一个函数 Pipe 解决 io 申请

数据转发

func Pipe(src net.Conn, dest net.Conn) {
    var (
        readBytes  int64
        writeBytes int64
    )
    ts := time.Now()

    wg := sync.WaitGroup{}
    wg.Add(1)

    closeFun := func(err error) {dest.Close()
        src.Close()}

    go func() {defer wg.Done()
        n, err := io.Copy(dest, src)
        readBytes += n
        closeFun(err)
    }()

    n, err := io.Copy(src, dest)
    writeBytes += n
    closeFun(err)

    wg.Wait()
    log.Printf("connection %s -> %s closed: readBytes %d, writeBytes %d, duration %s", src.RemoteAddr(), dest.RemoteAddr(), readBytes, writeBytes, time.Now().Sub(ts))
}

命令行入口 – main

没啥少说点,固定的函数名main,固定的 package main, 调用参数解析,间接启动服务即可

func main() {listenAddr, forwardAddr := ParseArgs()
    ListenAndServe(listenAddr, forwardAddr)
}

完结

蕴含 import, 空号,一共 104 行

100 行代码实现一个高性能网络转发小工具

应用场景就不说了,能够反对任意 TCP 网络数据转发

用 google 搜寻,有很多这样的代码片段,然而作为一个小工具,都不残缺,比方没有 参数解析 ,打印都是 print,而不是标准的 日志 ,有些 异样也没有解决

上面通过 104 行代码,实现一个麻雀虽小,然而五脏六腑都齐全的小工具

命令行解析

对应一个命令行工具,参数解析是第一步, 也可能是用户交你的程序交互的首选路径,不够其余参数如何,总的须要一个-v,打印下程序的版本吧

golang 的命令行有很多弱小的第三方库, 然而定位是小工具,编译的二进制越少约好,所有只用了官网的 flag 实现

var (version string)

func ParseArgs() (string, string) {listenAddr := flag.String("l", ":8080", "listen address")
    forwardAddr := flag.String("f", "","forwarding address")
    flagVersion := flag.Bool("v", false, "print version")

    flag.Parse()

    if *flagVersion {fmt.Println("version:", version)
        os.Exit(0)
    }

    if *forwardAddr == "" {flag.Usage()
        os.Exit(0)
    }

    return *listenAddr, *forwardAddr
}

version是一个全局变量,能够在编译的应用 flag 指定编译版本

这个函数实现了参数定义,参数校验,Usage 打印等,根本满足小工具的应用了

TCP Serve

func ListenAndServe(listenAddr string, forwardAddr string) {ln, err := net.Listen("tcp", listenAddr)
    if err != nil {log.Fatalf("listen addr %s failed: %s", listenAddr, err.Error())
    }

    log.Printf("accept %s to %s\n", listenAddr, forwardAddr)

    for {conn, err := ln.Accept()
        if err != nil {log.Printf("accept %s error: %s\n", listenAddr, err.Error())
        }

        go HandleRequest(conn, forwardAddr)
    }
}

对于承受网络申请,须要启动一个 TCP 服务,这里须要解决下端口抵触异样,启动日志等,最初通过一个死循环,对于每一个申请,启动一个 goroute 解决

golang 实现就是这么简略

申请解决

对于申请,HTTP 服务个别是对象 Request 做解决,返回一个 Response,这里实现也是相似

只是咱们是网络转发,所有先通过 net.Dialer 拨号,连贯到近程服务器再说

这里要留神下拨号超时,搜寻了很多代码片段,清一色的 net.Dial 间接应用,对应是个谬误地址也不晓得,所有留神加下 超时 谬误日志

func HandleRequest(conn net.Conn, forwardAddr string) {d := net.Dialer{Timeout: time.Second * 10}

    proxy, err := d.Dial("tcp", forwardAddr)
    if err != nil {log.Printf("try connect %s -> %s failed: %s\n", conn.RemoteAddr(), forwardAddr, err.Error())
        conn.Close()
        return
    }
    log.Printf("connected: %s -> %s\n", conn.RemoteAddr(), forwardAddr)

    Pipe(conn, proxy)
}

作为通明的数据转发,连贯后,就须要转发数据,这里独自用一个函数 Pipe 解决 io 申请

数据转发

func Pipe(src net.Conn, dest net.Conn) {
    var (
        readBytes  int64
        writeBytes int64
    )
    ts := time.Now()

    wg := sync.WaitGroup{}
    wg.Add(1)

    closeFun := func(err error) {dest.Close()
        src.Close()}

    go func() {defer wg.Done()
        n, err := io.Copy(dest, src)
        readBytes += n
        closeFun(err)
    }()

    n, err := io.Copy(src, dest)
    writeBytes += n
    closeFun(err)

    wg.Wait()
    log.Printf("connection %s -> %s closed: readBytes %d, writeBytes %d, duration %s", src.RemoteAddr(), dest.RemoteAddr(), readBytes, writeBytes, time.Now().Sub(ts))
}

命令行入口 – main

没啥少说点,固定的函数名main,固定的 package main, 调用参数解析,间接启动服务即可

func main() {listenAddr, forwardAddr := ParseArgs()
    ListenAndServe(listenAddr, forwardAddr)
}

完结

蕴含 import, 空号,一共 104 行

-h 打印命令行规范, -v 打印版本,能够看到转发申请和 io 统计,工夫等

正文完
 0