net.TCPConn允许FIN数据包后写入

net.TCPConn允许FIN数据包后写入,tcp,go,Tcp,Go,我正试图为一些服务器端代码编写单元测试,但在确定关机测试用例时遇到了问题。环回TCP连接似乎没有正确处理干净的关机。我在一个示例应用程序中对此进行了重新编程,该应用程序在lockstep中执行以下操作: 创建客户端和服务器连接 通过将消息从客户端成功发送到服务器来验证连接 使用通道通知服务器调用conn.Close(),并等待该调用完成 (尝试)通过再次调用客户端连接上的Write来验证连接是否完全断开 步骤4成功无误。我尝试使用json.Encoder和对TCPConn.Write的简单调用。

我正试图为一些服务器端代码编写单元测试,但在确定关机测试用例时遇到了问题。环回TCP连接似乎没有正确处理干净的关机。我在一个示例应用程序中对此进行了重新编程,该应用程序在lockstep中执行以下操作:

  • 创建客户端和服务器连接
  • 通过将消息从客户端成功发送到服务器来验证连接
  • 使用通道通知服务器调用conn.Close(),并等待该调用完成
  • (尝试)通过再次调用客户端连接上的Write来验证连接是否完全断开
  • 步骤4成功无误。我尝试使用json.Encoder和对TCPConn.Write的简单调用。我和WireShark检查了交通状况。服务器发送了一个FIN数据包,但客户端从未发送(即使是1s睡眠),服务器甚至发送了一个RST数据包来响应(4),客户端conn.Write仍然返回nil作为其错误

    这简直是疯了。我是不是遗漏了什么?目前正在运行Go v1.2.1/Darwin

    编辑:强制复制

    package main
    
    import (
      "bufio"
      "fmt"
      "net"
    )
    
    var (
      loopback = make(chan string)
      shouldClose = make(chan struct{})
      didClose = make(chan struct{})
    )
    
    func serve(listener *net.TCPListener) {
      conn, err := listener.Accept()
      if err != nil {
        panic(err)
      }
    
      s := bufio.NewScanner(conn)
      if !s.Scan() {
        panic(fmt.Sprint("Failed to scan for line: ", s.Err()))
      }
    
      loopback <- s.Text() + "\n"
    
      <-shouldClose
      conn.Close()
      close(didClose)
    
      if s.Scan() {
        panic("Expected error reading from a socket closed on this side")
      }
    }
    
    func main() {
      listener, err := net.ListenTCP("tcp", &net.TCPAddr{})
      if err != nil {
        panic(err)
      }
      go serve(listener)
    
      conn, err := net.Dial("tcp", listener.Addr().String())
      if err != nil {
        panic(fmt.Sprint("Dialer got error ", err))
      }
    
      oracle := "Mic check\n"
      if _, err = conn.Write([]byte(oracle)); err != nil {
        panic(fmt.Sprint("Dialer failed to write oracle: ", err))
      }
    
      test := <-loopback
      if test != oracle {
        panic("Server did not receive the value sent by the client")
      }
    
      close(shouldClose)
      <-didClose
    
      // For giggles, I can also add a <-time.After(500 * time.Millisecond)
      if _, err = conn.Write([]byte("This should fail after active disconnect")); err == nil {
        panic("Sender 'successfully' wrote to a closed socket")
      }
    }
    
    主程序包
    进口(
    “布菲奥”
    “fmt”
    “净额”
    )
    变量(
    环回=生成(chan字符串)
    shouldClose=make(chan结构{})
    didClose=make(chan结构{})
    )
    函数服务(侦听器*net.TCPListener){
    conn,err:=listener.Accept()
    如果错误!=零{
    恐慌(错误)
    }
    s:=bufio.NewScanner(康涅狄格州)
    如果!s.Scan()是{
    恐慌(fmt.Sprint(“未能扫描行:,s.Err()))
    }
    
    环回这是TCP连接的主动关闭方式。当客户端检测到服务器已关闭时,它将关闭其一半的连接

    在您的情况下,不是关闭客户端,而是发送更多数据。这会导致服务器发送RST数据包以强制关闭连接,因为收到的消息无效

    如果您仍然不确定,这里有一个与之相当的python客户端+服务器,它显示相同的行为。(我发现使用python很有帮助,因为它完全遵循基础BSD套接字API,而不使用C)

    服务器:

    import socket, time
    
    server = socket.socket()
    server.bind(("127.0.0.1", 9999))
    server.listen(1)
    sock, addr = server.accept()
    msg = sock.recv(1024)
    print msg
    print "closing"
    sock.close()
    time.sleep(3)
    print "done"
    
    客户:

    import socket, time
    
    sock = socket.socket()
    sock.connect(("127.0.0.1", 9999))
    sock.send("test\n")
    time.sleep(1)
    print "sending again!"
    sock.send("no error here")
    time.sleep(1)
    print "sending one last time"
    sock.send("broken pipe this time")
    
    要正确检测连接上的远程关闭,您应该执行
    Read()
    ,并查找
    io.EOF
    错误

    // we technically need to try and read at least one byte, 
    // or we will get an EOF even if the connection isn't closed.
    buff := make([]byte, 1)
        if _, err := conn.Read(buff); err != io.EOF {
        panic("connection not closed")
    }
    

    我担心的是Go API掩盖了这些信息。无法询问连接是否关闭,因此我希望库尊重FIN。这更多的是一个Go问题,而不是“TCP如何工作”问题。@ThomasBouldin:Go没有掩盖任何东西,它暴露了底层TCP连接的细节(或者我更合理地猜测,是BSD套接字API)。检测TCP连接上的远程关闭的方法是执行
    recv
    ,读取0个字节,然后Go通过在读取后返回EOF错误(以及读取0个字节)来暴露这一点.所以Go检查/确认FIN的方法是读取0B?@ThomasBouldin:对不起,我的错。使用nil缓冲区会无意中触发EOF
    ,因为达到EOF的唯一指标是“读取0字节”的条件。如果您在其中放入正确的字节片,它将在连接关闭前按预期阻塞。@TCP连接上的ThomasBouldin read()返回0表示对等方进行了正常关闭(即您收到了FIN),这实际上是在任何操作系统中实现套接字API的方式(这些操作系统上可用的编程语言要么直接公开,要么返回类似于“EOF”的内容)。TCP连接在收到FIN后不会关闭。然后,该方可以选择确认FIN,并进入半关闭状态,在该状态下,它可能会发送更多数据。不过,通常情况下,接收FIN的一方会发送ACK和自己的FIN以完全关闭连接。