WebRTC 跨设备文件传输解决方案

WebRTC(Web Real-Time Communication)技术不仅支持音视频通信,其 RTCDataChannel接口也非常适合实现点对点(P2P)的跨设备文件传输,具有低延迟、无需中转服务器(可直连时)等优势。

 核心实现方案

1. 基础架构设计

// 文件传输核心类框架
class WebRTCFileTransfer {
    constructor() {
        this.peerConnection = null;
        this.dataChannel = null;
        this.fileChunkSize = 16 * 1024; // 16KB 每块 this.transferState = {
            file: null,
            offset: 0,
            chunks: [],
            receivedSize: 0 };
    }

    // 初始化 PeerConnection
    async initializeConnection(configuration = { iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] }) {
        this.peerConnection = new RTCPeerConnection(configuration);
        
        // 监听 ICE 候选
        this.peerConnection.onicecandidate = (event) => {
            if (event.candidate) {
                // 通过信令服务器发送候选 this.sendSignal('candidate', event.candidate);
            }
        };

        // 监听连接状态
        this.peerConnection.onconnectionstatechange = () => {
            console.log('Connection state:', this.peerConnection.connectionState);
        };
    }
}

2. 信令服务器实现(关键)

WebRTC 需要信令服务器交换 SDP 和 ICE 候选信息。以下是简化的 WebSocket 信令服务器:

// Node.js + WebSocket 信令服务器
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });

const rooms = new Map();

wss.on('connection', (ws) => {
    ws.on('message', (message) => {
        const data = JSON.parse(message);
        
        switch (data.type) {
            case 'join':
                if (!rooms.has(data.roomId)) {
                    rooms.set(data.roomId, []);
                }
                rooms.get(data.roomId).push({ ws, id: data.userId });
                break;
                
            case 'offer':
            case 'answer':
            case 'candidate':
                // 转发信令给房间内其他用户
                const room = rooms.get(data.roomId);
                if (room) {
                    room.forEach(client => {
                        if (client.id !== data.userId) {
                            client.ws.send(JSON.stringify(data));
                        }
                    });
                }
                break;
        }
    });
});

3. 文件分片传输实现

// 发送方:文件分片处理
async function sendFile(file) {
    const chunkSize =16 * 1024; // 16KB const totalChunks = Math.ceil(file.size / chunkSize);
    let offset = 0;
 // 发送文件元数据 const metadata = {
        type: 'metadata',
        fileName: file.name,
        fileSize: file.size,
        fileType: file.type,
        totalChunks: totalChunks };
    dataChannel.send(JSON.stringify(metadata));
    
    // 分片发送文件内容 while (offset < file.size) {
        const chunk = file.slice(offset, offset + chunkSize);
        const reader = new FileReader();
 await new Promise((resolve) => {
            reader.onload = (e) => {
                const chunkData = {
                    type: 'chunk',
                    chunkIndex: Math.floor(offset / chunkSize),
                    data: e.target.result,
                    isLast: offset + chunkSize >= file.size
                };
                dataChannel.send(JSON.stringify(chunkData));
                resolve();
            };
            reader.readAsArrayBuffer(chunk);
        });
        
        offset += chunkSize;
        updateProgress((offset / file.size) * 100);
    }
}

// 接收方:文件重组
function setupReceiver() {
    let receivedChunks = [];
    let fileMetadata = null;
    let receivedCount = 0;
 dataChannel.onmessage = (event) => {
        const data = JSON.parse(event.data);
        
        switch (data.type) {
            case 'metadata':
                fileMetadata = data;
                receivedChunks = new Array(data.totalChunks);
                break;
                
            case 'chunk':
                receivedChunks[data.chunkIndex] = data.data;
                receivedCount++;
 if (receivedCount === fileMetadata.totalChunks) {
                    reassembleFile(fileMetadata, receivedChunks);
                }
                break;
        }
    };
}

function reassembleFile(metadata, chunks) {
    const blob = new Blob(chunks, { type: metadata.fileType });
    const url = URL.createObjectURL(blob);
    
    // 创建下载链接 const a = document.createElement('a');
    a.href = url;
    a.download = metadata.fileName;
    a.click();
    URL.revokeObjectURL(url);
}

