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

Golang WebSocket保存多文件时连接异常断开问题排查

WebSocket连接在Golang服务器保存文件后断开(错误1001: going away)

问题描述

尝试通过WebSocket在Golang服务器上保存多个音频文件,但每次创建单个文件后,WebSocket连接就会断开,报错websocket: close 1001 (going away)。移除文件保存的代码块后,循环可以正常运行。请排查客户端导致连接断开的原因。

服务端代码

conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
    fmt.Println("Error upgrading to WebSocket:", err)
    return
}
defer conn.Close()
for i := 0; ; i++ {
    questionaire:= questionaires[i].msg //a couple of questions to be sent to client
    if i == 0 {
        // send initial message to frontend
        sendServerMessageToClient(conn, questionaire)
    } else {
        messageType, msg, err := conn.ReadMessage()
        if err != nil || err == io.EOF {
            fmt.Println(err) // this line here logs the websocket error
            break
        }
        response = ""
        if messageType == websocket.BinaryMessage {

            // if this blocks of code here is removed, it works, otherwise restarts the websocket connection
            fileName := fmt.Sprintf("answer%d.wav", i)
            file, err := os.Create(fileName)
            if err != nil {
                fmt.Println(err)
            }
              
            _, err = file.Write(msg)
            if err != nil {
                fmt.Println(err)
            }
            // til here
           // handle the saved file and call to speech to text api 
            
           response = "yes" //assumed the response is "yes"
        }
        switch response {
        case "yes":
            if i < len(questionaires) {
                sendServerMessageToClient(conn, questionaire)
            } else {
                sendServerMessageToClient(conn, "end")
                return
            }
        case "no":
            sendServerMessage(conn, "Exiting")
            return
        default:
            i--
        }
    }
}

客户端代码

....
    <div id="statusLabel">Status: Waiting for server...</div><br><br>
    <audio controls id="audioPlayer"></audio><br><br>
    <button onclick="startRecording()">Start Recording</button>
    <button onclick="stopRecording()">Stop Recording</button>

    <script>
        const statusLabel = document.getElementById('statusLabel');
        const audioPlayer = document.getElementById('audioPlayer');
        let mediaRecorder;
        let audioChunks = [];
        let ws;

        function startRecording() {
            navigator.mediaDevices.getUserMedia({ audio: true })
                .then((stream) => {
                    audioChunks = [];
                    statusLabel.textContent = "Status: Recording...";
                    mediaRecorder = new MediaRecorder(stream);
                    mediaRecorder.ondataavailable = (event) => {
                        if (event.data.size > 0) {
                            audioChunks.push(event.data);
                            ws.send(event.data);
                        }
                    };
                    mediaRecorder.onstop = () => {
                        const audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
                        const audioUrl = URL.createObjectURL(audioBlob);
                        audioPlayer.src = audioUrl;
                    };
                    mediaRecorder.start();
                })
                .catch((error) => console.error('Error accessing microphone:', error));
        }

        function stopRecording() {
            statusLabel.textContent = "Stopped recording";
            ws.send("stop"); // Notify the server to stop recording
            mediaRecorder.stop();
        }

        // Establish WebSocket connection
        ws = new WebSocket("ws://golang-server.com/ws");

        ws.onmessage = (event) => {
            statusLabel.textContent = `${event.data}`;
        };

        ws.onclose = (event) => {
            statusLabel.textContent = "Connection closed";
        };
    </script>
 ...

问题排查与修复方案

客户端侧核心问题

  1. 分片发送音频数据导致服务器阻塞超时
    MediaRecorder.start()默认会每隔几百毫秒触发ondataavailable事件,客户端每次收到数据就调用ws.send(event.data)发送小二进制块。服务器在处理文件写入时(磁盘IO操作)会阻塞ReadMessage()循环,无法及时响应客户端的后续消息,客户端因长时间未收到服务器反馈,主动触发连接断开(1001错误)。

  2. 发送的"stop"文本消息未被服务器处理
    点击停止时客户端发送字符串"stop"(文本类型),但服务器仅处理BinaryMessage类型,此时response为空进入default分支,i--导致循环卡住,服务器无法继续发送消息,客户端长期无响应后主动断开。

修复建议

客户端修改

  • 合并音频后一次性发送:移除ondataavailable中的ws.send(event.data),仅在onstop时发送完整的音频Blob,减少频繁通信对服务器的压力:
mediaRecorder.ondataavailable = (event) => {
    if (event.data.size > 0) {
        audioChunks.push(event.data);
        // 移除此处的ws.send(event.data)
    }
};
mediaRecorder.onstop = () => {
    const audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
    const audioUrl = URL.createObjectURL(audioBlob);
    audioPlayer.src = audioUrl;
    // 一次性发送完整音频
    ws.send(audioBlob);
};
  • 调整停止逻辑:如果需要保留"stop"通知,需确保服务器能处理文本消息,或者改为在发送完完整音频后由服务器判断流程结束。

服务端配套修改

  • 关闭文件句柄:文件写入后必须关闭,避免句柄泄漏和磁盘缓存阻塞:
fileName := fmt.Sprintf("answer%d.wav", i)
file, err := os.Create(fileName)
if err != nil {
    fmt.Println(err)
    continue
}
defer file.Close() // 新增:确保文件关闭
_, err = file.Write(msg)
if err != nil {
    fmt.Println(err)
}
  • 处理文本消息:添加文本消息分支,处理客户端的"stop"指令:
if messageType == websocket.BinaryMessage {
    // 原文件保存逻辑
} else if messageType == websocket.TextMessage {
    msgStr := string(msg)
    if msgStr == "stop" {
        sendServerMessage(conn, "Exiting")
        return
    }
}
  • 添加心跳机制:服务器定期发送Ping帧,客户端回复Pong,避免因长时间无通信被客户端判定为连接失效。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 09:05:39