WebRTC 信令服务实战:用 socketio-over-nodejs 搭建 Socket.io 信令服务器
【免费下载链接】WebRTC-Experiment
WebRTC, WebRTC and WebRTC. Everything here is all about WebRTC!!
socketio-over-nodejs 是 WebRTC-Experiment 仓库中一套以 Socket.io 驱动的 Node.js 信令服务器参考实现:它利用 Socket.io 的命名空间(namespace)机制,为每个 WebRTC 会话(房间)动态创建独立的信令通道,用于交换参与请求、房间信息、SDP 与 ICE 候选等信令数据。本文将以 socketio-over-nodejs/README.md 为主线,结合 server.js 源码,完整讲解服务端部署、客户端 openSignalingChannel/openSocket 接入、房间存在性检测(Presence Detection)三大部分,让你可以在一小时内把这套信令服务器跑起来并接入自己的 WebRTC 应用。
一、项目概览:为什么 WebRTC 需要 Socket.io 信令服务
WebRTC 的媒体数据(音视频流、数据通道)走 P2P 通道,但连接建立之前的协商过程(交换 SDP 描述、ICE 候选)必须依赖一个信令通道。socketio-over-nodejs 就是为这个信令环节设计的轻量级服务端实现,其核心思路是:
- 一个 WebRTC 会话对应一个 Socket.io 命名空间:客户端通过
io.connect(URL).emit('new-channel', {channel, sender})请求创建新命名空间,此后该会话内所有信令消息都在这个专属命名空间内广播; - 服务端不做业务逻辑:只负责把消息路由、广播给同命名空间内的其他参与者,SDP/ICE 的语义处理完全由客户端(如 RTCMultiConnection、PeerConnection.js)完成;
- 天然适配 HTTPS:WebRTC 的
getUserMedia要求在安全上下文中运行,因此 README 与 server.js 均以 HTTPS 服务器形态给出。
从 package.json 可以看到,该 npm 包(socketio-over-nodejs,版本 3.0.x)依赖 socket.io 0.9.x,scripts.start 为 node server.js,即安装后即可直接以 server.js 作为入口运行。
二、服务端:理解 server.js 的信令核心
2.1 HTTPS 服务器与证书
server.js 使用 Node 内置 https 模块创建服务器,并加载 TLS 证书:
var options = {
// key: fs.readFileSync('fake-keys/privatekey.pem'),
// cert: fs.readFileSync('fake-keys/certificate.pem')
key: fs.readFileSync('/etc/letsencrypt/live/webrtcweb.com/privkey.pem'),
cert: fs.readFileSync('/etc/letsencrypt/live/webrtcweb.com/fullchain.pem')
};
var app = require('https').createServer(options, function(request, response) { ... });
两点实操提示:
- 务必替换为自己的证书。仓库在 fake-keys/ 目录下提供了
privatekey.pem与certificate.pem两个占位私钥/证书文件(仅供本地测试),源码注释也明确提醒 "don't forget to use your own keys!"。正式部署时,应使用自己域名签发的证书,例如 Let's Encrypt 生成的路径(如上源码所示)。 - 端口通过环境变量配置:文件末尾
app.listen(process.env.PORT || 9559)说明默认监听 9559 端口,也支持通过PORT环境变量覆盖,便于部署到 Heroku 等 PaaS 平台。启动后控制台会打印Please open SSL URL: https://localhost:9559/。
2.2 Socket.io 挂载与传输配置
var io = require('socket.io').listen(app, {
log: true,
origins: '*:*'
});
io.set('transports', [
// 'websocket',
'xhr-polling',
'jsonp-polling'
]);
这里 origins: '*:*' 表示允许任意来源跨域连接(便于各域名页面接入),传输方式被限定为 xhr-polling 与 jsonp-polling 两种轮询式传输(源码中将 websocket 注释掉)。需要说明的是:这是针对 socket.io 0.9.x 时代网络环境(如代理限制 WebSocket)的保守配置,如果你的部署环境允许,也可以放开 websocket 传输以获得更低延迟。
2.3 频道注册、Presence 与命名空间路由
服务端维护了一个内存对象 channels = {} 来登记所有频道(房间),核心逻辑在 server.js:
socket.on('new-channel', function (data) {
if (!channels[data.channel]) {
initiatorChannel = data.channel; // 记录频道创建者
}
channels[data.channel] = data.channel;
onNewNamespace(data.channel, data.sender); // 动态创建命名空间
});
socket.on('presence', function (channel) {
var isChannelPresent = !! channels[channel];
socket.emit('presence', isChannelPresent); // 回传频道是否存在
});
socket.on('disconnect', function (channel) {
if (initiatorChannel) {
delete channels[initiatorChannel]; // 创建者断开则注销频道
}
});
new-channel:收到创建请求后把频道名登记进channels,并调用onNewNamespace为它动态注册/channel命名空间;第一个创建者会被记录为initiatorChannel;presence:查询某频道是否已存在,供客户端决定是加入已有会话还是创建新会话(见本文第四节);disconnect:当频道创建者断开连接时,从channels中删除该频道,实现房间生命周期的回收。
onNewNamespace 函数(server.js)负责在每个命名空间内广播消息与通知用户离开:
function onNewNamespace(channel, sender) {
io.of('/' + channel).on('connection', function (socket) {
var username;
socket.on('message', function (data) {
if (data.sender == sender) {
if(!username) username = data.data.sender;
socket.broadcast.emit('message', data.data); // 广播给同房间其他成员
}
});
socket.on('disconnect', function() {
if(username) {
socket.broadcast.emit('user-left', username); // 通知其他成员有人离开
username = null;
}
});
});
}
关键机制:
- 命名空间内仅使用
socket.broadcast.emit(向除自己外的同房间成员广播),因此无需额外实现"房间成员列表"; - 通过记录首个发送消息者的
username来广播user-left事件,让其他客户端可以移除对应的视频元素或做 UI 清理。
三、客户端接入:openSignalingChannel 与 openSocket
3.1 面向 RTCMultiConnection 的 openSignalingChannel
README 指出:在 ui.js 文件或各类库中可以找到 openSocket/openSignalingChannel 方法。以 RTCMultiConnection 为例,其 v2 API 会通过 connection.setCustomSocketHandler(openSignalingChannel) 挂载自定义信令处理器(见 RTCMultiConnection/dev/enableV2Api.js),随后调用你在 connection.openSignalingChannel 中定义的方法。
README 给出的 openSignalingChannel 完整示例:
var SIGNALING_SERVER = 'https://socketio-over-nodejs2.herokuapp.com:443/';
connection.openSignalingChannel = function(config) {
var channel = config.channel || this.channel || 'default-namespace';
var sender = Math.round(Math.random() * 9999999999) + 9999999999;
io.connect(SIGNALING_SERVER).emit('new-channel', {
channel: channel,
sender : sender
});
var socket = io.connect(SIGNALING_SERVER + channel);
socket.channel = channel;
socket.on('connect', function () {
if (config.callback) config.callback(socket);
});
socket.send = function (message) {
socket.emit('message', {
sender: sender,
data : message
});
};
socket.on('message', config.onmessage);
};
对照服务端代码,这段客户端的执行流程与 server.js 一一对应:
io.connect(SIGNALING_SERVER).emit('new-channel', {channel, sender})触发服务端new-channel处理器,登记频道并动态创建命名空间;io.connect(SIGNALING_SERVER + channel)连接到刚创建的专属命名空间/channel;- 封装
socket.send(message),将其包装成{sender, data}后经message事件发出——服务端据此识别消息来源并broadcast.emit给同房间其他成员; - 收到命名空间上的
message事件时,回调config.onmessage,将远端信令数据(SDP/ICE/参与请求等)交给上层连接处理。
config.channel(房间名)可来自调用方,也可从 this.channel 取得,缺省回退到 'default-namespace',保证单房间场景无需额外传参即可工作。
3.2 面向自定义库的 openSocket
openSocket 是 openSignalingChannel 的姊妹形态,使用方式几乎一致(见 README):
var config = {
openSocket: function (config) {
var SIGNALING_SERVER = 'https://socketio-over-nodejs2.herokuapp.com:443/';
config.channel = config.channel || location.href.replace(/\/|:|#|%|\.|\[|\]/g, '');
var sender = Math.round(Math.random() * 999999999) + 999999999;
io.connect(SIGNALING_SERVER).emit('new-channel', {
channel: config.channel,
sender: sender
});
var socket = io.connect(SIGNALING_SERVER + config.channel);
socket.channel = config.channel;
socket.on('connect', function () {
if (config.callback) config.callback(socket);
});
socket.send = function (message) {
socket.emit('message', {
sender: sender,
data: message
});
};
socket.on('message', config.onmessage);
}
};
与上一节的区别仅在于默认频道名的生成方式:openSocket 用当前页面 URL 去掉 / : # % . [ ] 等特殊字符后作为默认 channel,这样同一页面地址天然对应同一信令房间,适合"打开同一页面即可互相连接"的演示型应用。
3.3 消息流总结
把上述两端拼起来,一条完整信令链路的形态是:
客户端A 服务端(server.js) 客户端B
|-- new-channel{channel,sender} -->|
| |-- 登记channels、创建/channel命名空间
|-- connect(/channel) ----------->| |
| |<-- connect(/channel) -----|
|-- send({sender,data}) --------->| |
| |-- broadcast.emit('message',data) -->|
|<-- message事件(远端数据) --------| |
README 中明确说明:io.connect(URL).emit('new-channel') 会启动一个全新的命名空间,它被"私有或公开地"用于传输/交换房间详情、参与请求、SDP、ICE 等一切协商数据——这正是该方案"一房间一命名空间"的核心价值。
四、Presence Detection:检测房间是否已存在
在实际应用中,发起方需要判断"目标房间是否已有人创建",决定自己是创建新会话还是加入既有会话。README 给出的完整实现:
var SIGNALING_SERVER = 'https://socketio-over-nodejs2.herokuapp.com:443/';
function testChannelPresence(channel) {
var socket = io.connect(SIGNALING_SERVER);
socket.on('presence', function (isChannelPresent) {
console.log('is channel present', isChannelPresent);
if (!isChannelPresent) startNewSession();
});
socket.emit('presence', channel);
}
// test whether default channel already created or not!
testChannelPresence('default-channel');
对应服务端 server.js 的实现:socket.on('presence', function (channel) { var isChannelPresent = !! channels[channel]; socket.emit('presence', isChannelPresent); })——服务端在内存 channels 中查表并回传布尔值。
使用注意:
- 服务端以
channels内存对象为准,若服务器重启,所有房间记录清空,客户端会收到false并重新创建会话; - 该机制依赖"创建者在线"(见
disconnect时删除initiatorChannel),因此它是轻量的"会话存活性"探测,而非持久化房间注册表。
五、部署与接入步骤
5.1 本地运行
npm install socketio-over-nodejs
node server.js
# 输出:Please open SSL URL: https://localhost:9559/
也可以从本仓库直接运行:socketio-over-nodejs/package.json 定义了 npm start 即 node server.js。注意默认代码加载的是 /etc/letsencrypt/live/webrtcweb.com/ 下的正式证书,本地测试请按 server.js 注释提示改用 fake-keys/ 下的 privatekey.pem 与 certificate.pem。
5.2 接入自有 WebRTC 应用
- 页面引入 socket.io 客户端脚本(仓库内配套示例见 socket.io/PeerConnection.js 与 demos/client-side-socket-io.html,后者展示了如何用
io.connect连接信令通道并配合RTCPeerConnection交换分段 SDP 与 ICE 候选); - 将
SIGNALING_SERVER替换为你部署的信令服务器地址; - 按第三节示例实现
connection.openSignalingChannel或config.openSocket; - 需要判断房间存活性时,按第四节实现
presence检测。
六、协议与许可
socketio-over-nodejs 采用 MIT 许可协议,版权归 Muaz Khan(详见 README 末节)。这意味着你可以自由地将这套信令方案集成进商业或开源项目中,只需保留版权声明即可。
【免费下载链接】WebRTC-Experiment
WebRTC, WebRTC and WebRTC. Everything here is all about WebRTC!!
火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。
更多推荐
所有评论(0)