You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

服务器如何感知客户端已离线?TCP连接场景疑问解析

TCP连接关闭检测:Go服务端如何感知客户端离线?

你的猜测是对的——这两种场景下,都是操作系统的TCP协议栈+Go标准库net包在协同工作,通过TCP协议本身的机制来通知服务端连接状态变化,不需要依赖额外的PING/PONG或TCP keep-alive(这俩是处理更极端断连场景的补充方案)。下面分别解释两个场景的具体流程:

场景1:客户端程序完整运行结束

你觉得“未显式关闭连接”是误解——客户端代码里的defer conn.Close()会在main函数执行完毕时自动触发,主动发起TCP的四次挥手流程:

  1. 客户端向服务端发送FIN包,告知“我没有数据要发送了”;
  2. 服务端TCP栈收到FIN后,先回复ACK确认;
  3. 服务端的conn.Read()调用会收到EOF(因为FIN表示对方已终止发送),Go的net库将这个EOF包装为错误返回;
  4. 服务端的handleConnection函数检测到Read错误,退出循环,执行defer conn.Close(),向客户端发送FIN包,完成四次挥手的最后一步,连接正式关闭。

场景2:客户端进程被提前强制关闭

当你直接杀死客户端进程时,操作系统会自动回收该进程持有的所有资源,包括它打开的TCP连接:

  1. 操作系统会向服务端发送RST(重置)包,明确告知“这个连接已经被强制终止”;
  2. 服务端的TCP栈收到RST后,会在后续的conn.Read()调用中返回对应的错误(就是你看到的wsarecv: An existing connection was forcibly closed by the remote host);
  3. 服务端检测到这个错误后,立即退出循环并关闭连接,结束该客户端的处理逻辑。

补充:和PING/PONG、TCP keep-alive的区别

上述两种场景都是TCP协议本身能直接感知的连接关闭(主动挥手/强制重置),而PING/PONG(应用层心跳)或TCP keep-alive是用来处理客户端异常断连但无法发送FIN/RST的情况(比如突然断电、网络彻底中断)——此时服务端的TCP栈收不到任何通知,只能通过定期探测来确认连接是否存活。


附完整测试代码

client.go

package main

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

func main() {
    // Connect to the server
    conn, err := net.Dial("tcp", "localhost:8080")
    if err != nil {
        fmt.Println("Error connecting:", err)
        return
    }
    defer conn.Close()

    // Send a message to the server
    message := "Hello from client!"
    time.Sleep(10000 * time.Millisecond)
    conn.Write([]byte(message))

    // Read the response from the server
    buffer := make([]byte, 1024)
    n, err := conn.Read(buffer)
    if err != nil {
        fmt.Println("Error reading:", err)
        return
    }

    // Print the server's response
    fmt.Printf("Received response from server: %s\n", buffer[:n])
}

server.go

package main

import (
    "fmt"
    "net"
)

func handleConnection(conn net.Conn) {
    defer conn.Close()

    // Obtain and print the client's IP address when the connection is established
    clientAddr := conn.RemoteAddr()
    fmt.Printf("Client connected from IP address: %s\n", clientAddr)

    for {
        // Read data from the client
        buffer := make([]byte, 1024)
        n, err := conn.Read(buffer)
        if err != nil {
            fmt.Println("Error reading:", err)
            return
        }

        // Print received message
        fmt.Printf("Received message from client (%s): %s\n", clientAddr, buffer[:n])

        // Send a response back to the client
        response := "Hello from server!"
        conn.Write([]byte(response))
    }
}

func main() {
    // Start listening on a port
    listener, err := net.Listen("tcp", "127.0.0.1:8080")
    if err != nil {
        fmt.Println("Error listening:", err)
        return
    }
    defer listener.Close()

    fmt.Println("Server listening on port 8080")

    for {
        // Wait for a connection
        conn, err := listener.Accept()
        if err != nil {
            fmt.Println("Error accepting connection:", err)
            continue
        }

        // Handle the connection in a goroutine
        go handleConnection(conn)
    }
}

内容的提问来源于stack exchange,提问作者LeaBit

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 15:34:58