如何用Goroutine实现TCP客户端的并发消息收发?
解决TCP客户端并发收发消息的问题
嘿,你的思路完全正确——把发送消息和接收消息的逻辑拆分成两个独立的goroutine,就是解决这个串行阻塞问题的核心方案!
为什么你的原代码会阻塞?
你原来的代码在同一个循环里先等待控制台输入,再等待服务器消息,这就导致必须先输入内容,程序才会去检查有没有服务器发来的消息,完全做不到实时接收。而用goroutine把这两个逻辑分开后,它们就能各自独立运行,互不干扰。
完整的并发实现代码
这里给你一个可以直接用的版本,我会在后面解释关键细节:
package main import ( "bufio" "fmt" "log" "net" "os" ) func SocketClient() { // 建立TCP连接 conn, err := net.Dial("tcp", ":9000") if err != nil { log.Fatal("连接服务器失败: ", err) } defer conn.Close() log.Println("已连接到服务器") // 启动接收服务器消息的goroutine go func() { serverReader := bufio.NewReader(conn) for { msg, err := serverReader.ReadString('\n') if err != nil { log.Printf("接收消息失败/连接断开: %v", err) return } // 打印收到的消息(注意msg已经包含换行符) fmt.Printf("服务器: %s", msg) } }() // 启动读取控制台输入并发送的goroutine go func() { inputReader := bufio.NewReader(os.Stdin) for { fmt.Print("我: ") msg, err := inputReader.ReadString('\n') if err != nil { log.Printf("读取控制台输入失败: %v", err) return } // 发送消息到服务器 _, err = conn.Write([]byte(msg)) if err != nil { log.Printf("发送消息失败/连接断开: %v", err) return } } }() // 让主goroutine保持运行,否则程序会直接退出 // 这里用select{}永久阻塞,直到程序被手动终止或者连接断开 select{} } func main() { SocketClient() }
关键细节说明
goroutine的独立性:两个goroutine分别负责自己的任务,一个专注读服务器消息,一个专注读控制台输入并发消息,它们之间不需要额外的同步(比如互斥锁)——因为Go标准库的
net.Conn已经保证了Read和Write方法是并发安全的,可以同时调用。主goroutine的存活:最后的
select{}是让主goroutine一直阻塞,不然主函数执行完就会退出,所有子goroutine也会被强制终止。如果你需要优雅退出(比如用户输入exit就关闭客户端),可以加一个退出通道来控制:
package main import ( "bufio" "fmt" "log" "net" "os" "strings" ) func SocketClient() { // 建立TCP连接 conn, err := net.Dial("tcp", ":9000") if err != nil { log.Fatal("连接服务器失败: ", err) } defer conn.Close() log.Println("已连接到服务器") // 新增一个退出通道,用于通知所有goroutine退出 quit := make(chan struct{}) // 接收goroutine里加入退出检测 go func() { serverReader := bufio.NewReader(conn) for { select { case <-quit: log.Println("接收goroutine已退出") return default: msg, err := serverReader.ReadString('\n') if err != nil { log.Printf("接收失败: %v", err) close(quit) // 通知其他goroutine退出 return } fmt.Printf("服务器: %s", msg) } } }() // 发送goroutine里处理退出命令 go func() { inputReader := bufio.NewReader(os.Stdin) for { fmt.Print("我: ") msg, err := inputReader.ReadString('\n') if err != nil { log.Printf("读取输入失败: %v", err) close(quit) return } // 检测用户输入exit trimmedMsg := strings.TrimSpace(msg) if trimmedMsg == "exit" { log.Println("用户发起退出") close(quit) return } _, err = conn.Write([]byte(msg)) if err != nil { log.Printf("发送失败: %v", err) close(quit) return } } }() // 主goroutine等待退出信号 <-quit log.Println("客户端已退出") } func main() { SocketClient() }
这个版本里,当用户输入exit或者任何一方出现错误时,都会关闭quit通道,所有goroutine都会收到信号并优雅退出。
内容的提问来源于stack exchange,提问作者abhishek
相关产品推荐
相关产品推荐

