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

如何在Golang中复用net.Conn实现Socket多消息收发?

问题分析与解决方案

核心问题定位

  • 连接存储错误:服务端用端口号作为连接map的key,新连接会直接覆盖旧连接,导致仅能保存最后一个客户端连接。
  • 单次读取即关闭:服务端connRead函数仅执行一次读取就关闭连接,完全违背长连接复用的需求。
  • 客户端读取逻辑bug:连续调用两次readLine(),会跳过一行消息;且未处理流结束的异常场景。
  • 消息边界不清晰:虽然手动添加了换行符,但两端的消息发送/接收逻辑未对齐,导致读取时无法稳定识别完整消息。

修改后的代码实现

Golang服务端代码

修正连接管理、循环读取逻辑,保证长连接复用:

import (
    "bufio"
    "flag"
    "fmt"
    "log"
    "net"
    "os"
    "sync"
)

const TYPE = "tcp"

var (
    SOCKET_PORT   = flag.Int("socket_server_port", 9001, "Socket server port")
    socketConnArr = make(map[string]net.Conn)
    connMutex     sync.Mutex // 并发操作map的安全锁
)

func StartSocket() {
    listen, err := net.Listen(TYPE, fmt.Sprintf(":%d", *SOCKET_PORT))
    if err != nil {
        log.Fatalf("listen err: %s", err)
        os.Exit(1)
    }
    defer listen.Close()

    fmt.Printf("Socket server running at :%d...\n", *SOCKET_PORT)

    for {
        conn, err := listen.Accept()
        if err != nil {
            log.Printf("accept err: %s", err)
            continue
        }
        remoteAddr := conn.RemoteAddr().String()
        connMutex.Lock()
        socketConnArr[remoteAddr] = conn
        connMutex.Unlock()

        fmt.Printf("client connected: %s\n", remoteAddr)

        // 为每个客户端启动独立的读写goroutine
        go handleClient(conn)
    }
}

// 处理单个客户端的持续读写逻辑
func handleClient(conn net.Conn) {
    remoteAddr := conn.RemoteAddr().String()
    // 连接断开时清理map并关闭连接
    defer func() {
        connMutex.Lock()
        delete(socketConnArr, remoteAddr)
        connMutex.Unlock()
        conn.Close()
        fmt.Printf("client disconnected: %s\n", remoteAddr)
    }()

    // 发送初始确认消息
    connWrite(conn, "socket server ack")

    // 循环读取客户端消息,直到连接断开
    scanner := bufio.NewScanner(conn)
    for scanner.Scan() {
        msg := scanner.Text()
        fmt.Printf("Received from %s: %s\n", remoteAddr, msg)
        // 可在此添加业务逻辑,比如回复客户端
        connWrite(conn, "Received: "+msg)
    }

    if err := scanner.Err(); err != nil {
        log.Printf("read from %s err: %s", remoteAddr, err)
    }
}

func SockWrite(message string) {
    connMutex.Lock()
    defer connMutex.Unlock()

    for _, conn := range socketConnArr {
        go connWrite(conn, message)
    }
}

func connWrite(conn net.Conn, message string) {
    // 统一添加换行符作为消息分隔符
    _, err := fmt.Fprintf(conn, "%s\n", message)
    if err != nil {
        log.Printf("write err: %s", err)
    }
}

func GetConnList() map[string]net.Conn {
    connMutex.Lock()
    defer connMutex.Unlock()
    // 返回map副本,避免外部修改原数据
    copyMap := make(map[string]net.Conn, len(socketConnArr))
    for k, v := range socketConnArr {
        copyMap[k] = v
    }
    return copyMap
}

Android Kotlin客户端代码

修正读取逻辑,优化资源管理:

lifecycleScope.launch(Dispatchers.IO) {
    var response: String?
    var reader: BufferedReader? = null
    var writer: PrintWriter? = null
    var socket: Socket? = null
    try {
        socket = Socket(SERVER_IP, SERVER_PORT)
        writer = PrintWriter(socket.getOutputStream(), true) // 自动flush
        reader = BufferedReader(InputStreamReader(socket.getInputStream()))

        // 发送初始化消息
        writer.println(socketInitMsg)
        Log.i(SocketHelper.TAG, "socket connected: ${socket.isConnected}")

        // 循环读取服务端消息,直到流关闭
        while (reader.readLine().also { response = it } != null) {
            Log.i(SocketHelper.TAG, "response: $response")
            // 在此添加消息处理逻辑
        }

        Log.i(SocketHelper.TAG, "socket disconnected")
    } catch (e: IOException) {
        Log.e(SocketHelper.TAG, "IO error: ${e.localizedMessage}")
    } catch (e: Exception) {
        Log.e(SocketHelper.TAG, "error: ${e.localizedMessage}")
    } finally {
        // 确保所有资源被关闭
        reader?.close()
        writer?.close()
        socket?.close()
    }
}

关键改进点说明

  • 连接管理优化:用客户端RemoteAddr()作为map的key,添加互斥锁保证并发安全,连接断开时自动从map中清理。
  • 长连接维持:服务端通过bufio.Scanner循环读取客户端消息,直到连接断开;客户端修正读取逻辑,避免跳过消息。
  • 消息边界统一:两端均用换行符\n作为消息分隔符,确保发送和接收的消息能被正确识别。
  • 错误处理增强:服务端不再因单个连接错误终止程序,客户端添加finally块保证资源正常释放。
  • 并发安全:对连接map的操作添加互斥锁,避免多goroutine操作导致的panic。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 22:54:53