【问题标题】:How to close a reading TCP connection from another goroutine?如何关闭来自另一个 goroutine 的读取 TCP 连接?
【发布时间】:2019-03-01 12:39:54
【问题描述】:

我的服务器场景是这样的:一个 io 线程一直在 tcp 连接上进行读取。一段时间后,控制线程可能会由于活动量低或其他原因决定关闭它。如果调用c.Close(),io线程会报如下错误:read tcp xxx->xxx: use of closed network connection

代码是这样的:

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!
    }
}

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

【问题讨论】:

    标签: go


    【解决方案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()
        }
    }
    

    }

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

    【讨论】:

      【解决方案2】:

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

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

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

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

      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
              }
          }
      }
      

      在您的情况下,您不想处理包含“使用关闭的网络连接”的错误。可以忽略它并记住关闭循环。否则一些内存会泄漏并挂起例程。可能是一个隐藏的错误,你忽略了一遍又一遍地抛出。

      【讨论】:

      • 由于网络连接在对等方关闭连接时返回 io.EOF,因此应用程序应在出现此错误时跳出读取循环。该问题使用 bufio.Scanner 读取连接。请注意,Scanner.Err() 在 EOF 错误时返回 nil,而不是 io.EOF。
      【解决方案3】:
      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 值中读取。

      【讨论】:

      • @ThunderCat 已修复
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-20
      • 2013-07-28
      • 1970-01-01
      • 2021-08-13
      • 2012-12-26
      相关资源
      最近更新 更多