【问题标题】:golang socket doesn't send all bytes before Write() method returnsgolang 套接字在 Write() 方法返回之前不会发送所有字节
【发布时间】:2018-11-11 12:34:39
【问题描述】:

今天当我尝试向我的服务器(一个非常简单的 TCP 服务器,也是用 Golang 编写的)发送 100M 数据时,我发现 TCPConn.Write 方法返回 104857600 和 nil 错误,然后我关闭了套接字。但是我的服务器只接收到很少的数据。我想是因为Write方法工作在异步模式下,所以虽然方法返回104857600,但是只有很少的数据发送到服务器。所以我想知道是否有办法将Write工作设置为同步模式,或者如何检测是否所有数据都从套接字发送到服务器。

代码如下: 服务器:

const ListenAddress = "192.168.0.128:8888"
func main() {

    var l net.Listener
    var err error
    l, err = net.Listen("tcp", ListenAddress)
    if err != nil {
        fmt.Println("Error listening:", err)
        os.Exit(1)
    }
    defer l.Close()
    fmt.Println("listen on " + ListenAddress)
    for {
        conn, err := l.Accept()
        if err != nil {
            fmt.Println("Error accepting: ", err)
            os.Exit(1)
        }
        //logs an incoming message
        fmt.Printf("Received message %s -> %s \n", conn.RemoteAddr(), conn.LocalAddr())
        // Handle connections in a new goroutine.
        go handleRequest(conn)
    }
}
func handleRequest(conn net.Conn) {
    defer conn.Close()
    rcvLen := 0
    rcvData := make([]byte,20 * 1024 * 1024) // 20M
    for {
        l , err := conn.Read(rcvData)
        if err != nil {
            fmt.Printf("%v", err)
            return
        }
        rcvLen += l
        fmt.Printf("recv: %d\r\n", rcvLen)

        conn.Write(rcvData[:l])
    }
}

客户:

conn, err := net.Dial("tcp", "192.168.0.128:8888")
if err != nil {
    fmt.Println(err)
    os.Exit(-1)
}
defer conn.Close()
data := make([]byte, 500 * 1024 * 1024)
len, err := conn.Write(data)
fmt.Println("send len: ", len)

客户端的输出:

send len:  524288000

服务器的输出:

listen on 192.168.0.128:8888
Received message 192.168.0.2:50561 -> 192.168.0.128:8888 
recv: 166440
recv: 265720
EOF

我知道如果我可以通过 SetLinger 方法让客户端等待一段时间,数据将在套接字关闭之前全部发送到服务器。但我想找到一种方法让套接字在返回之前发送所有数据而不调用 SetLinger()。提前致谢。请原谅我的英语不好。

【问题讨论】:

  • 写入不是异步的。是什么让您认为您的“服务器只接收很少的数据”?不管你认为你的代码多么简单,它仍然可能是不正确的。请务必将其包含在问题中。
  • "检测是否所有数据都从套接字发送到服务器。" - 您是否真的想知道是否所有数据都已发送(并且可能仍在传输中并且可能会在那里丢失)或者您是否想知道是否所有数据都已收到并且可能还由服务器处理?我的猜测是最后一种情况,在这种情况下您需要服务器发回某种确认。
  • @Peter 我添加了我的代码。请看一看。我仍然对这个问题感到困惑。谢谢。
  • @SteffenUllrich 我只想确保在 Write() 返回之前发送所有数据。

标签: sockets asynchronous go


【解决方案1】:

在尝试写入之前您是否轮询过套接字?

套接字后面是操作系统的 tcp 堆栈。在套接字上写入时,您将字节推送到发送缓冲区。然后,您的操作系统会自行决定何时以及如何发送。如果接收端的接收缓冲区中没有可用的缓冲区空间,则发送端知道这一点,不会在发送缓冲区中放入更多信息。

确保您的发送缓冲区有足够的空间容纳您接下来尝试发送的任何内容。这是通过轮询套接字来完成的。此方法通常称为 Socket.Poll。我建议您查看 golang 文档以了解确切用法。

【讨论】:

    【解决方案2】:

    您没有正确处理 conn.Read 返回的错误。来自the docs(强调我的):

    当 Read 在成功读取 n > 0 个字节后遇到错误或文件结束条件时,它返回读取的字节数。它可能会从同一次调用中返回(非零)错误,或者从后续调用中返回错误(并且 n == 0)。 [...]

    调用者应始终处理返回的 n > 0 个字节,然后再考虑错误错误。这样做可以正确处理读取某些字节后发生的 I/O 错误以及两种允许的 EOF 行为。

    请注意,您正在重新发明io.Copy(尽管缓冲区大小过大)。您的服务器代码可以重写为:

    func handleRequest(conn net.Conn) {
        defer conn.Close()
        n, err := io.Copy(conn, conn)
    }
    

    【讨论】:

    • 谢谢。经过几天的学习,我意识到 Golang 使 TCPConn.Write 异步工作,我应该做的是给它时间在内部将所有数据发送到服务器。至于保证所有数据发送成功,上层协议可以保证。
    猜你喜欢
    • 1970-01-01
    • 2020-04-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-22
    • 2016-10-31
    • 1970-01-01
    相关资源
    最近更新 更多