tcp 连接竟然在关闭之后也能读取到数据

202 阅读1分钟

最近在学计网,学到 tcp 三次握手四次挥手。书上说关闭连接(挥手)分为以下4个步骤:

  1. A 向 B 发送关闭报文。
  2. B 向 A 回复确认报文。A 收到报文后进入到半关闭状态,不再发送数据。
  3. B 回复后依然可以发送数据。在 B 发送完所有数据后会发送关闭报文。
  4. A 向 B 回复确认报文。B 收到报文后进入关闭状态,A 等待一段时间后也进入关闭状态。

这里的第 3 步还挺有意思的。想想也是,全双工的连接那自然需要两边都关闭才叫真的关闭。只有一边关了的话另一边肯定还能发数据。之前都是以为 tcp 连接断开了就没用了,平时写代码都是 defer conn.Close() 的。没想到关了连接之后还能读到数据。笔者又写了个 demo 试了一下,发现真的可以。代码如下:

// a.go 客户端
package main

import (
  "net"
  "os"
)

func main() {
  // 2. 建立连接
  c, _ := net.Dial("tcp", "127.0.0.1:9090")
  defer c.Close()
  
  // 4. 关闭写方向连接
  conn := c.(*net.TCPConn)
  conn.CloseWrite()

  // 7. 打印收到的数据
  conn.WriteTo(os.Stdout)
}
// b.go 服务端
package main

import (
  "fmt"
  "net"
  "time"
)

func main() {
  // 1. 监听端口
  l, _ := net.Listen("tcp", "127.0.0.1:9090")
  defer l.Close()

  // 3. 建立连接
  conn, _ := l.Accept()
  defer conn.Close()

  // 5. 等客户端半关连接
  time.Sleep(100 * time.Millisecond)

  // 6. 写数据
  fmt.Fprintln(conn, "goodbye")
}

打开两个终端,先在一个终端执行 go run b.go,再在另一个终端执行 go run a.go。最终 a 这边会打印 "goodbye"。

image.png