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

使用net/rpc/jsonrpc通过Unix套接字发送大数组/切片为何阻塞?

Go net/rpc jsonrpc 大数组响应导致客户端阻塞问题排查

问题现象

使用Go内置net/rpc配合net/rpc/jsonrpc编解码器时,将数组作为RPC响应发送,当响应数据大小约为48字节时,客户端会在client.Call处阻塞;若将数组长度常量N改为5(响应数据更小),程序则能正常运行,且该问题仅在数组/切片大小超过49字节时触发。

问题根源

net/rpc/jsonrpc依赖JSON编码后的数据流完成通信,其默认实现未处理好TCP分包/粘包场景:

  • 当响应数据较小时,TCP会将整个JSON响应封装为单个数据包发送,客户端能一次性读取完整的JSON对象,正常解析响应。
  • 当响应数据达到一定大小,TCP会将数据拆分为多个数据包发送,而jsonrpc客户端的默认读取逻辑无法正确识别响应的结束边界,会一直等待剩余数据,最终导致阻塞。

解决方案

方案1:改用Go原生gob编解码器

gob是Go专属的序列化格式,对复杂数据类型、大数据的处理更稳定,不存在JSON的边界识别问题,是最快捷的解决方式:

  • 服务端:直接使用rpc.Register注册服务,通过rpc.Accept处理连接,无需指定jsonrpc。
  • 客户端:使用rpc.Dial建立连接,替代jsonrpc.Dial。

方案2:自定义jsonrpc边界处理逻辑

若必须使用JSON编码,可通过长度前缀的方式明确响应边界,让客户端先读取数据长度,再读取对应字节数的JSON内容:

服务端修改示例

func serveCustomJSONRPC(conn net.Conn) {
    defer conn.Close()
    codec := jsonrpc.NewServerCodec(conn)
    for {
        reqHeader, err := codec.ReadRequestHeader()
        if err != nil {
            break
        }
        resp := &rpc.Response{ServiceMethod: reqHeader.ServiceMethod, Seq: reqHeader.Seq}
        // 处理RPC请求
        err = rpc.ServeRequest(codec)
        if err != nil {
            resp.Error = err.Error()
        }
        // 序列化响应并添加长度前缀
        respBytes, _ := json.Marshal(resp)
        lengthBuf := binary.BigEndian.AppendUint32(nil, uint32(len(respBytes)))
        // 先发送长度,再发送响应内容
        conn.Write(lengthBuf)
        conn.Write(respBytes)
    }
}

客户端修改示例

func customJSONRPCCall(client *rpc.Client, method string, args, reply interface{}) error {
    // 获取底层连接
    conn, ok := client.Codec.(*jsonrpc.ClientCodec).Conn.(net.Conn)
    if !ok {
        return fmt.Errorf("invalid jsonrpc codec")
    }
    // 先读取响应长度
    var respLength uint32
    if err := binary.Read(conn, binary.BigEndian, &respLength); err != nil {
        return err
    }
    // 读取对应长度的响应内容
    respBytes := make([]byte, respLength)
    if _, err := io.ReadFull(conn, respBytes); err != nil {
        return err
    }
    // 解析响应
    var resp rpc.Response
    if err := json.Unmarshal(respBytes, &resp); err != nil {
        return err
    }
    if resp.Error != "" {
        return errors.New(resp.Error)
    }
    return json.Unmarshal([]byte(resp.Result), reply)
}

方案3:确保响应流正确关闭

检查服务端代码,确保发送完响应后正确刷新连接写入缓冲区,或在必要时关闭连接写入端,避免客户端等待额外数据。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 23:30:49