基于 WebRTC DataChannel 的文件与文本共享:DataConnection.js 快速实战指南(WebRTC-Experiment)

【免费下载链接】WebRTC-Experiment WebRTC, WebRTC and WebRTC. Everything here is all about WebRTC!! 【免费下载链接】WebRTC-Experiment 项目地址: https://gitcode.com/gh_mirrors/we/WebRTC-Experiment

导读

本文围绕 WebRTC-Experiment 仓库中 file-sharing/README.md 所介绍的 DataConnection.js 轻量级库展开,它基于 WebRTC DataChannel(RTCDataChannel)实现了浏览器到浏览器之间的文件共享与文本消息传输:任意大小的文件、任意长度的文本消息、以及任意类型的数据对象都可以直接在两个(或多个)浏览器之间点对点传输,全程不经过服务器保存数据内容。读完本文,你将掌握 DataConnection 从引入、建立连接、发送文件/文本、处理进度事件到接入自定义信令通道的完整用法,并通过仓库源码(data-connection.js)理解其底层分片传输、SDP/ICE 交换与房间广播机制。

一、DataConnection.js 是什么:三个核心能力

根据 file-sharing/README.md 的开篇说明,DataConnection.js 库允许开发者实现三类数据共享:

  1. 共享任意大小的文件(Share file of any size);
  2. 共享任意长度的文本消息(Share text message of any length);
  3. 共享任意类型、任意大小的文本型数据(Text data regardless of the size and type)。

从源码结构看(data-connection.js),window.DataConnection 是一个对外暴露的构造函数,内部由 Signaler(信令与连接管理)、Offer/Answer(RTCPeerConnection 封装)、RTCDataChannel(数据通道封装)、FileSender/FileReceiver(文件分片发送/接收)、TextSender/TextReceiver(文本分片发送/接收)、FileSaver(落盘保存)等模块协作完成。它的定位是一个"纯数据"连接方案:offerAnswerConstraints 中强制 OfferToReceiveAudio: false、OfferToReceiveVideo: false(data-connection.js),即只建 DataChannel、不协商任何音视频轨道,因此非常轻量。

二、快速上手:引入库并建立连接

2.1 引入库文件

原文档给出的引入方式是 CDN 形式,仓库内对应实现文件为 data-connection.js:

<script src="https://www.webrtc-experiment.com/data-connection.js"></script>

从该文件头部注释可知其许可为 MIT License,文档主页即 file-sharing/README.md。若希望完全依赖 WebRTC 标准接口,可参照仓库内 file-sharing/index.html 的做法,额外引入 WebRTC adapter(adapter-latest.js)以抹平各浏览器前缀差异。

2.2 最小可用代码

原文档给出的"最后一步"即最小编程模型:

var connection = new DataConnection( /* 'optional::firebase-channel' */ );

// 检查是否存在已建立好的连接(比如别人先 setup 了同一房间)
connection.check('connection-name');

document.getElementById('setup-new-connection').onclick = function() {
    connection.setup('connection-name');
};

