应用场景就不说了,能够反对任意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统计,工夫等