第十九章 如何高效和业务系统交互

我们现在重新来讨论青龙流式系统信令服务和业务之间关系。

信令简介

信令:驱动系统运转。控制各个模块的前后调用关系;业务不同,逻辑不同,信令也会千差万别

我们假设在一对一通信的场景下:

要实现一对一通信,驱动系统的核心就是信令。信令控制着系统各个模块之间的前后调用关系,比如当收到用户成功加入房间后,系统需要立即将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库进行实现
  • 如何使用ws库
    • 在Node中通过requier进行引入,然后利用ws的API进行交互实现如on方法接收消息,具体api请参开模块api。
    • 也可以使用其他的websocket库实现,如socket.io
  • 如何进行信令转发
    • 需要根据收到的不同的信令,返回不同的结果
  • 如何实现业务
    • 可以在自身模块是实现,也可以调用其他业务模块
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

Like (0)
Previous 2024-09-30
Next 2024-09-30

相关推荐

发表回复

Please Login to Comment
本文授权以下站点有原版访问授权 https://www.shxcj.com https://www.2img.ai https://www.2video.cn