Golang TCP程序协程启动时频繁报connection refused错误原因咨询
Why does my Go server-client code fail with "connection refused" when run in the same process but works separately?
这是一个典型的goroutine调度竞态条件问题,我来给你拆解清楚:
当你在main函数里同时启动server()和client()两个goroutine时,Go的调度器并不会保证哪个goroutine先执行。绝大多数情况下,client()会抢先尝试发起连接,而此时server()可能还没完成net.Listen()的初始化流程,端口9999还处于未被监听的状态,自然就会抛出connection refused错误。偶尔成功的情况只是刚好调度器让server()先完成了监听准备,属于小概率的巧合。
而把server和client分开在不同文件运行时,你是手动先启动server,等它完全就绪后再启动client,从根源上避免了竞态问题,所以每次都能正常工作。
解决方法
我们可以通过同步机制确保server完成监听后,client再发起连接,这里用sync.WaitGroup是最直接的方案:
修改后的代码示例:
package main import ( "encoding/gob" "fmt" "net" "sync" ) var wg sync.WaitGroup func server() { // listen on a port ln, err := net.Listen("tcp", ":9999") if err != nil { fmt.Println("server, Listen", err) wg.Done() // 即使启动失败也标记完成,避免死等 return } fmt.Println("Server is ready to accept connections") wg.Done() // 通知client可以发起连接了 for { // accept a connection c, err := ln.Accept() if err != nil { fmt.Println("server, Accept", err) continue } // handle the connection go handleServerConnection(c) } } func handleServerConnection(c net.Conn) { // receive the message var msg string err := gob.NewDecoder(c).Decode(&msg) if err != nil { fmt.Println("handleServerConnection", err) } else { fmt.Println("Received", msg) } c.Close() } func client() { wg.Wait() // 等待server完成监听准备 // connect to the server c, err := net.Dial("tcp", "127.0.0.1:9999") if err != nil { fmt.Println("client, Dial", err) return } // send the message msg := "Hello World" fmt.Println("Sending", msg) err = gob.NewEncoder(c).Encode(msg) if err != nil { fmt.Println("client, NewEncoder", err) } c.Close() } func main() { wg.Add(1) go server() go client() var input string fmt.Scanln(&input) }
其他可选方案
- 在client中添加重试逻辑:如果连接失败,间隔一段时间后重试几次,直到成功或超时。
- 使用通道同步:用一个
chan struct{}来传递server就绪的信号,效果和WaitGroup类似。
核心思路都是消除两个goroutine之间的竞态,确保server处于就绪状态后,client再尝试连接。
内容的提问来源于stack exchange,提问作者Lame Fanello
相关产品推荐
相关产品推荐

