Структура процесса сервера OWT и поток обработки кода JS[Open WebRTC Toolkit]

Структура процесса сервера OWT и поток обработки кода JS[Open WebRTC Toolkit]


Оглавление

вставьте сюда описание изображения

Статьи по Теме:

  1. Установите сервер OWT [Open WebRTC Toolkit] в среде Ubuntu.
  2. Установите сервер OWT [Open WebRTC Toolkit] в среде Docker.
  3. Общий анализ архитектуры сервера OWT [Open WebRTC Toolkit]
  4. Анализ сигналов сервера OWT (часть 1) [Open WebRTC Toolkit]
  5. Анализ сигналов сервера OWT (часть 2) [Open WebRTC Toolkit]
  6. Структура процесса сервера OWT и поток обработки кода JS[Open WebRTC Toolkit]
  7. REST-API сервера OWT

1. Структура процесса

  1. Среди основных функций SFU будут задействованы несколько компонентов, таких как Management API, Cluster Manager, WebRTC Portal, Conference Agent и WebRTC Agent.
  2. С точки зрения языка программирования, многие компоненты будут содержать коды Javascript и C++, процесс каждого компонента запускается NodeJS, а код C++ будет компилироваться в виде расширения NodeJS, а затем использоваться кодом Javascript.
  3. С точки зрения структуры кода многие компоненты имеют общий код, прямой анализ исходного кода проекта вызовет некоторые помехи, поэтому код после выпуска проверяется.
  4. Каждый компонент представляет собой независимый каталог, содержащий полный код, необходимый для запуска этого компонента.

изображение.png

  1. Каждый компонент отвечает за обработку некоторых задач, например WebRTC Portal отвечает за сигнализацию взаимодействия с WebRTC клиентами, Conference Agent отвечает за управление и контроль конференц-залов, а WebRTC Agent отвечает за публикацию и подписку на потоки, Каждая задача будет соответствовать задача.

  2. Каждый агент (например, агент WebRTC) будет иметь основной процесс (называемый рабочим процессом в коде, также называемый процессом агента) и несколько рабочих процессов (называемых в коде процессом узла).

  3. Процесс агента будет предоставлять службы RPC (getNode, recycleNode, queryNode), управляемые процессом узла, а процесс узла будет предоставлять службы RPC для фактической работы (например, операции публикации WebRTC и т. д.).

  4. getNode вернет rpcId процесса узла (т. е. nodeId), который используется вызывающей стороной для прямой инициации RPC к узлу.

  5. Менеджер кластера отвечает за планирование агента, а не узла.

  6. Агент и несколько узлов работают на одном компьютере, и для одного и того же компьютера нет необходимости планировать. Он будет записывать взаимосвязь между агентом и задачей как вариант загрузки агента и соответствующим образом реализовывать логику планирования.

  7. Схема базовой архитектуры модуля:

    1. Вызовы RPC внутри модуля реализованы через очередь сообщений RabbitMQ, а не прямые вызовы, то есть все пунктирные линии на приведенном выше рисунке взаимодействуют косвенно через RabbitMQ вместо прямых соединений.
    2. Внутренний обмен потоками модуля (Internal IO) по умолчанию использует протокол TCP для прямого взаимодействия.
    3. Информация о конференц-зале (макет по умолчанию, поддерживаемые форматы кодирования и т. д.) сохраняется в MongoDB при создании конференции.
      вставьте сюда описание изображения
  8. Затем объедините конкретный код для анализа процесса создания и взаимодействия описанной выше структуры процесса, а также потока обработки нескольких типовых запросов.

2. API управления

  1. На самом деле Management API (каталог компонентов — management_api) не принадлежит к вышеописанной структуре процессов, он включает в себя главный процесс и несколько рабочих процессов (по умолчанию — количество ядер ЦП) и использует кластерный модуль NodeJS для реализовать многопроцессорные сервисы.
  2. Его вход — api.js, который будет предоставлять различные интерфейсы RESTful, определенные в каталоге ресурсов, такие как комнаты (комнаты), потоки (потоки), участники (пользователи)
// Only following paths need authentication.
var authPaths = ['/v1/rooms*', '/v1.1/rooms*', '/services*', '/cluster*'];
app.get(authPaths, serverAuthenticator.authenticate);
app.post(authPaths, serverAuthenticator.authenticate);
app.delete(authPaths, serverAuthenticator.authenticate);
app.put(authPaths, serverAuthenticator.authenticate);
app.patch(authPaths, serverAuthenticator.authenticate);

