【问题标题】:How to manage connections returned by Accept() in golang?如何在golang中管理Accept()返回的连接?
【发布时间】:2020-05-06 18:11:17
【问题描述】:

对于 tcp 服务器,我们经常使用 Listen() 和 Accept()。

func main() {
    ln, err := net.Listen("tcp", ":6000")
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }

    for {
        conn, err := ln.Accept()
        if err != nil {
            fmt.Println(err)
            continue
        }

        go handleConnection(conn)
    }
}

但是有两种方法可以处理 Accept 返回的连接:

1

func handleConnection(c net.Conn) {
    buf := make([]byte, 4096)

    for {
        n, err := c.Read(buf)
        if err != nil || n == 0 {
            c.Close()
            break
        }
        n, err = c.Write(buf[0:n])
        if err != nil {
            c.Close()
            break
        }
    }
    fmt.Printf("Connection from %v closed.\n", c.RemoteAddr())
}

2

func handleConnection(c net.Conn) {
    defer c.Close()
    buf := make([]byte, 4096)   

    n, err := c.Read(buf)
    if err != nil || n == 0 {
        c.Close()
        break
    }
    n, err = c.Write(buf[0:n])
    if err != nil {
        c.Close()
        break
    }
    fmt.Printf("Connection from %v closed.\n", c.RemoteAddr())
}

这两种方法是否正确?似乎方法 1 重用了连接。但是如何向客户端指示EOF?

【问题讨论】:

  • 使用io.Copy(c, c) 来解决这个问题。关闭连接以指示 EOF。 (1&2) 读取可以返回数据和错误。在处理错误之前写入数据。 (2) 该程序可能不会从连接中读取所有数据。
  • 旁注:在代码部分 2 中,您有一个defer c.Close(),因此从handleConnection 返回(或恐慌)将调用c.Close()。这意味着您不需要剩余的 c.Close() 调用。

标签: go tcp connection tcpserver


【解决方案1】:

此答案假定程序的目标是将数据回显给客户端。这个答案包括由 torek 和 limon 提供的 cmets 信息。

Read 的文档说

Read 最多将 len(p) 个字节读入 p。它返回读取的字节数 (0

第二个程序可能不会读取所有数据,因为 Read 可以返回可用数据而不是等待更多数据。第二个程序可能不会写入所有数据,因为 read 会返回数据和错误。

让我们专注于第一个程序,因为它更正确。该程序可以在一个连接上进行多次读取和写入调用,但这不是重用连接。

defer c.Close() 在函数的开头比在整个函数中调用c.Close() 更好。 defer 确保无论函数如何退出,连接都会关闭。

func handleConnection(c net.Conn) {
    defer c.Close()
    buf := make([]byte, 4096)

    for {
        n, err := c.Read(buf)
        if err != nil || n == 0 {
            break
        }
        n, err = c.Write(buf[0:n])
        if err != nil {
            break
        }
    }
    fmt.Printf("Connection from %v closed.\n", c.RemoteAddr())
}

读取可以返回一些数据和错误。在处理错误之前处理所有返回的数据:

func handleConnection(c net.Conn) {
    defer c.Close()
    buf := make([]byte, 4096)

    for {
        n, er := c.Read(buf)
        if n > 0 {
            _, ew := c.Write(buf[:n])
            if ew != nil {
                break
            }
        }
        if er != nil {
            break
        }
    }
    fmt.Printf("Connection from %v closed.\n", c.RemoteAddr())
}

使用io.Copy 而不是在应用程序中编写复制逻辑。 io.Copy 函数可以正确获取所有细节。此外,io.Copy 可以使用一些较低级别的优化来将数据从一个连接复制到另一个连接。

func handleConnection(c net.Conn) {
    defer c.Close()
    io.Copy(c, c)
    fmt.Printf("Connection from %v closed.\n", c.RemoteAddr())
}

【讨论】:

    【解决方案2】:

    这两种方法做不同的事情:

    第一种方法读取一些数据并将其写回,直到出现错误或读取 0 个字节。

    第二种方法读取一次,将其写回,然后关闭连接。

    第一种方法不会“重用”连接。它正在实现一个简单的类似回声的协议,该协议一直运行到连接终止。第二种方法回显一次并关闭连接。

    【讨论】:

      猜你喜欢
      • 2019-06-09
      • 1970-01-01
      • 2019-04-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多