如何关闭另一个goroutine的读取TCP连接?

时间:2019-02-28 05:03:32

标签: go

我的服务器senario是这样的:一个io线程一直在进行tcp连接上的读取。一段时间后,控制线程可能由于活动不足或其他原因决定关闭它。如果调用c.Close(),则io线程将报告如下错误:读取tcp xxx-> xxx:使用封闭的网络连接

代码如下:

func recv(c net.Conn) {
    input := bufio.NewScanner(c)
    for input.Scan() {
        msg <- input.Text()
        ...
    }
}

//main
conn, err := s.Accept()
...
go recv(conn)
for {
    select {
    case m := <-msg:
         ...
    case <-timeout:
        conn.Close() // oops!
    }
}

也许我可以忽略该错误,但是我想知道是否有更好的方法。

3 个答案:

答案 0 :(得分:1)

选项用于close连接或set the read deadline到过去的某个时间。无论哪种方式,连接上的read都将返回错误。

处理这些错误的最简单方法是统一处理从网络连接读取中返回的所有错误:关闭连接,清理与该连接关联的资源,然后继续。可以两次关闭连接。

func recv(c net.Conn, msg chan string) {
    defer close(msg) // Notify main goroutine that recv is done.
    defer c.Close()  // Release resources.

    input := bufio.NewScanner(c)

    // Loop reading lines until read on c returns an error.
    // These errors include io.EOF (normal close by the peer),
    // the error caused by the main goroutine closing the connection
    // and other networking errors.
    for input.Scan() {
        msg <- input.Text()
    }
}

// main

conn, err := s.Accept()
if err != nil {
    // handle error
}

msg := make(chan string)
go recv(conn, msg)

for {
    select {
    case m, ok := <-msg:
        if !ok {
            // The recv goroutine closed the channel and the connection.
            return
        }
        fmt.Println(m)
    case <-timeout:
        // Closing the connection causes the recv goroutine to break
        // out of the loop. If the recv goroutine received a line of 
        // text and has yet sent the text to the msg channel, then 
        // a return from main at this point will cause the recv goroutine
        // to block forever. To avoid this, continue looping until until
        // the recv goroutine signals that it's done by closing the msg
        // channel.
        conn.Close()
    }
}

}

应用程序可以记录它正在关闭连接并在此之后以特殊方式处理读取错误,但是只有在这种情况下应用程序需要做一些特殊的事情时才这样做。

答案 1 :(得分:0)

有些错误建议单独处理,例如:

EOF:长接收到的消息已读完,一切正常,因此继续。

“现有连接已被远程主机强行关闭”:客户端关闭了应用。正常且通话结束,因此返回。

否则:一些登录c或服务器错误,需要记录并修复

handler(c net.Conn){
    defer c.Close()
    for {
       ...
       if e!=nil {
            if e == io.EOF{
                continue
            }
            if strings.Contains(e.Error(), "An existing connection was forcibly closed by the remote host.") {
                return
            }
            log.Println(e.Error())
            return
        }
    }
}

在您的情况下,您不想处理包含``使用封闭的网络连接''的错误,可以忽略它并记住要关闭循环,否则某些内存将泄漏并且例程被挂起。是一个隐藏的错误,您可以一遍又一遍地忽略。

答案 2 :(得分:0)

func recv(c *net.Conn) {
    if c == nil {
       return
    }
    input := bufio.NewScanner(*c)
    for input.Scan() {
        msg <- input.Text()
        ...
    }
}

//main
conn, err := s.Accept()
c := &conn
...
go recv(&c)
for {
    select {
    case m := <-msg:
         ...
    case <-timeout:
        conn.Close()
        c = nil
    }
}

不确定这是否是最佳方法。关闭conn时,可以将其设置为nil而不是从nil conn值读取。