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

如何用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()
}

关键细节说明

  1. goroutine的独立性:两个goroutine分别负责自己的任务,一个专注读服务器消息,一个专注读控制台输入并发消息,它们之间不需要额外的同步(比如互斥锁)——因为Go标准库的net.Conn已经保证了Read和Write方法是并发安全的,可以同时调用。

  2. 主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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.12 04:55:24