net.TCPConn允许写入FIN包后

时间:2014-05-26 23:35:11

标签: tcp go

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

  1. 创建客户端&服务器连接。
  2. 通过从客户端成功向服务器发送消息来验证连接。
  3. 使用渠道告诉服务器调用conn.Close()并等待该调用完成。
  4. (尝试)通过再次在客户端连接上调用Write来验证连接是否完全断开。
  5. 第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")
      }
    }
    

1 个答案:

答案 0 :(得分:3)

这是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")
}