我们现在重新来讨论青龙流式系统信令服务和业务之间关系。
信令简介
信令:驱动系统运转。控制各个模块的前后调用关系;业务不同,逻辑不同,信令也会千差万别
我们假设在一对一通信
的场景下:
要实现一对一通信
,驱动系统的核心就是信令。信令控制着系统各个模块之间的前后调用关系
,比如当收到用户成功加入房间后,系统需要立即将RTCPeerConnention
对象创建好,以便向STUN/TURN服务器
请求其
外网的IP地址和端口;而当收到另一个用户加入房间的消息时,系统需要将自己的外网IP地址和端口交换给对方
,从而建立起Socket连接; WebRTC使用信令服务器交换媒体信息
和网络候选者
信息,信令服务器承担着消息传输和交换的工作,WebRTC规定了信令服务器的实现方式:任何能够进行网络信息交换的技术都可以用来实现信令服务器
,如HTTP、XMPP及WebSocket
等
在这个场景下,用户如何加入房间,其实也是具体业务逻辑需要考虑的一部分,通常需要用户登录认证业务逻辑,只有经过用户认证的用户才能与业务系统交互。
参考上面的架构图,
我们可以认为信令服务是整体业务的一部分,它会与认证服务器,和具体业务服务 如现在的场景里的房间服务互相交互的。在简单的系统里可以是一个单体服务方式部署,在复杂系统里,以分布式调用方式存在。
信令服务器主要作用
- 实现业务层管理
- 如用户创建房间,加入房间,退出房间等 这些是业务逻辑功能。
- 确定何时初始化、关闭和修改通话会议,也可以进行错误报告
- 让通信双方彼此交换网络信息
- 最常见的是交换通信双方的
IP地址和端口 - ICE Candidate
,这也是webrtc架构中信令服务器最基础的的功能。 - 两个WebRTC之间会尽可能选择
P2P
进行传输,同一个局域网
内直接通过P2P进行传输,不同局域网
内需要使用P2P穿越
后进行数据传输,P2P穿越成功后直接传输,失败后进行中转
等 – 后续的候选人中进行解说
- 最常见的是交换通信双方的
- 通信双方交换媒体信息
- 媒体信息用
SDP
来表示,这个SDP可以简单理解为:媒体类型的编码器是什么、是否支持该媒体类型和对应的编码器、编码方式是什么等。这也是webrtc架构中信令服务器最基础的的功能。
- 媒体信息用
信令传输协议的选择
一般选择TCP
或者基于TCP
的HTTP/HTTPS、WS/WSS等协议作为信令服务器的传输协议;
- 原因一:TCP是
可靠的传输协议
,可以保证传输的数据可靠、有序到达 - 原因二:TCP上传输数据是
流式的
,不必担心传输的数据过大导致的拆包传输
的问题
信令服务器的常见特点
- 实现或者调用相关的业务逻辑,如可以支持用户认证,同时支撑
多个WebRTC通话环境
,即多个房间
,且房间之间互不影响 - 实时性好,不可有明显的延时
- 支持可靠的信令传输,发送者要知道明确的发送反馈,即使发送失败了
- 性能好、可拓展性要好,要兼顾后续的拓展功能如传输应用数据等
信令服务器的实现方案
信令服务器的作用不是很复杂,常规的就是传输几个简单的信令,而这些信令既可以使用TCP、HTTP/HTTPS传输,也可以使用WebSocket/WSS协议传输,因此可以通过Web服务器实现信令服务器(如Nginx、Node)
原生方案
使用原生C/C++、Java等语言从零开发一个信令服务器,这种方案的实现成本非常高
现成的技术
利用现成的Web服务器做应用开发,如以Apache、Nginx、NodeJS等为服务,在其上做应用开发是不错的选择
优势
- 一般信令系统都需要使用HTTP/HTTPS、WS/WSS等基于TCP的传输协议,而Apache、Ng inx、NodeJS等服务器显然在这方面有天然的优势
- 实时通信的信令服务器一般负荷不会很高,这个量级对于Node和Nginx来说,单台服务器就可以了,再者通过Nginx和Node实现起来也相对容易些,且稳定性也挺高
信令服务器的实现
- 如何创建一个HTTP服务
- 通过Node的
HTTP/HTTPS库
和express库进行实现
- 通过Node的
- 如何使用ws库
- 在Node中通过requier进行引入,然后利用ws的API进行交互实现如
on
方法接收消息,具体api请参开模块api。 - 也可以使用其他的websocket库实现,如socket.io
- 在Node中通过requier进行引入,然后利用ws的API进行交互实现如
- 如何进行信令转发
- 需要根据收到的不同的信令,返回不同的结果
- 如何实现业务
- 可以在自身模块是实现,也可以调用其他业务模块
var express = require('express');
var app = express();
var http = require('http').Server(app);
//处理登录页面表单数据
app.post('/login',
urlencodedParser,
passport.authenticate('local', { failureRedirect: '/login' }),
function(req, res){
//这是一个简单的例子实现了登录,你可以在其他模块实现功能
var redirectTo = req.session.redirectTo ? req.session.redirectTo : '/';
delete req.session.redirectTo;
console.log(`Redirecting to: '${redirectTo}'`);
res.redirect(redirectTo);
}
);
let WebSocket = require('ws');
var streamerHttpServer = require(config.UseHTTPS ? 'https' : 'http').Server(streamerServerOptions);
streamerHttpServer.listen(streamerPort);
let streamerServer = new WebSocket.Server({ server: streamerHttpServer, backlog: 1 });
console.logColor(logging.Green, `WebSocket listening to Streamer connections on :${streamerPort}`)
let streamer; // WebSocket connected to Streamer
streamerServer.on('connection', function (ws, req) {
console.logColor(logging.Green, `Streamer connected: ${req.connection.remoteAddress}`);
sendStreamerConnectedToMatchmaker();
ws.on('message', function onStreamerMessage(msg) {
console.logColor(logging.Blue, `<- Streamer: ${msg}`);
try {
msg = JSON.parse(msg);
} catch(err) {
console.error(`cannot parse Streamer message: ${msg}\nError: ${err}`);
streamer.close(1008, 'Cannot parse');
return;
}
try {
if (msg.type == 'ping') {
streamer.send(JSON.stringify({ type: "pong", time: msg.time}));
return;
}
let playerId = msg.playerId;
delete msg.playerId; // no need to send it to the player
// Convert incoming playerId to a string if it is an integer, if needed. (We support receiving it as an int or string).
// ........
if (!player) {
console.log(`dropped message ${msg.type} as the player ${playerId} is not found`);
return;
}
if (msg.type == 'answer') {
player.ws.send(JSON.stringify(msg));
} else if (msg.type == 'iceCandidate') {
player.ws.send(JSON.stringify(msg));
} else if (msg.type == 'disconnectPlayer') {
player.ws.close(1011 /* internal error */, msg.reason);
} else {
console.error(`unsupported Streamer message type: ${msg.type}`);
streamer.close(1008, 'Unsupported message type');
}
} catch(err) {
console.error(`ERROR: ws.on message error: ${err.message}`);
}
});
function onStreamerDisconnected() {
sendStreamerDisconnectedToMatchmaker();
disconnectAllPlayers();
}
ws.on('close', function(code, reason) {
try {
console.error(`streamer disconnected: ${code} - ${reason}`);
onStreamerDisconnected();
} catch(err) {
console.error(`ERROR: ws.on close error: ${err.message}`);
}
});
ws.on('error', function(error) {
try {
console.error(`streamer connection error: ${error}`);
ws.close(1006 /* abnormal closure */, error);
onStreamerDisconnected();
} catch(err) {
console.error(`ERROR: ws.on error: ${err.message}`);
}
});
streamer = ws;
streamer.send(JSON.stringify(clientConfig));
});
总结
在具体场景中,比如视频会议,远程桌面场景中的业务功能的实现都是我们说的业务服务器。信令服务器是青龙流式系统架构中一个组成模块,要实现青龙流式系统架构就要有这个组成模块。信令服务器是同时也是业务服务器的组成部分。如果我们不使用青龙流式系统架构来实现上述业务场景,我们的业务实现里就可以没有信令服务器,而去使用其他的流式服务器
RA/SD 衍生者AI训练营。发布者:chris,转载请注明出处:https://www.shxcj.com/archives/6550