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

WebSocket多文件传输异常:代码问题排查及正确实现咨询

问题描述

我想通过WebSocket向服务器发送多个文件,目前写了前端和后端代码但无法正常运行。我觉得问题出在前端sendFile函数的缓冲区读取环节,但解决不了。请问该怎么实现多文件的独立传输?


前端代码

<html lang="en">
<head>
    <link rel="shortcut icon" href="#" />
    <script type="text/javascript">
        window.onload = function () {
            var conn;
            var server_msg = document.getElementById("server_msg");

            function sendFile(i) {
                indx = i.toString();
                var file = document.getElementById("image"+indx).files[0];

                if (!file) {
                    conn.send("none");
                } else {
                    conn.send(indx+file.name);
                    var reader = new FileReader();
                    var rawData = new ArrayBuffer();

                    reader.loadend = function() {

                    }
                    reader.onload = function(e) {
                        rawData = e.target.result;
                        conn.send(rawData);
                    }
                    reader.readAsArrayBuffer(file);
                }
            }

            document.getElementById("form").onsubmit = function () {
                if (!conn) {
                    return false;
                }
                for (let i = 1; i < 4; i++) {
                    sendFile(i);
                }
                return false;
            };

            if (window["WebSocket"]) {
                conn = new WebSocket("ws://" + document.location.host + "/ws");
                conn.binaryType = "arraybuffer";
                conn.onopen = function (evt) {
                    server_msg.innerHTML = "<b>Connection opened.</b>";
                    return false;
                };
                conn.onmessage = function (evt) {
                    server_msg.innerHTML = evt.data;
                    return false;
                };
                conn.onclose = function (evt) {
                    server_msg.innerHTML = "<b>Connection closed.</b>";
                    return false;
                };
                conn.onerror = function (evt) {
                    server_msg.innerHTML = "<b>Error happened!!!</b>" + evt.value;
                    return false;
                }
            } else {
                server_msg.innerHTML = "<b>Your browser does not support WebSockets.</b>";
            }
        };
    </script>
</head>
<body>
    <div id="server_msg"></div>
    <form id="form" enctype="multipart/form-data">
        <input type="file" id="image1" name="image1"> <br>
        <input type="file" id="image2" name="image2"> <br>
        <input type="file" id="image3" name="image3"> <br>
        <input type="submit" value="Send" />
    </form>
</body>
</html>

后端代码

app.Get("/ws", websocket.New(func(c *websocket.Conn) {
    defer c.Close()

    for ind := 1; ind < 4; ind++ {
        // step-1: check if filename is null
        _, msg, err := c.ReadMessage()
        if err != nil {
            log.Println("Producer: read error 1 - ", err)
            return
        }
        filename := string(msg)
        log.Printf("Producer: recieved filename - %s", filename)

        if filename != "none" {
            // step-2: read image bytes
            _, img_bytes, err := c.ReadMessage()
            if err != nil {
                log.Println("Producer: read error 2 - ", err)
                return
            }
            log.Println("Producer: recieved raw data")

            // step-3: save image
            fp, err := os.Create(filename)
            if err != nil {
                log.Fatal(err)
            }
            _, err2 := fp.Write(img_bytes)
            if err2 != nil {
                log.Fatal(err2)
            }
            fp.Close()
        }
    }
}))

问题分析与解决

核心问题是WebSocket发送异步,循环调用sendFile会导致消息顺序混乱:你同时触发3个文件的读取和发送,后端按固定顺序读取,但前端文件读取完成时间不确定,导致文件名和内容消息交叉到达,后端无法正确匹配。

前端修改方案

将sendFile改为异步函数,通过Promise控制发送顺序,确保一个文件的文件名和内容发送完成后,再处理下一个文件:

<html lang="en">
<head>
    <link rel="shortcut icon" href="#" />
    <script type="text/javascript">
        window.onload = function () {
            var conn;
            var server_msg = document.getElementById("server_msg");

            // 异步发送单个文件,返回Promise确保顺序
            async function sendFile(i) {
                const indx = i.toString();
                const fileInput = document.getElementById("image"+indx);
                const file = fileInput.files[0];

                if (!file) {
                    await new Promise(resolve => {
                        conn.send("none");
                        resolve();
                    });
                } else {
                    // 先发送文件名
                    await new Promise(resolve => {
                        conn.send(indx+file.name);
                        resolve();
                    });
                    // 读取文件并发送内容
                    const rawData = await new Promise((resolve, reject) => {
                        const reader = new FileReader();
                        reader.onload = function(e) {
                            resolve(e.target.result);
                        };
                        reader.onerror = reject;
                        reader.readAsArrayBuffer(file);
                    });
                    await new Promise(resolve => {
                        conn.send(rawData);
                        resolve();
                    });
                }
            }

            document.getElementById("form").onsubmit = async function () {
                if (!conn) {
                    return false;
                }
                // 串行发送,确保顺序
                for (let i = 1; i < 4; i++) {
                    await sendFile(i);
                }
                server_msg.innerHTML += "<br>所有文件已发送";
                return false;
            };

            if (window["WebSocket"]) {
                conn = new WebSocket("ws://" + document.location.host + "/ws");
                conn.binaryType = "arraybuffer";
                conn.onopen = function (evt) {
                    server_msg.innerHTML = "<b>Connection opened.</b>";
                };
                conn.onmessage = function (evt) {
                    server_msg.innerHTML += "<br>" + evt.data;
                };
                conn.onclose = function (evt) {
                    server_msg.innerHTML = "<b>Connection closed.</b>";
                };
                conn.onerror = function (evt) {
                    server_msg.innerHTML = "<b>Error happened!!!</b>" + evt.message;
                }
            } else {
                server_msg.innerHTML = "<b>Your browser does not support WebSockets.</b>";
            }
        };
    </script>
</head>
<body>
    <div id="server_msg"></div>
    <form id="form" enctype="multipart/form-data">
        <input type="file" id="image1" name="image1"> <br>
        <input type="file" id="image2" name="image2"> <br>
        <input type="file" id="image3" name="image3"> <br>
        <input type="submit" value="Send" />
    </form>
</body>
</html>

后端优化方案

将固定循环改为持续读取,添加错误反馈和结束标记,提升灵活性:

app.Get("/ws", websocket.New(func(c *websocket.Conn) {
    defer c.Close()

    for {
        // 读取文件名或结束标记
        _, msg, err := c.ReadMessage()
        if err != nil {
            if websocket.IsCloseError(err, websocket.CloseNormalClosure) {
                log.Println("客户端正常关闭连接")
            } else {
                log.Println("Producer: read error 1 - ", err)
            }
            return
        }
        filename := string(msg)
        // 约定"end"为结束标记,收到后停止接收
        if filename == "end" {
            log.Println("收到结束标记,停止接收文件")
            c.WriteMessage(websocket.TextMessage, []byte("所有文件接收完成"))
            return
        }
        log.Printf("Producer: recieved filename - %s", filename)

        if filename != "none" {
            // 读取文件内容
            _, img_bytes, err := c.ReadMessage()
            if err != nil {
                log.Println("Producer: read error 2 - ", err)
                return
            }
            log.Println("Producer: recieved raw data")

            // 保存文件,添加defer确保文件关闭
            fp, err := os.Create(filename)
            if err != nil {
                log.Printf("创建文件失败: %v", err)
                c.WriteMessage(websocket.TextMessage, []byte("创建文件失败: "+filename))
                continue
            }
            defer fp.Close()
            _, err2 := fp.Write(img_bytes)
            if err2 != nil {
                log.Printf("写入文件失败: %v", err2)
                c.WriteMessage(websocket.TextMessage, []byte("写入文件失败: "+filename))
                continue
            }
            log.Printf("文件保存成功: %s", filename)
            c.WriteMessage(websocket.TextMessage, []byte("文件接收完成: "+filename))
        }
    }
}))

关键说明

  1. 顺序保证:通过async/await实现串行发送,确保每个文件的文件名和内容按顺序到达后端。
  2. 错误反馈:前后端添加了错误提示,方便调试问题。
  3. 灵活性:后端改为循环读取直到收到结束标记,不再依赖固定的文件数量。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 02:15:38