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

Golang net包读写阻塞求助:客户端服务端握手过程挂起

问题分析与解决方案

首先得敲黑板强调:TCP是无消息边界的字节流协议,这就是你遇到挂起问题的核心根源。当你分两次发送消息时,服务端的Read调用根本没法区分“第一条消息结束”和“第二条消息开始”——它只会傻等足够的字节,或者直到连接关闭,两边就这么互相卡住了。而合并成一条发送时,服务端一次读到完整内容,自然流程顺畅。

你遗漏的关键逻辑,就是消息的边界处理。必须在客户端和服务端之间提前约定好消息的分隔规则,常见的靠谱方案有这几种:

1. 固定长度消息

约定每条消息的字节长度固定,比如每条都是100字节,不足的部分用空字节填充。服务端每次读取固定长度的字节,就知道一条消息收尾了。

示例服务端读取逻辑:

buf := make([]byte, 100)
// 读取第一条消息
n, err := conn.Read(buf)
if err != nil {
    fmt.Println("read error:", err)
    return
}
msg1 := string(buf[:n])
// 读取第二条消息
n, err = conn.Read(buf)
if err != nil {
    fmt.Println("read error:", err)
    return
}
msg2 := string(buf[:n])
// 处理完回复客户端
conn.Write([]byte("两条消息已收到"))

2. 特殊分隔符标记

在每条消息末尾加一个特殊分隔符(比如\n或者自定义的__MSG_END__),服务端读取字节时,碰到分隔符就判定一条消息结束。

示例服务端读取逻辑(以\n为分隔符):

import "bytes"

func readMsg(conn net.Conn) (string, error) {
    buf := make([]byte, 1024)
    var msg []byte
    for {
        n, err := conn.Read(buf)
        if err != nil {
            return "", err
        }
        // 检查是否包含分隔符
        if idx := bytes.Index(buf[:n], []byte("\n")); idx != -1 {
            msg = append(msg, buf[:idx]...)
            return string(msg), nil
        }
        msg = append(msg, buf[:n]...)
    }
}

func handleConn(conn net.Conn) {
    defer conn.Close()
    // 读取第一条消息
    msg1, err := readMsg(conn)
    if err != nil {
        fmt.Println("read msg1 error:", err)
        return
    }
    // 读取第二条消息
    msg2, err := readMsg(conn)
    if err != nil {
        fmt.Println("read msg2 error:", err)
        return
    }
    fmt.Printf("收到消息:%s | %s\n", msg1, msg2)
    conn.Write([]byte("两条消息已收到"))
}

3. 先发送消息长度(最可靠常用)

在每条消息前先发送消息的长度(比如用4字节的大端序int表示),服务端先读取长度,再读取对应长度的字节,精准定位消息边界。

示例服务端读取逻辑:

import "encoding/binary"

// 读取消息长度
func readMsgLen(conn net.Conn) (int, error) {
    lenBuf := make([]byte, 4)
    _, err := conn.Read(lenBuf)
    if err != nil {
        return 0, err
    }
    return int(binary.BigEndian.Uint32(lenBuf)), nil
}

// 读取完整消息内容
func readMsgContent(conn net.Conn, length int) (string, error) {
    contentBuf := make([]byte, length)
    totalRead := 0
    for totalRead < length {
        n, err := conn.Read(contentBuf[totalRead:])
        if err != nil {
            return "", err
        }
        totalRead += n
    }
    return string(contentBuf), nil
}

func handleConn(conn net.Conn) {
    defer conn.Close()
    // 读取第一条消息
    len1, err := readMsgLen(conn)
    if err != nil {
        fmt.Println("read msg1 len error:", err)
        return
    }
    msg1, err := readMsgContent(conn, len1)
    if err != nil {
        fmt.Println("read msg1 content error:", err)
        return
    }
    // 读取第二条消息
    len2, err := readMsgLen(conn)
    if err != nil {
        fmt.Println("read msg2 len error:", err)
        return
    }
    msg2, err := readMsgContent(conn, len2)
    if err != nil {
        fmt.Println("read msg2 content error:", err)
        return
    }
    fmt.Printf("收到消息:%s | %s\n", msg1, msg2)
    conn.Write([]byte("两条消息已收到"))
}

另外还要注意:Golang的net.Conn.Read不一定会一次返回你想要的所有字节,它可能只返回部分数据,所以必须循环读取直到获取足够的字节——这也是你可能忽略的细节,比如服务端第一次Read只读到第一条消息的一半,然后一直等剩下的部分,客户端发完两条消息在等回复,两边就都僵住了。

补全你提供的服务端代码片段(还原核心逻辑):

/* ======= Server start ==============================================*/
package main

import (
    "fmt"
    "net"
)

func handleConn(conn net.Conn) {
    defer conn.Close()
    buf := make([]byte, 1024)
    // 第一次读取,可能只拿到部分数据
    n, err := conn.Read(buf)
    if err != nil {
        fmt.Println("read error:", err)
        return
    }
    fmt.Println("received first msg:", string(buf[:n]))
    // 第二次读取,这里会一直等待,因为没有消息边界,不知道第一条已经结束
    n, err = conn.Read(buf)
    if err != nil {
        fmt.Println("read error:", err)
        return
    }
    fmt.Println("received second msg:", string(buf[:n]))
    // 回复客户端
    conn.Write([]byte("hello client"))
}

func main() {
    listener, err := net.Listen("tcp", ":8080")
    if err != nil {
        fmt.Println("listen error:", err)
        return
    }
    defer listener.Close()
    for {
        conn, err := listener.Accept()
        if err != nil {
            fmt.Println("accept error:", err)
            continue
        }
        go handleConn(conn)
    }
}

总结一下,你只需要给客户端和服务端加上消息边界的约定逻辑,同时保证读取时循环获取足够字节,分两次发送消息的流程就能正常跑通,不会再挂起。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 03:39:38