在Write()方法返回之前,golang套接字不会发送所有字节

时间:2018-11-11 12:34:40

标签: sockets asynchronous go

今天,当我尝试向服务器(也是用Golang编写的非常简单的TCP服务器)发送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()。提前致谢。请原谅我的英语不好。

2 个答案:

答案 0 :(得分:2)

您是否在尝试写入之前先对套接字进行轮询?

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

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

答案 1 :(得分:1)

您没有处理conn返回的错误。请正确阅读。来自the docs(重点是我):

  

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

     

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

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

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