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为全双工通信协议:
- 标准输入EOF触发后,
io.Copy完成写入操作正常返回,但此时客户端只完成了数据发送,没有主动关闭连接的写半通道,服务端的bufio.Scanner会一直阻塞等待新的输入,不会退出循环,也就不会主动关闭服务端侧的连接。 - 客户端后台启动的读goroutine
mustCopy(os.Stdout, conn)会一直阻塞等待从连接中读取数据,虽然主goroutine执行完mustCopy后会调用defer conn.Close()关闭连接,但由于服务端异步执行的echo协程还在往连接写入回显内容,会出现部分回显还没读取完成连接就被关闭的情况,如果你观察到回显没输出完程序就卡住,本质就是这个问题。
- 标准输入EOF触发后,
修复方案
客户端修改
在发送完所有输入后主动关闭写半通道,通知服务端没有更多数据需要发送:
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
相关产品推荐
相关产品推荐

