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

基于PeerJS的React安卓端文件传输速度过慢问题求助

安卓设备上PeerJS文件传输速度过慢的优化方案

我开发了一个React应用,通过PeerJS实现跨设备文件传输,但安卓设备上的文件传输速度过慢。项目通过二维码获取双方的Peer ID。以下是原发送和接收代码:

原发送端代码

useEffect(() => {
    var peer = new Peer();
    peer.on("open", function (id) {
      console.log(id);
    });
    peerRef.current = peer;
  }, []);

  const sendFiles = () => {
    var conn = peerRef.current.connect(`${receiverId}`);
    if (files != null) {
      conn.on("open", () => {
        for (let j = 0; j < files.length; j++) {
          const chunkSize = 1024 * 1024; // In bytes
          const chunks = Math.ceil(files[j].size / chunkSize);
          console.log(files[j].size);
          var pro = 0;
          for (let i = 0; i < chunks; i++) {
            const offset = i * chunkSize;
            pro = ((i + 1) / chunks) * 100;
            setProgress(pro);
            conn.send({
              file: files[j].slice(offset, offset + chunkSize),
              name: files[j].name,
              size: files[j].size,
              type: "file",
              progress: ((i + 1) / chunks) * 100,
            });
          }
        }
      });
    }

  };

原接收端代码

useEffect(() => {
    var peer = new Peer();

    peer.on("open", function (id) {
      setReceiverID(id);
      console.log(id);
    });

    peer.on("connection", (conn) => {
      var chunk = [];
      conn.on("data", (data) => {
        if (data) {
          setReceiving(true);
        }
        chunk = [...chunk, data.file];
        setProgress(data.progress);
        if (data.progress == 100) {
          console.log(data);
          combineTheChunks(chunk);
          chunk = [];
          setProgress(0);
          setReceivedName((prevname) => [...prevname, data.name]);
        }

        
      });
    });
  }, []);

问题分析

原代码存在几个导致安卓端传输缓慢的核心问题:

  • 同步密集发送Chunk:同步循环发送所有Chunk,没有考虑安卓设备的网络处理能力,导致数据拥塞,PeerJS无法及时处理所有数据包。
  • 过频繁的状态更新:每次发送Chunk都调用setProgress,触发React组件频繁重渲染,占用设备资源。
  • 低效的Chunk存储:接收端用chunk = [...chunk, data.file]创建新数组存储Chunk,频繁的数组拷贝会消耗内存和CPU。
  • Peer实例未优化:默认Peer配置可能没有针对移动端网络做适配,比如ICE服务器、带宽限制等。

优化方案及修改后代码

1. 异步发送+接收确认

通过等待接收端的确认消息控制发送节奏,避免安卓端网络拥塞。

2. 增大Chunk Size

将Chunk从1MB提升至2-4MB,减少发送次数,降低协议开销。

3. 减少状态更新频率

仅在进度明显变化时更新进度状态,避免不必要的重渲染。

4. 优化Chunk存储

用Map按文件名存储Chunk数组,按索引直接赋值,避免数组拷贝和顺序问题。

5. Peer配置优化

添加STUN服务器配置,指定WebRTC语义,提升移动端连接质量。


修改后的发送端代码

useEffect(() => {
  const peer = new Peer({
    config: {
      iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
      sdpSemantics: 'unified-plan'
    }
  });
  
  peer.on("open", (id) => console.log(id));
  peerRef.current = peer;

  // 组件卸载时销毁Peer,避免内存泄漏
  return () => peer.destroy();
}, []);

const sendFiles = async () => {
  const conn = peerRef.current.connect(`${receiverId}`);
  if (!files) return;

  conn.on("open", async () => {
    // 封装等待接收端确认的方法
    const waitForAck = () => new Promise(resolve => {
      conn.once("data", (ack) => ack.type === "ack" && resolve());
    });

    for (const file of files) {
      const chunkSize = 2 * 1024 * 1024; // 2MB Chunk
      const totalChunks = Math.ceil(file.size / chunkSize);
      let currentProgress = 0;

      for (let i = 0; i < totalChunks; i++) {
        const offset = i * chunkSize;
        const chunk = file.slice(offset, offset + chunkSize);
        currentProgress = Math.round(((i + 1) / totalChunks) * 100);

        // 每1%更新一次进度,减少重渲染
        if (currentProgress % 1 === 0) {
          setProgress(currentProgress);
        }

        // 发送Chunk及元数据
        conn.send({
          file: chunk,
          name: file.name,
          size: file.size,
          type: "file",
          chunkIndex: i,
          totalChunks,
          progress: currentProgress
        });

        // 等待接收端确认后再发送下一个Chunk
        await waitForAck();
      }
    }
  });
};

修改后的接收端代码

useEffect(() => {
  const peer = new Peer({
    config: {
      iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
      sdpSemantics: 'unified-plan'
    }
  });

  peer.on("open", (id) => {
    setReceiverID(id);
    console.log(id);
  });

  peer.on("connection", (conn) => {
    // 用Map存储多文件的Chunk,支持并发传输
    const fileChunkMap = new Map();

    conn.on("data", (data) => {
      if (data.type === "file") {
        setReceiving(true);

        // 初始化当前文件的Chunk数组
        if (!fileChunkMap.has(data.name)) {
          fileChunkMap.set(data.name, new Array(data.totalChunks));
        }
        const chunks = fileChunkMap.get(data.name);
        chunks[data.chunkIndex] = data.file; // 按索引直接存储,保证顺序

        // 减少进度更新频率
        const currentProgress = data.progress;
        if (currentProgress % 1 === 0) {
          setProgress(currentProgress);
        }

        // 发送确认消息,通知发送端可以继续发送下一个Chunk
        conn.send({ type: "ack" });

        // 所有Chunk接收完成,合并文件
        if (currentProgress === 100) {
          const allChunks = fileChunkMap.get(data.name);
          combineTheChunks(allChunks, data.name);
          fileChunkMap.delete(data.name);
          setProgress(0);
          setReceivedName(prev => [...prev, data.name]);
        }
      }
    });
  });

  return () => peer.destroy();
}, []);

额外优化建议

  • 如果传输大文件,可考虑添加带宽检测,动态调整Chunk大小和发送速率。
  • 若存在NAT穿透问题,添加TURN服务器配置,提升安卓设备在复杂网络环境下的连接成功率。
  • 接收端的combineTheChunks方法可优化为使用Blob直接合并,避免内存溢出:
    const combineTheChunks = (chunks, fileName) => {
      const blob = new Blob(chunks);
      // 后续处理Blob(如下载、存储等)
    };
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 16:35:16