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

Go TCP客户端使用os.Stdin输入结束后仍持续运行的原因是什么?

问题原因
  • 首先确认你是否正确触发了标准输入的EOF:Unix/Linux/macOS系统下需要在新行按Ctrl+D,如果当前行有未提交的输入,需要按两次Ctrl+D才会发送EOF;Windows系统下需要按Ctrl+Z后再按回车,才会结束标准输入。如果没有触发EOF,mustCopy(conn, os.Stdin)中的io.Copy会一直阻塞等待输入,主goroutine不会退出,程序自然不会结束。
  • 如果你已经正确触发了EOF,程序依然没有退出,核心原因是TCP为全双工通信协议:
    1. 标准输入EOF触发后,io.Copy完成写入操作正常返回,但此时客户端只完成了数据发送,没有主动关闭连接的写半通道,服务端的bufio.Scanner会一直阻塞等待新的输入,不会退出循环,也就不会主动关闭服务端侧的连接。
    2. 客户端后台启动的读goroutine mustCopy(os.Stdout, conn) 会一直阻塞等待从连接中读取数据,虽然主goroutine执行完mustCopy后会调用defer conn.Close()关闭连接,但由于服务端异步执行的echo协程还在往连接写入回显内容,会出现部分回显还没读取完成连接就被关闭的情况,如果你观察到回显没输出完程序就卡住,本质就是这个问题。
修复方案

客户端修改

在发送完所有输入后主动关闭写半通道,通知服务端没有更多数据需要发送:

func main() {
    conn, err := net.Dial("tcp", "localhost:8000")
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close()
    go mustCopy(os.Stdout, conn)
    mustCopy(conn, os.Stdin)
    // 关闭写半通道
    if tcpConn, ok := conn.(*net.TCPConn); ok {
        _ = tcpConn.CloseWrite()
    }
    // 等待读goroutine读取完所有回显,生产环境可用sync.WaitGroup更优雅实现
    select {}
}

服务端修改

服务端需要等待所有异步echo协程执行完成后再关闭连接,避免回显丢失:

import "sync"

func handleConn(c net.Conn) {
    var wg sync.WaitGroup
    input := bufio.NewScanner(c)
    for input.Scan() {
        wg.Add(1)
        go func(text string) {
            defer wg.Done()
            echo(c, text, 1*time.Second)
        }(input.Text())
    }
    // 等待所有回显处理完成
    wg.Wait()
    c.Close()
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 15:36:02