duanji7182 2014-12-12 07:00
浏览 89

如何重用监听器/连接? Golang

I'm trying to connect a computer behind NAT with the internet through a 3rd party server(aka reverse connection). I'm listening on two ports. On one port (dstNet) is connecting the machine behind NAT and on the other port are connecting the internet clients. The issue is that I don't know how to handle the disconnection of the machine behind NAT. Even if the machine is connecting again the the traffic is not handled sent/written anymore... I get [DEBUG] socks: Copied 0 bytes to client which is my warning of course. Below is the code. It's quite long but I can't find what to trim.

// Make a bridge between dstNet which is
// usually behind NAT and srcNet which is usually a client
// which wants to route the traffic though the NAT machine.
package main

import (
    "bufio"
    "errors"
    log "github.com/golang/glog"
    "io"
    "net"
    "time"
)

const (
    // listen on the dstNet so that we can
    // create a connection with the NAT client
    dstNet = "0.0.0.0:9000"
    // listen on srcNet so that we can get traffic
    // to forward to dstNet
    srcNet = "0.0.0.0:9001"
)

var errCh = make(chan error, 1)

// make a channel to send the reverse connections
var lrCh = make(chan net.Conn, 1)
func listenDst() {
    // Listen on the dstNet
    lr, err := net.Listen("tcp", dstNet)
    if err != nil {
        log.Error(err)
        errCh <- err
        return
    }
    // accept the connection
    for {
        lrConn, err := lr.Accept()
        if err != nil {
            log.Error(err)
            errCh <- err
            return
        }
            log.Errorf("sent connection")
        //  lrConn.SetReadDeadline(time.Now().Add(10 * time.Second))
            lrCh <- lrConn

    }

}

func main() {

    go func() {
        for err := range errCh {
            if err != nil {
                panic(err)
            }
        }
    }()
    // listen for the nat server
    go listenDst()

    // listen for clients to connect
    l, err := net.Listen("tcp", srcNet)
    if err != nil {
        log.Error(err)
        panic(err)
    }
    // accept the connection
    for {
        conn, err := l.Accept()
        if err != nil {
            log.Error(err)
            panic(err)
        }
        // serve the connection
        go func(conn net.Conn) {
            defer conn.Close()
            bufConn := bufio.NewReader(conn)
            dst := <-lrCh
            defer dst.Close()

            // Start proxying
            errCh2 := make(chan error, 2)
            go proxy("target", dst, bufConn, errCh2)
            go proxy("client", conn, dst, errCh2)

            // Wait
            var ei int
            for err = range errCh2 {
                switch {
                case err != nil && err.Error() == "no byte":
                    log.Error(err)
                case err != nil && err.Error() == "use of closed network connection":
                    // if the connection is closed we restart it.
                    log.Error(err)
                    // BUG() attempt to write again the bytes
                case err != nil:
                    log.Error(err)
                    errCh <- err
                }
                if ei == 1 {
                    log.Errorf("done with errors")
                    close(errCh2)
                }
                ei++

            }
        }(conn)

    }
}

// proxy is used to suffle data from src to destination, and sends errors
// down a dedicated channel
func proxy(name string, dst io.Writer, src io.Reader, errCh2 chan error) {
    n, err := io.Copy(dst, src)
    // Log, and sleep. This is jank but allows the otherside
    // to finish a pending copy
    log.Errorf("[DEBUG] socks: Copied %d bytes to %s", n, name)
    time.Sleep(10 * time.Millisecond)
    // Send any errors
    switch {
    case err != nil:
        log.Error(err)
        errCh2 <- err
    case n < 1:
        errCh2 <- errors.New("no byte")
    default:
        errCh2 <- nil
    }
    return
}
  • 写回答

2条回答 默认 最新

  • douyan1896 2014-12-12 13:51
    关注

    The only time you can reuse a connection after an error is if is a temporary condition.

    if err, ok := err.(net.Error); ok && err.Temporary() {
    }
    

    If you are trying to proxy a TCPconnection, and there is any other error (checking for Temporary may not even be that useful), you need to drop the whole thing and start over. You have no idea what the state of the remote server is, how many packets are in flight or lost, and it's only going to cause more difficult bugs the harder you try. (tip: don't hide concurrency or timing problems with a sleep. It's just making it harder in the long run)

    Here is a much simpler proxy pattern for go if you want to reference it: https://gist.github.com/jbardin/821d08cb64c01c84b81a

    func Proxy(srvConn, cliConn *net.TCPConn) {
        // channels to wait on the close event for each connection
        serverClosed := make(chan struct{}, 1)
        clientClosed := make(chan struct{}, 1)
    
        go broker(srvConn, cliConn, clientClosed)
        go broker(cliConn, srvConn, serverClosed)
    
        // wait for one half of the proxy to exit, then trigger a shutdown of the
        // other half by calling CloseRead(). This will break the read loop in the
        // broker and allow us to fully close the connection cleanly without a
        // "use of closed network connection" error.
        var waitFor chan struct{}
        select {
        case <-clientClosed:
            // the client closed first and any more packets from the server aren't
            // useful, so we can optionally SetLinger(0) here to recycle the port
            // faster.
            srvConn.SetLinger(0)
            srvConn.CloseRead()
            waitFor = serverClosed
        case <-serverClosed:
            cliConn.CloseRead()
            waitFor = clientClosed
        }
    
        // Wait for the other connection to close.
        // This "waitFor" pattern isn't required, but gives us a way to track the
        // connection and ensure all copies terminate correctly; we can trigger
        // stats on entry and deferred exit of this function.
        <-waitFor
    }
    
    // This does the actual data transfer.
    // The broker only closes the Read side.
    func broker(dst, src net.Conn, srcClosed chan struct{}) {
        // We can handle errors in a finer-grained manner by inlining io.Copy (it's
        // simple, and we drop the ReaderFrom or WriterTo checks for
        // net.Conn->net.Conn transfers, which aren't needed). This would also let
        // us adjust buffersize.
        _, err := io.Copy(dst, src)
    
        if err != nil {
            log.Printf("Copy error: %s", err)
        }
        if err := src.Close(); err != nil {
            log.Printf("Close error: %s", err)
        }
        srcClosed <- struct{}{}
    }
    
    评论

报告相同问题?

悬赏问题

  • ¥15 基于卷积神经网络的声纹识别
  • ¥15 Python中的request,如何使用ssr节点,通过代理requests网页。本人在泰国,需要用大陆ip才能玩网页游戏,合法合规。
  • ¥100 为什么这个恒流源电路不能恒流?
  • ¥15 有偿求跨组件数据流路径图
  • ¥15 写一个方法checkPerson,入参实体类Person,出参布尔值
  • ¥15 我想咨询一下路面纹理三维点云数据处理的一些问题,上传的坐标文件里是怎么对无序点进行编号的,以及xy坐标在处理的时候是进行整体模型分片处理的吗
  • ¥15 CSAPPattacklab
  • ¥15 一直显示正在等待HID—ISP
  • ¥15 Python turtle 画图
  • ¥15 stm32开发clion时遇到的编译问题