app.post('/services', servicesResource.create);
app.get('/services', servicesResource.represent);

app.get('/services/:service', serviceResource.represent);
app.delete('/services/:service', serviceResource.deleteService);

...

3. Портал WebRTC

  1. WebRTC Portal (директория компонента - portal), у него всего один главный процесс, основная логика такова:
    1. После запуска основного процесса он подключится к серверу RabbitMQ и получит клиент RPC, который используется для инициации RPC для других компонентов. RPC на основе RabbitMQ реализуется amqp_client.js и используется различными компонентами.
    2. После успешного подключения он зарегистрируется в Менеджере кластеров, а логика взаимодействия с Менеджером кластеров реализована clusterWorker.js, а также используется различными компонентами, которые будут проанализированы позже.
    3. После успешной регистрации он предоставит собственную службу RPC и отслеживает состояние службы RPC, которая в основном используется для обработки аномальных событий.
    4. После успешного мониторинга выполните функцию startServers, чтобы запустить сервер постоянных соединений SocketIO. Логика сервера SocketIO находится в socketIOServer.js и v11Client.js (помимо v11Client есть еще v10Client и legacyClient для совместимости со старыми версиями).
      1. socketIOServer прослушивает только шесть событий входа в систему, повторного входа в систему, refreshReconnectionTicket, выхода из системы, отключения и подключения. Среди них первые четыре определяются сигнальным протоколом сервера OWT, а последние два инициируются отключением и доступом клиента постоянного подключения. При обработке событий входа и повторного входа он будет определять, какую версию Клиента использовать в соответствии со значением поля протокола.
      2. После создания клиента будет вызвана его функция присоединения, и, наконец, в portal.js инициируется RPC для получения nodeId агента конференции, а затем инициируется RPC присоединения к узлу агента конференции.
      3. v11Client прослушивает все другие события протокола сигнализации сервера OWT, такие как публикация, подписка и т. д.
      4. После того, как событие отслеживается в v11Client, для обработки будут вызываться различные функции в portal.js.Давайте возьмем событие публикации в качестве примера, чтобы обобщить стек вызовов:
