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