4. 传输优化策略

// 流量控制与重传机制
class TransferController {
    constructor() {
        this.windowSize = 10; // 滑动窗口大小
        this.sentPackets = new Map();
        this.ackQueue = [];
        this.sequenceNumber = 0;
    }
 // 可靠传输实现
    sendWithAck(data) {
        const packet = {
            seq: this.sequenceNumber++,
            data: data,
            timestamp: Date.now(),
            retries: 0
        };
 this.sentPackets.set(packet.seq, packet);
        dataChannel.send(JSON.stringify({
            type: 'data',
            seq: packet.seq,
            payload: data }));
        
        // 启动超时重传
        setTimeout(() => this.checkAck(packet.seq), 1000);
    }
 // 接收方确认
    sendAck(seq) {
        dataChannel.send(JSON.stringify({
            type: 'ack',
            seq: seq
        }));
    }
}

完整实现对比

特性纯 WebRTC 方案WebRTC + IndexedDBWebRTC + 服务端辅助
传输速度高(P2P直连)中等中等(依赖中继)
可靠性中等(依赖NAT穿透)高(本地存储)高(服务端备份)
大文件支持受内存限制支持超大文件支持超大文件
离线传输需要初始连接支持断点续传需要网络连接
实现复杂度中等较高高

部署配置示例

# Docker 部署配置
version: '3.8'
services:
  signaling:
    image: node:16
    ports:
      - "8080:8080"
    volumes:
      - ./signaling-server:/app
    command: node server.js
  
  turn-server:
    image: coturn/coturn ports:
 "3478:3478"
      - "3478:3478/udp"
      - "5349:5349"
    environment:
      - TURN_SECRET=your-secret-key - REALM=your-domain.com
    command: |
      --fingerprint
      --lt-cred-mech
      --realm=${REALM}
      --user=username:${TURN_SECRET}
      --no-tls
      --no-dtls

安全增强措施

// 端到端加密
async function encryptChunk(chunk, key) {
    const iv = crypto.getRandomValues(new Uint8Array(12));
    const algorithm = { name: 'AES-GCM', iv: iv };
 const encrypted = await crypto.subtle.encrypt(
        algorithm,
        key,
        chunk );
    
    return {
        iv: Array.from(iv),
        data: Array.from(new Uint8Array(encrypted))
    };
}

// 生成传输密钥
async function generateTransferKey() {
    const key = await crypto.subtle.generateKey(
        { name: 'AES-GCM', length: 256 },
        true,
        ['encrypt', 'decrypt']
    );
 const exported = await crypto.subtle.exportKey('jwk', key);
    return JSON.stringify(exported); // 通过安全通道共享
}

性能监控

// 传输统计
class TransferMetrics {
    constructor() {
        this.metrics = {
            startTime: Date.now(),
            bytesTransferred: 0,
            chunksSent: 0,
            retransmissions: 0,
            latencySamples: []
        };
    }
    
    logTransfer(chunkSize) {
        this.metrics.bytesTransferred += chunkSize;
        this.metrics.chunksSent++;
 // 计算实时速度 const elapsed = (Date.now() - this.metrics.startTime) / 1000;
        const speed = (this.metrics.bytesTransferred / elapsed / 1024).toFixed(2);
 return {
            speed: `${speed} KB/s`,
            progress: `${this.metrics.bytesTransferred} bytes`,
            efficiency: `${this.metrics.retransmissions / this.metrics.chunksSent * 100}% retransmission rate`
        };
    }
}

此方案充分利用 WebRTC 的 P2P 特性,在支持 STUN/TURN 的情况下可实现 NAT 穿透,提供高效的文件传输能力。关键优化点包括分片传输、流量控制、断点续传(结合 IndexedDB)和端到端加密。

当前最佳实现案例:SendTomo文件传输工具,跨设备互传、跨网络、不限速、支持大文件快速传输、文件不经过服务器。

 

Logo

邀请您加入社区

更多推荐