source/portal/v11Client.js socket.on('publish', function(pubReq,
source/portal/portal.js that.publish =  = function(participantId, streamId, pubInfo) =>
source/portal/rpcRequest.js that.publish = function(controller, participantId, streamId, Options):向Conference Agent的node发起publish RPC
  1. Логика обработки в Conference Agent будет проанализирована позже, вот вопрос, на который нужно сначала ответить: как запланировать несколько порталов WebRTC в кластере. Обратитесь к логике обработки создания токена, которая находится в функции generateToken в management_api/resource/v1/tokenResource.js, где функция requestHandler.schedulePortal вызывается для планирования портала WebRTC. Функция schedulePortal в management_api/requestHandler.js инициирует запланированный RPC для менеджера кластера, предоставляет данные запроса клиента (интернет-провайдер и регион) и запрашивает назначение адреса портала WebRTC.
    1. Стек вызовов:
source/management_api/resource/v1/tokensResource.js generateToken = function(currentRoom, ... =>
source/management_api/resource/v1/tokensResource.js rpc.callRpc(cluster_name, 'schedule', ... =>
source/cluster_manager/scheduler.js that.schedule = function (task, preference, ...
  1. Логика обработки в Cluster Manager будет проанализирована позже.Обработка других событий аналогична.Вы можете обратиться к: OWT Server Signaling Analysis (Part 2) [Open WebRTC Toolkit] , в котором подробно описаны процедуры обработки клиентских и серверных событий.
  2. Анализируется основная логика WebRTC Portal.

4. Агент конференции

  1. Конференц-агент (каталог компонента - conference_agent) представляет собой типичную структуру агент-узел. Поскольку процесс запуска различных Агентов в основном одинаков, эта часть исходного кода является общей. Тип Агента отличается параметром запуска -U ( называется целью в коде).
  2. Процесс подключения агента к серверу RabbitMQ, регистрации в диспетчере кластеров и предоставления услуг RPC в основном такой же, как и в портале WebRTC, но в конце он также инициализирует nodeManager (фактическая реализация управления процессами узла, исходный код в nodeManager.js).
  3. При инициализации nodeManager будут переданы обратные вызовы для добавления и удаления задач и обратные вызовы для аварийного выхода из процесса узла. В этих обратных вызовах будет вызываться соответствующий интерфейс clusterWorker для инициирования RPC к диспетчеру кластера. , то есть регистрировать, добавлять и удалять задачи в Cluster Manager. Все они находятся в ведении clusterWorker, а процесс взаимодействия с Cluster Manager будет расширен позже.
  4. После создания nodeManager процесс узла будет запущен через функцию launchNode. За помещение отвечает процесс-нода, точкой входа процесса-ноды является workNode.js, в процесс-ноду также будет передаваться параметр target. После запуска процесса узла файл .js с тем же именем, что и цель, или index.js в каталоге с таким же именем предоставляет службу RPC процесса узла.Например, служба RPC процесса узла Конференции Агент определяется в файле conference.js.Эти службы могут предоставляться через rpcAPI. Глядя на эту часть кода, не удивляйтесь, если вы не увидите определения rpcAPI.
  5. Теперь резюмируем процесс присоединения к RPC:
source/portal/portal.js that.join = function(participantId, token) =>
source/portal/rpcRequest.js that.join = function(controller, roomId, participant) =>
source/agent/conference/conference.js that.join = function(roomId, participantInfo, callback) =>
source/agent/conference/conference.js  initRoom = function(roomId, origin) => //保存房间信息,创建roomController、accessController
source/agent/conference/conference.js  addParticipant = function(participantInfo, permission) => //保存用户信息
  1. accessController отвечает за логику доступа, такую ​​как обработка сигналов WebRTC и т. д. roomController отвечает за поддержание различных состояний в комнате и управление функциями, такими как поток, отношения подписки и т. д., а также за публикацию (публикацию) , подписка (subscribe), смешанный поток (mix), установка макета смешанного потока (setLayout) и т. д. Существует также много фактической работы по обработке, которая выполняется путем инициирования RPC для узловых процессов других компонентов.
  2. Затем мы суммируем поток обработки публикации RPC:
source/portal/v11Client.js socket.on('publish', function(pubReq,
source/portal/portal.js that.publish =  = function(participantId, streamId, pubInfo) =>
source/portal/rpcRequest.js that.publish = function(controller, participantId, streamId, Options) =>
source/agent/conference/conference.js that.publish = function(participantId, streamId, pubInfo, callback) =>
source/agent/conference/accessController.js that.initiate = function(participantId, sessionId, direction, origin, sessionOptions, ... =>
source/agent/conference/rpcRequest.js that.getWorkerNode = function(clusterManager, purpose, ... => //发起RPC,拿到WebRTC Agent的nodeId 
source/agent/conference/rpcRequest.js that.initiate = function(accessNode, sessionId, ... => //向WebRTC Agent的node发起publish RPC

# WebRTC Agent的RPC服务定义在webrtc/index.js中
source/agent/conference/rpcRequest.js that.initiate = function(accessNode, sessionId, ... => //向WebRTC Agent的node发起publish RPC
source/agent/webrtc/index.js that.publish = function (operationId, connectionType, ... =>
source/agent/webrtc/index.js createWebRTCConnection = function (transportId, ... => //创建WrtcConnection(`webrtc/wrtcConnection.js`),它负责和C++ 代码进行交互

# WebRTC PC连接成功后,会向Conference Agent的node发起onSessionProgress RPC
source/agent/conference/conference.js conference.js  that.onSessionProgress  =>
source/agent/conference/accessController.js  that.onSessionStatus  =>
source/agent/conference/accessController.js accessController.js  onReady  =>
source/agent/conference/conference.js  onSessionEstablished  =>
source/agent/conference/conference.js  addStream  =>
source/agent/conference/roomController.js  that.publish
  1. SDP для публикации и подписки потоков обрабатывается через RPC onSessionSignaling, и этот процесс выглядит следующим образом:
source/portal/rpcRequest.js that.onSessionSignaling  =>
source/agent/conference/conference.js that.onSessionSignaling  =>
source/agent/conference/rtcController.js onClientTransportSignaling =>
source/agent/conference/rpcRequest.js that.onTransportSignaling =>
source/agent/webrtc/index.js that.onTransportSignaling =>
source/agent/webrtc/wrtcConnection.js that.onSignalling

// 其他资料上述显示过程如下:
WebRTC Portal  rpcRequest.js  that.onSessionSignaling  =>
conference.js  that.onSessionSignaling  =>
accessController.js  that.onSessionSignaling:向WebRTC Agent的node发起onSessionSignaling RPC
  1. Наконец, подведем итог процесса подписки RPC:
source/portal/rpcRequest.js that.publish = function(controller, participantId, streamId, Options) =>
source/agent/conference/conference.js that.subscribe = function(controller, participantId, ... =>
source/agent/conference/rtcController.js initiate(ownerId, sessionId, direction, origin, ... =>
source/agent/conference/rpcRequest.js that.getWorkerNode:发起RPC,拿到WebRTC Agent的nodeId
source/agent/conference/rpcRequest.js that.initiate = function(accessNode, sessionId, ... => //向WebRTC Agent的node发起publish RPC

# WebRTC PC连接成功后,会向Conference Agent的node发起onSessionProgress RPC
source/agent/conference/conference.js conference.js  that.onSessionProgress  =>
source/agent/conference/accessController.js that.onSessionStatus  =>
source/agent/conference/accessController.js onReady  =>
source/agent/conference/conference.js onSessionEstablished  =>
source/agent/conference/conference.js addSubscription  =>
source/agent/conference/roomController.js that.subscribe  =>
source/agent/conference/roomController.js spreadStream:如果流的发布和订阅由不同的WebRTC Agent处理,就需要把流从发布Agent扩散到订阅Agent
source/agent/conference/roomController.js linkup(subscribe的内部函数):向WebRTC Agent的node发起linkup RPC
  1. Если требуется потоковое распространение, проанализируйте его отдельно после анализа агента WebRTC. Обработку других RPC можно вывести из одного экземпляра.
  2. Анализируется основная логика режима SFU Агента конференции.

5. Агент WebRTC

  1. Агент WebRTC (каталог компонента — agent/webrtc), который также является типичной структурой агент-узел.
  2. Коды index.js, clusterWorker.js, nodeManager.js и workNode.js являются общими и представляют логику обработки процесса RPC узла.
  3. Поскольку публикацией и подпиской на поток могут заниматься разные Агенты WebRTC, узлу Агента WebRTC может потребоваться не только выполнение передачи данных с клиентом WebRTC, но также может потребоваться выполнение передачи данных с узлами других Агентов ( диффузия потока).
  4. Для этих двух передач определите первый как тип webrtc, а второй — как внутренний тип в коде. В этом разделе сначала рассматривается только тип webrtc, а затем анализируется его в следующем разделе в случае потоковой диффузии.
  5. Служба RPC процесса узла агента WebRTC определена в файле webrtc/index.js. Ниже приведена сводка процесса публикации, onSessionSignaling, подписки и соединения. Аналогичным образом можно проанализировать другие службы RPC.
  6. Первая публикация:
source/agent/conference/rpcRequest.js that.initiate  =>
source/agent/webrtc/index.js that.publish  =>
source/agent/webrtc/index.js createWebRTCConnection:创建WrtcConnection(`webrtc/wrtcConnection.js`),它负责和C++ 代码进行交互
source/agent/connections.js that.addConnection:保存创建的连接对象
  1. При создании WrtcConnection будет выполняться функция initWebRtcConnection из webrtc/wrtcConnection.js, которая будет прослушивать события, отправленные кодом C++ (переведенные webrtc/connection.js в событие status_event), и будет возвращаться к webrtc / Функция notifyStatus файла index.js инициирует RPC onSessionProgress для узла агента конференции.
  2. Затем onSessionSignaling:
source/portal/rpcRequest.js  that.onSessionSignaling  =>
source/agent/conference/conference.js that.onSessionSignaling =>
source/agent/conference/rtcController.js onClientTransportSignaling =>
source/agent/conference/rpcRequest.js that.onTransportSignaling =>
source/agent/webrtc/index.js that.onSessionSignaling =>
source/agent/webrtc/wrtcConnection.js that.onSignalling:把SDP传给C++ 层的代码进行处理
  1. Тогда подпишитесь:
source/portal/portal.js that.subscribe = function(participantId, ... =>
source/portal/rpcRequest.js that.publish = function(controller, participantId, streamId, Options) =>
source/agent/conference/conference.js that.subscribe = function(controller, participantId, ... =>
source/agent/conference/rtcController.js initiate(ownerId, sessionId, direction, origin, ... =>
source/agent/conference/rpcRequest.js that.initiate = function(accessNode, sessionId, ... => //向WebRTC Agent的node发起publish RPC
source/agent/webrtc/index.js that.subscribe = function (operationId, connectionType, ... =>
source/agent/webrtc/index.js createWebRTCConnection = function (transportId, ... => //创建WrtcConnection(`webrtc/wrtcConnection.js`),它负责和C++ 代码进行交互
source/agent/connections.js that.addConnection:保存创建的连接对象
  1. И, наконец, соединение:
source/agent/conference/roomController.js that.subscribe  =>
source/agent/conference/roomController.js spreadStream:如果流的发布和订阅由不同的WebRTC Agent处理,就需要把流从发布Agent扩散到订阅Agent
source/agent/conference/roomController.js linkup(subscribe的内部函数):向WebRTC Agent的node发起linkup RPC
source/agent/webrtc/index.js  that.linkup  =>
source/agent/internalConnectionRouter.js linkup(dstId, from) =>
source/agent/connections.js that.linkupConnection =>
source/agent/webrtc/wrtcConnection.js sender.addDestination =>
source/agent/webrtc/wrtcConnection.js addDestination 调用C++ 接口关联发布端和订阅端
  1. Анализируется основная логика режима SFU WebRTC Агента.

6. Распространение потоков между узлами агента WebRTC

  1. Если публикация и подписка потока обрабатываются разными агентами WebRTC, помните, что узел, который обрабатывает выпуск, — это original_node, а узел, который обрабатывает подписку, — target_node, то есть клиент A публикует поток на original_node, а клиент B подписывается на поток от target_node. Для того, чтобы клиент B успешно получил поток от клиента A, нам нужно распространить (отправить) поток с original_node на target_node, что эквивалентно добавлению подписки на original_node и релиза на target_node.Конечно, эта подписка и релиз на самом деле являются двумя концами одного и того же соединения.
  2. Теперь резюмируем функцию spreadStream агента конференции roomController.js:
    1. В соответствии с записанной информацией о потоке получите nodeId (original_node) издателя потока и nodeId (target_node) подписчика.
    2. Если исходный_узел равен целевому_узлу, публикация и подписка обрабатываются одним и тем же агентом WebRTC, распространение не требуется, в противном случае требуется распространение.
    3. Инициируйте RPC createInternalConnection к target_node с направлением внутрь, то есть добавьте способ публикации на target_node, и инициируйте RPC createInternalConnection к original_node с направлением out, то есть добавьте способ подписки на original_node.
    4. Инициируйте RPC публикации на target_node и инициируйте RPC подписки на original_node. Оба RPC предоставят внутренний адрес подключения однорангового узла.
    5. Инициируйте связывание RPC с original_node и свяжите издателя (клиент A) с подписчиком (внутреннее соединение).
    var spreadStream = function (stream_id, target_node, target_node_type, on_ok, on_error) {
    
    
        log.debug('spreadStream, stream_id:', stream_id,
            'target_node:', target_node, 'target_node_type:', target_node_type);
        if (!streams[stream_id] || !terminals[streams[stream_id].owner]) {
    
    
            return on_error('Cannot spread a non-existing stream');
        }

        const stream_owner = streams[stream_id].owner;
        const original_node = terminals[stream_owner].locality.node;
        const audio = ((streams[stream_id].audio && target_node_type !== 'vmixer' && target_node_type !== 'vxcoder') ? true : false);
        const video = ((streams[stream_id].video && target_node_type !== 'amixer' && target_node_type !== 'axcoder') ? true : false);
        const spread_id = stream_id + '@' + target_node;
        const data = !!streams[stream_id].data;

        if (!audio && !video && !data) {
    
    
            return on_error('Cannot spread stream without audio, video or data.');
        }

        if (original_node === target_node) {
    
    
            log.debug('no need to spread');
            return on_ok();
        }

        const on_spread_start = function () {
    
    
            log.debug('spread start:', spread_id);
            const i = streams[stream_id].spread.findIndex((s) => (s.target === target_node));
            if (i >= 0) {
    
    
              if (streams[stream_id].spread[i].status === 'connected') {
    
    
                log.debug('spread already exists:', spread_id);
                on_ok();
              } else if (streams[stream_id].spread[i].status === 'connecting') {
    
    
                log.debug('spread is connecting:', spread_id);
                streams[stream_id].spread[i].waiting.push({
    
    onOK: on_ok, onError: on_error});
                return true;
              } else {
    
    
                log.error('spread status is ambiguous:', spread_id);
                on_error('spread status is ambiguous');
              }
              return false;
            }
            streams[stream_id].spread.push({
    
    target: target_node, status: 'connecting', waiting: []});
            return true;
        };

        const on_spread_failed = function(reason) {
    
    
            log.error('spreadStream failed, stream_id:', stream_id, 'reason:', reason);
            const i = (streams[stream_id] ? streams[stream_id].spread.findIndex((s) => {
    
    return s.target === target_node;}) : -1);
            if (i > -1) {
    
    
                streams[stream_id].spread[i].waiting.forEach((e) => {
    
    
                  e.onError(reason);
                });
                streams[stream_id].spread.splice(i, 1);
            }
            on_error(reason);
        };

        const on_spread_ok = function () {
    
    
            log.debug('spread ok:', spread_id);
            const i = streams[stream_id].spread.findIndex((s) => {
    
    return s.target === target_node;});
            if (i >= 0) {
    
    
              streams[stream_id].spread[i].status = 'connected';
              process.nextTick(() => {
    
    
                streams[stream_id].spread[i].waiting.forEach((e) => {
    
    
                  e.onOK();
                });
                streams[stream_id].spread[i].waiting = [];
              });
              on_ok();
            } else {
    
    
              on_error('spread record missing');
            }
        }

        if (!on_spread_start()) {
    
    
            return;
        }

        if (['vmixer', 'amixer', 'vxcoder', 'axcoder', 'aselect'].includes(target_node_type)) {
    
    
            const locality = terminals[stream_owner].locality;
            if (!locality.ip || !locality.port) {
    
    
                log.error('No internal address for locality:', locality);
                on_spread_failed('No internal address for locality');
            } else {
    
    
                makeRPC(
                    rpcClient,
                    target_node,
                    'publish',
                    [
                        stream_id,
                        'internal',
                        {
    
    
                            controller: selfRpcId,
                            publisher: (terminals[stream_owner].owner || 'common'),
                            audio: (audio ? {
    
    codec: streams[stream_id].audio.format} : false),
                            video: (video ? {
    
    codec: streams[stream_id].video.format} : false),
                            data: data,
                            ip: locality.ip,
                            port: locality.port,
                        }
                    ],
                    function pubOk() {
    
     on_spread_ok(); },
                    function pubError(e) {
    
     on_spread_failed(e); }
                );
            }
        } else {
    
    
            on_spread_ok();
        }
    };
  1. После выполнения функции spreadStream внутренняя функция linkup подписки инициирует соединение RPC с target_node, чтобы связать издателя (внутреннее соединение) и подписчика (клиент B). На данный момент ссылка от клиента A к original_node, затем к target_node и, наконец, к клиенту B завершена.
  2. Посмотрите на код, относящийся к внутреннему соединению агента WebRTC, сначала создайте:
source/agent/audio/index.js that.createInternalConnection  =>
source/agent/InternalConnectionFactory.js that.create:根据方向,创建InConnection或OutConnection
  1. Для внутреннего соединения TCP и UDP InConnection отвечает за прослушивание, а OutConnection — за соединение.
  2. Отличие в обработке публикации заключается в том, что объект подключения получает ранее созданный InConnection и вызывает его функцию подключения вместо создания WrtcConnection. Разница в обработке подписки заключается в том, что объект подключения получает ранее созданный OutConnection и вызывает его функцию подключения. Отличие обработки компоновки состоит в том, что типы объектов соединения различны, и в конечном итоге выполняются функции addDestination разных классов C++.
    // Create the connection and return the port info
    that.create = function (connId, direction, internalOpt) {
    
    
        // Get internal connection's arguments
        var prot = internalOpt.protocol;
        var minport = internalOpt.minport || 0;
        var maxport = internalOpt.maxport || 0;
        var ticket = internalOpt.ticket;

        if (preparedSet[connId]) {
    
    
            log.warn('Internal Connection already prepared:', connId);
            // FIXME: Correct work flow should not reach here, when a connection
            // is in use, it should not be created again. we should ensure the
            // right call sequence in upper layer.
            return preparedSet[connId].connection.getListeningPort();
        }
        var conn = (direction === 'in')
            ? InConnection(prot, minport, maxport, ticket)
            : OutConnection(prot, minport, maxport, ticket);

        preparedSet[connId] = {
    
    connection: conn, direction: direction};
        return conn.getListeningPort();
    };

7. Менеджер кластера

  1. Наконец, проанализируйте Cluster Manager (каталог компонента — cluster_manager). вопрос:
    1. Процесс регистрации агента в Cluster Manager.
    2. Процесс планирования портала WebRTC.
    3. Процесс получения nodeId Агента конференции порталом WebRTC.
    4. Узел агента конференции получает nodeId агента WebRTC.
    5. Процесс добавления, удаления и выполнения задач.
  2. Прежде всего, в Cluster Manager есть только главный процесс, но он реализует механизм кластера master-slave, и для запуска Cluster Manager можно развернуть несколько машин, которые автоматически выберут хост для обработки RPC-запросов от других модулей и других подчиненных устройств. будет периодически проверять онлайн-статус хоста, как только хост отключится, автоматически будет выбран другой хост. Кластерный механизм режима master-slave и определение службы RPC находятся в clusterManager.js.
  3. Давайте возьмем агента конференции в качестве примера, чтобы увидеть, как агент регистрируется в диспетчере кластера:
# 创建clusterWorker触发
source/common/clusterWorker.js joinCluster  =>
source/common/clusterWorker.js join:向Cluster Manager发起join RPC
source/cluster_manager/clusterManager.js join  =>
source/cluster_manager/clusterManager.js workerJoin  =>
source/cluster_manager/scheduler.js that.add:保存worker进程的信息
  1. Среди них функция workerJoin создаст планировщик (планировщик) по мере необходимости.Каждый воркер (агентный процесс агента, отличающийся параметром цели) будет иметь независимый планировщик, и последующие задачи планирования также будут выполняться этим планировщиком.
  2. Затем посмотрите на процесс планирования WebRTC Portal, например:
source/management_api/resource/v1/tokensResource.js generateToken =>
source/management_api/requestHandler.js exports.schedulePortal  =>
source/management_api/requestHandler.js scheduleAgent('portal', ...  =>
source/cluster_manager/scheduler.js that.schedule:根据调度策略和负载信息,选择出一个worker,回调回去
  1. Функция расписания в scheduler.js сначала проверяет, существует ли запланированная задача: если задача уже существует и назначенный рабочий процесс также существует, он возвращает рабочий процесс напрямую. В противном случае отфильтруйте и запланируйте из доступных рабочих.
  2. Логика фильтрации находится в matcher.js, который в основном учитывает интернет-провайдера, регион и формат обрабатываемых данных.Стратегия планирования находится в Strategy.js, включая пять стратегий: наименьшее количество использованных, наиболее используемых, последних использованных, круговой алгоритм и случайный выбор.
  3. Глядя на код API управления, можно обнаружить, что задача при запросе планирования представляет собой случайное число, поэтому пользователи в одной комнате могут быть запланированы на разные порталы WebRTC.
  4. Затем посмотрите, как WebRTC Portal получает nodeId агента конференции, например:
source/portal/portal.js that.join
source/portal/rpcRequest.js that.getController  =>
source/cluster_manager/scheduler.js that.schedule => //拿到Conference Agent的worker进程id
source/agent/index.js (rpcAPI)getNode: function(task, callback) =>
source/agent/nodeManager.js that.getNode => 根据房间号查找已启动的node,或启动新的node
source/agent/index.js (rpcAPI)getNode: function(task, callback) 把Conference Agent的nodeId回调回去
  1. Когда портал WebRTC запрашивает расписание, задачей является номер комнаты, поэтому все пользователи в одной комнате будут назначены одному и тому же агенту конференции. В процессе назначения узлов агентом также учитывается номер комнаты, поэтому все пользователи в одной комнате обрабатываются одним и тем же процессом узла агента конференции и могут взаимодействовать друг с другом.
  2. Затем мы смотрим на процесс получения nodeId агента конференции nodeId агента WebRTC:
source/agent/conference/conference.js that.publish = function(participantId, streamId, pubInfo, callback) =>
source/agent/conference/accessController.js that.initiate = function(participantId, sessionId, direction, origin, sessionOptions, ... =>
source/agent/conference/rpcRequest.js that.getWorkerNode = function(clusterManager, purpose, ... => //发起RPC,拿到WebRTC Agent的nodeId 
source/cluster_manager/scheduler.js that.schedule => //拿到WebRTC Agent的worker进程id
source/agent/conference/rpcRequest.js //回到that.getWorkerNode,向WebRTC Agent的worker进程发起getNode RPC
source/agent/index.js (rpcAPI)getNode: function(task, callback) =>
source/agent/nodeManager.js that.getNode => 根据房间号查找已启动的node,或启动新的node
source/agent/index.js (rpcAPI)getNode: function(task, callback) 把WebRTC Agent的nodeId回调回去
  1. Задача, когда агент конференции запрашивает расписание, публикация использует идентификатор потока, а подписка использует идентификатор подписки.Эти два идентификатора генерируются случайным образом на портале WebRTC, поэтому публикация и подписка на поток могут быть запланированы для разных агентов WebRTC. Если это запланировано для разных агентов WebRTC, данные будут перенаправлены через механизм распространения потока, проанализированный выше. Если они запланированы для одного и того же агента WebRTC, они будут назначены одному и тому же процессу узла, поскольку при назначении узлов учитывается номер комнаты.
  2. Наконец, возьмите Conference Agent в качестве примера, чтобы увидеть процесс добавления, удаления и выполнения задач. Обратите внимание, что следующий процесс на самом деле является не процессом обработки фактической задачи, а процессом отслеживания задачи Диспетчером кластеров.
  3. Добавьте задачи:

source/agent/nodeManager.js that.getNode  =>
source/agent/nodeManager.js addTask = function(nodeId, task)  =>
index.js创建nodeManager时提供的onTaskAdded回调  =>
source/common/clusterWorker.js that.addTask  =>
source/common/clusterWorker.js pickUpTasks = function (taskList)  =>
source/cluster_manager/clusterManager.js pickUpTasks = function (worker, tasks)  =>
source/cluster_manager/scheduler.js that.pickUpTasks  =>
source/cluster_manager/scheduler.js executeTask = function (worker, task):把task添加到worker的tasks数组中,表示worker开始执行这个task
  1. Удалить задачи:
source/agent/nodeManager.js  that.recycleNode  =>
source/agent/nodeManager.js  removeTask = function(nodeId, task, ...  =>
index.js创建nodeManager时提供的onTaskRemoved回调  =>
source/common/clusterWorker.js that.removeTask  =>
source/common/clusterWorker.js layDownTask = function (worker, task) =>
source/cluster_manager/clusterManager.js layDownTask  =>
source/cluster_manager/scheduler.js that.layDownTask  =>
source/cluster_manager/scheduler.js cancelExecution:把task从worker的tasks数组中移除,表示worker结束执行这个task
  1. Фактическое выполнение задачи не имеет ничего общего с Cluster Manager и запускается вызывающей стороной, непосредственно инициирующей RPC на узле. Перед инициацией RPC nodeId будет получен через RPC getNode, который запускает процесс добавления задач. После того, как задача будет обработана, RPC recycleNode будет инициирован для освобождения узла, тем самым запуская процесс удаления задачи.

8. Краткое описание всего процесса

  1. Блок-схема суммирует описанный выше полный процесс, блок-схема OWT Server SFU:
    изображение.png
  2. Значения чисел на соединительных линиях на рисунке:
    1. Клиент получает токен.
      1. Клиент отправляет HTTP-запрос POST в Management API.
      2. API управления инициирует запланированный RPC для менеджера кластера, чтобы запросить планирование портала WebRTC.
    2. Логин клиента.
      1. Клиент отправляет событие входа SocketIO на портал WebRTC.
      2. Портал WebRTC инициирует планирование RPC для менеджера кластера, запрашивая расписание агента конференции.
      3. Портал WebRTC инициирует присоединение RPC к агенту конференции (узлу).
    3. публиковать.
      1. Издатель отправляет событие публикации SocketIO на портал WebRTC.
      2. Портал WebRTC инициирует публикацию RPC для агента конференции (узла).
      3. Агент конференции инициирует плановый RPC для менеджера кластера, чтобы запросить планирование агента WebRTC.
      4. Агент конференции инициирует публикацию RPC для агента WebRTC (узла).
      5. Издатель и агент WebRTC (узел) передают медиаданные через ПК.
    4. подписаться.
      1. подписчик отправляет событие подписки SocketIO на портал WebRTC.
      2. Портал WebRTC инициирует подписку RPC на агент конференции (узел).
      3. Агент конференции инициирует плановый RPC для менеджера кластера, чтобы запросить планирование агента WebRTC.
      4. Агент конференции инициирует подписку RPC на агент WebRTC (узел).
      5. Абонент и агент WebRTC (узел) передают медиаданные через ПК.

Ссылка:
«Практический бой WebRTC Native Development»
Общий анализ архитектуры сервера OWT Схема инфраструктуры в

Supongo que te gusta

Origin blog.csdn.net/weixin_41910694/article/details/127812184
Recomendado
Clasificación