关键点说明:

  • new DataConnection(channel):构造时可传入一个可选的通道名。若不传,源码会以当前页面 URL(去掉 /:|#%. 等字符)作为默认通道(data-connection.js)。
  • connection.check(roomid):向信令通道"探听"是否已有广播中的房间,适用于作为后来者加入别人创建好的连接。
  • connection.setup(roomid):由会话发起方广播房间信息,通知其他用户来加入(data-connection.js)。

二者分工在源码中很清晰:setup 会置 detectedRoom = true 并调用 signaler.broadcast() 周期性地广播 { roomid, broadcasting: true }(默认每 3000ms 一次,可通过 root.interval 调整,见 data-connection.js);check 则记录目标 roomid 并初始化信令器(data-connection.js),一旦在信令通道上收到广播消息,会触发 onconnection 回调并尝试 join。

2.3 连接打开与收尾事件

在 file-sharing/index.html 的真实演示中,连接建立成功后控件才会启用:

connection.onopen = function(e) {
    document.getElementById('file').disabled = false; // 连接打开后才允许选文件
};

connection.onerror = function(e) {
    console.debug('Error in data connection. Target user id', e.userid, 'Error', e);
};

connection.onclose = function(e) {
    console.debug('Data connection closed. Target user id', e.userid, 'Error', e);
};

对应 README 中的错误处理小节:

// 打开数据连接失败
connection.onerror = function(e) {}

// 数据端口(DataChannel)意外断开
connection.onclose = function(e) {}

从源码看,onopen/onclose/onerror 都来自 RTCDataChannel 通道事件的透传(data-connection.js),并在 onclose 时从 root.channels 中移除对应通道(data-connection.js)。

三、文本共享(Text Chat)

README 给出最简洁的广播式发送:

connection.send('longest possible text message');

点对点(直发某个用户)的方式:

connection.channels['user-id'].send('longest possible text message');

connection.channels 是"已建立通道的索引表",以对端 userid 为键(data-connection.js),其 send 方法最终仍会走 root.send(message, this.channel)。

3.1 底层实现:文本分片发送

查看 data-connection.js 中 TextSender 的实现:

  • 发送端先把内容 JSON.stringify(非字符串对象会自动序列化);
  • 若长度不超过 packetSize(固定 1000 字符)则一次性发送;
  • 若超过 1000 字符,则按每包 1000 字符切片,每发一包后用 setTimeout(..., 500) 间隔 500ms 发送下一包,直到最后一段打上 last: true 标记;
  • 每个数据包形如 { type: 'text', message: '...', packets: n, last: bool },先经 JSON.stringify 后写入通道。

接收端 TextReceiver(data-connection.js)把各分片 content.push() 收集,等收到 last: true 后 join 还原完整文本,并通过 root.onmessage({ data, userid }) 回抛给业务层。因此"任意长度文本"在实现上是按包重组完成的,这就是 README 承诺"任意长度"的原因。

四、文件共享(File Sharing)

发送文件同样有两种方式:

// 广播给所有已连接用户
connection.send(file);

// 直发给某个用户
connection.channels['user-id'].send(file);

send 内部通过 data.size 是否存在来判断是文件还是文本(data-connection.js),文件走 FileSender,文本走 TextSender。

4.1 文件分片与进度事件

README 给出的三个关键回调:

// 显示进度条!
channel.onFileProgress = function (e) {
    // e.userid
    // var packets = e.packets;

    // packets.remaining
    // packets.sent      (发送端)
    // packets.received  (接收端)
    // packets.length
};

// 文件成功发送
channel.onFileSent = function (e) {
    // e.userid
    // var file = e.file;
    // file.name
    // file.size
};

// 文件成功接收
channel.onFileReceived = function (e) { /* e.fileName, e.userid */ };

注:README 中示例变量名为 channel,实际这些回调既可挂在 connection 上(在 file-sharing/index.html 演示中就是 connection.onFileProgress/onFileSent/onFileReceived),也可结合 channels[userid] 使用,效果等价。

4.2 底层实现:Base64 分片传输

FileSender(data-connection.js)的实现要点:

  1. 用 FileReader.readAsDataURL(file) 把文件读成 DataURL(Base64 字符串);
  2. 以 packetSize = 1000 字符为一块切片,每包包含 type: 'file'、message(当前切片)、packets(总包数),最后一包追加 last: true 以及 name(文件名);
  3. 每发一包间隔 500ms(setTimeout)继续发下一包,避免一次性灌爆 DataChannel 缓冲;
  4. 每包发送前回调 root.onFileProgress,暴露 packets.remaining / sent / length 供渲染进度条;最后一包发出时触发 root.onFileSent。

接收端 FileReceiver(data-connection.js)反向操作:

  • 按 data.packets 记录总包数,content.push(data.message) 收集分片;
  • 每收一包回调 onFileProgress(此时含 packets.received);
  • 收到 last: true 后把全部分片 join('') 还原 DataURL,交给 FileSaver.SaveToDisk({ fileURL, fileName }) 触发浏览器下载,并回调 onFileReceived。

FileSaver.SaveToDisk(data-connection.js)通过动态创建 <a download> 并派发鼠标点击事件完成下载,随后 revokeObjectURL 释放内存。

4.3 文件真实落地示例

file-sharing/index.html 演示页中文件选择框与进度展示:

document.querySelector('input[type=file]').onchange = function() {
    var file = this.files[0];
    connection.send(file);
};

connection.onFileProgress = function(e) {
    appendDIV(e.packets.remaining + ' packets remaining.', e.userid);
};
connection.onFileSent = function(e) {
    appendDIV(e.file.name + ' sent.', e.userid);
};
connection.onFileReceived = function(e) {
    appendDIV(e.fileName + ' received.', e.userid);
};

该页面配合信令服务端(见下文)即可在真实双标签/双浏览器之间跑通"选文件 → 分片传输 → 自动下载"的完整链路。

五、自定义用户 ID 与直连消息

connection.userid = 'username';

源码中构造时默认 this.userid = getToken()(一个 5000~65535 之间的随机数,见 data-connection.js)。手动指定 userid 后,它会在所有信令消息中被带上(data.userid = userid),对端即可通过 e.userid 识别你,并可用 connection.channels['username'].send(...) 精准直发。

六、自定义信令通道(Signaling Channel)

6.1 支持的通道类型

README 明确指出,你可以接入任意信令通道:

  1. SIP-over-WebSockets;
  2. WebSocket over Node.js/PHP 等;
  3. Socket.io over Node.js 等;
  4. XMPP 等;
  5. XHR-POST-ing。

默认情况下,若不提供自定义信令通道,库会使用 Firebase(要求页面已引入 https://cdn.firebase.com/v0/firebase.js,见 data-connection.js),数据以 JSON 节点形式存在 Firebase 上,收到后立即 snap.ref().remove() 以模拟 socket.io 行为。

6.2 接入自定义通道

connection.openSignalingChannel = function(callback) {
    return io.connect().on('message', callback);
};

实现约定(对应 data-connection.js):openSignalingChannel(callback) 需要返回一个具有 send(string) 方法的对象,库内部把信令对象 JSON.stringify 后调用 socket.send(...);收到的消息字符串则交给 callback 回调,由库内 onSocketMessage 统一解析。

6.3 socket.io 服务端最小实现

README 给出的 Node.js 服务端代码:

io.sockets.on('connection', function (socket) {
    socket.on('message', function (data) {
        socket.broadcast.emit('message', data);
    });
});

仓库中的完整生产版参考 socketio-over-nodejs/server.js:它维护 channels 表记录已创建的房间,通过 new-channel 事件建立命名空间(io.of('/' + channel)),在命名空间内把 message 广播给除发送者外的所有人,并处理 presence 探测与 disconnect 清理。信令服务端只转发 JSON 信令(SDP/ICE/参与请求),不接触任何文件内容。

6.4 使用 Firebase 做信令

// "chat" 是你的 firebase id
connection.firebase = 'chat';

源码实现中默认地址为 https://' + (root.firebase || 'webrtc') + '.firebaseIO.com/' + channel(data-connection.js),通过 socket.push(data) 写入、child_added 监听读取。

七、手动加入房间与参与者管理

7.1 手动决定是否加入

connection.onconnection = function(room) {
    var button = document.createElement('button');
    button.onclick = function() {
        connection.join(room);
    };
};

onconnection 在每个新数据连接被发现时触发,join(room) 则允许你手动加入先前创建的连接。对应源码逻辑在 data-connection.js 与 data-connection.js:onmessage 收到 { roomid, broadcasting: true } 广播时触发 root.onconnection;join 会向广播者发送 participationRequest,随后由广播者 createOffer 完成 SDP 协商。

7.2 有人离开时

connection.onuserleft = function(userid) {
    console.debug(userid, 'left');
};

从源码看,离开处理远比 README 示例复杂(data-connection.js):信令层收到 leaving: true 时触发 onleave(README 中的 onuserleft 由上层映射);若离开者是广播者且未开启 autoCloseEntireSession,会把"广播者角色"转交给第一个参与者(playRoleOfBroadcaster,data-connection.js),保证房间不因发起者退出而解散;同时会 peer.close() 并清理 peers 表。

7.3 更多会话控制

从源码还可以发现 README 未展开的能力:

  • connection.eject(userid) / connection.leave(userid):不传参会话整体退出;广播者可向指定用户发送 getOut 消息将其踢出(data-connection.js);
  • root.autoCloseEntireSession:广播者离开时是否强制关闭整个会话;
  • 页面 beforeunload、按 F5、点击非 _blank 链接都会自动触发 leave()(data-connection.js),避免残留僵尸连接。

八、浏览器支持与许可

README 中列出的兼容性说明(基于该库编写时代的浏览器生态,使用时请以当前浏览器对 DataChannel 的支持为准):

浏览器支持版本
FirefoxStable / Aurora / Nightly
Google ChromeStable / Canary / Beta / Dev
AndroidChrome Beta

技术上,该库通过 window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection 的前缀兼容写法同时适配新旧浏览器(data-connection.js),建议在生产环境额外引入 adapter 库。

许可方面:DataConnection.js 以 MIT License 发布,版权归 Muaz Khan(2013)。仓库根目录另有 LICENSE 可查看整体许可。

九、从演示页到本地运行:完整跑通

仓库内 file-sharing/index.html 是完整可运行的演示页面,其关键流程为:

  1. 页面加载后根据 URL hash 生成房间号(如 #file-sharing-xxxx);
  2. 通过 io.connect(SIGNALING_SERVER) 连接信令服务器,发送 new-channel 建立命名空间;
  3. 覆写 connection.openSignalingChannel 接入该 socket;
  4. 调用 connection.check(roomid) 探查房间;点击"Setup New Data Connection"按钮调用 connection.setup(roomid) 并生成可分享链接;
  5. 连接打开后启用文件选择框,选文件即 connection.send(file),进度、成功、接收事件实时渲染到页面。

若要在本地体验,可配合仓库根目录的 server.js(HTTP 静态服务器,默认端口 9001,可用 process.env.PORT 覆盖)与 socketio-over-nodejs/server.js(HTTPS + socket.io 信令服务,默认端口 9559)共同搭建。需要注意 WebRTC 的 getUserMedia/DataChannel 对安全上下文的要求:生产环境请使用 HTTPS,自建 HTTPS 可参照仓库内 fake-keys 目录的证书配置方式。

十、同仓库的姊妹实现:WebRTC-File-Sharing

若需要对照学习另一种文件分片策略,可阅读 WebRTC-File-Sharing/README.md 与 WebRTC-File-Sharing/File.js:该实现使用 chunkSize(默认 40KB)的二进制分片(file.slice + readAsArrayBuffer + DataURL 转换),并引入 uuid 标识一次发送实例、maxChunks/currentPosition 进度追踪,配合 WebRTC-File-Sharing/PeerConnection.js 的 WebSocket 信令封装。相比之下,本文主讲的 DataConnection.js 更强调"一行 API 即用"的易用性,将分片、重组、信令、通道管理全部内聚在单一文件中。

结语

DataConnection.js 以极小的 API 面覆盖了 WebRTC 数据共享的完整闭环:setup/check 建立房间、send 智能区分文件与文本、onFileProgress/onFileSent/onFileReceived 驱动进度 UI、openSignalingChannel 让信令后端自由替换、onconnection/join/onuserleft 提供会话级控制。结合 file-sharing/data-connection.js 源码可以看到,其核心并不神秘——本质就是 DataChannel 上的 Base64 分片 + 定时发送 + 重组下载,加上一份健壮的 SDP/ICE 交换与房间广播逻辑。对于希望在浏览器端实现点对点文件传输、长文本聊天或任意文本数据的开发者而言,这份仓库与文档是一份可以直接借鉴的完整参考实现。

【免费下载链接】WebRTC-Experiment WebRTC, WebRTC and WebRTC. Everything here is all about WebRTC!! 【免费下载链接】WebRTC-Experiment 项目地址: https://gitcode.com/gh_mirrors/we/WebRTC-Experiment

Logo

邀请您加入社区

更多推荐