Media soup源码分析(一)信令的传输过程

    技术2022-07-11  126

    正文目录

    JS部分1、先看整体2、展开Worker3、constructor4、channel的建立5、JS部分的总结 C++部分1、main流程图2、main.cpp3、Channel Socket的建立4、UnixStreamSocket.cpp5、consumerSocket6、UnixStreamSocket7、onRead8、OnUvRead9、UserOnUnixStreamRead10、OnConsumerSocketMessage11、Request12、OnConsumerSocketMessage13、OnChannelRequest

    JS部分

    JS部分的重点是Worker.js

    1、先看整体

    从Worker.JS这个文件来看,上面构造了一些变量,如process、channel等,下面是一个Worker的类

    2、展开Worker

    展开Worker类可以看见它有11个函数,分别是

    1.constructor()——构造函数 2.get pid()——获得Worker进程的ID 3.get closed()——确认Worker是否关闭 4.get appData()——返回custom的数据 5.set appData()——当设置无效时抛出异常信息 6.get observer()——开启观察者模式 7.close()——关闭Worker 8.async dump()——转存Worker 9.async getResourceUsage()——获得worker进程资源使用信息 10.async updateSettings()——更新设置 11.async createRouter()——创建房间

    3、constructor

    我们的重点是channel怎么建立的,所以重点关注第1个构造函数constructor()

    this._child = child_process_1.spawn( // command spawnBin, // args spawnArgs, // options { env: { MEDIASOUP_VERSION: '3.6.7' }, detached: false, // fd 0 (stdin) : Just ignore it. // fd 1 (stdout) : Pipe it for 3rd libraries that log their own stuff. // fd 2 (stderr) : Same as stdout. // fd 3 (channel) : Producer Channel fd. // fd 4 (channel) : Consumer Channel fd. // fd 5 (channel) : Producer PayloadChannel fd. // fd 6 (channel) : Consumer PayloadChannel fd. stdio: ['ignore', 'pipe', 'pipe', 'pipe', 'pipe', 'pipe', 'pipe'], windowsHide: true }); this._pid = this._child.pid; this._channel = new Channel_1.Channel({ producerSocket: this._child.stdio[3], consumerSocket: this._child.stdio[4], pid: this._pid }); this._payloadChannel = new PayloadChannel_1.PayloadChannel({ // NOTE: TypeScript does not like more than 5 fds. // @ts-ignore producerSocket: this._child.stdio[5], // @ts-ignore consumerSocket: this._child.stdio[6] });

    spawn里标准io口有7个参数,分别是标准输入、标准输出、标准错误、以及4个通道,源码中对标准输入规定的是ignore,其它6个参数是pipe(管道),这里要注意的是,这个管道并不是Linux进程间通信的匿名管道或有名管道,它是UnixSocketPair,因为只有UnixSocketPair才是全双工通信,从代码中我们也能看出它是全双工的,而匿名(有名)管道是半双工通信

    接着重点是 this._channel = new Channel_1.Channel ,它创建了一个channel,并传入了3个参数,分别是stdio[3]和stdio[4],以及pid(因为可能会有多个Worker,而一个进程对应一个Worker,所以需要知道每个进程的ID),这样通过这个channel,JS部分便能和C++部分通信了

    4、channel的建立

    创建这个channel会调用channel.js里的构造函数,我们来看看channel.js里的代码

    this._consumerSocket.on('data', (buffer) => { if (!this._recvBuffer) { this._recvBuffer = buffer; } else { this._recvBuffer = Buffer.concat([this._recvBuffer, buffer], this._recvBuffer.length + buffer.length); } if (this._recvBuffer.length > NS_PAYLOAD_MAX_LEN) { logger.error('receiving buffer is full, discarding all data into it'); // Reset the buffer and exit. this._recvBuffer = undefined; return; } while (true) // eslint-disable-line no-constant-condition { let nsPayload; try { nsPayload = netstring.nsPayload(this._recvBuffer); } catch (error) { logger.error('invalid netstring data received from the worker process: %s', String(error)); // Reset the buffer and exit. this._recvBuffer = undefined; return; } // Incomplete netstring message. if (nsPayload === -1) return; try { // We can receive JSON messages (Channel messages) or log strings. switch (nsPayload[0]) { // 123 = '{' (a Channel JSON messsage). case 123: this._processMessage(JSON.parse(nsPayload.toString('utf8'))); break; // 68 = 'D' (a debug log). case 68: logger.debug(`[pid:${pid}] ${nsPayload.toString('utf8', 1)}`); break; // 87 = 'W' (a warn log). case 87: logger.warn(`[pid:${pid}] ${nsPayload.toString('utf8', 1)}`); break; // 69 = 'E' (an error log). case 69: logger.error(`[pid:${pid} ${nsPayload.toString('utf8', 1)}`); break; // 88 = 'X' (a dump log). case 88: // eslint-disable-next-line no-console console.log(nsPayload.toString('utf8', 1)); break; default: // eslint-disable-next-line no-console console.warn(`worker[pid:${pid}] unexpected data: %s`, nsPayload.toString('utf8', 1)); } } catch (error) { logger.error('received invalid message from the worker process: %s', String(error)); } // Remove the read payload from the buffer. this._recvBuffer = this._recvBuffer.slice(netstring.nsLength(this._recvBuffer)); if (!this._recvBuffer.length) { this._recvBuffer = undefined; return; } } });

    在channel.js中,上面这片代码启动了Socket的侦听函数,当C++传来数据时,会促发recvBuffer接收数据,然后进入while循环处理数据,做出相应的处理

    5、JS部分的总结

    经过上述步骤后,便拿到了SocketPair一端的文件描述符,实际不管是Socket还是普通文件,都是通过文件描述符的方式来进行的操作

    C++部分

    1、main流程图

    main主函数部分的模块可分为下图的几个步骤,在最新版V3的源码中,main创建了两个Socket,分别是Channel和PayloadChannel

    2、main.cpp

    #define MS_CLASS "mediasoup-worker" // #define MS_LOG_DEV_LEVEL 3 #include "common.hpp" #include "DepLibSRTP.hpp" #include "DepLibUV.hpp" #include "DepLibWebRTC.hpp" #include "DepOpenSSL.hpp" #include "DepUsrSCTP.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "Settings.hpp" #include "Utils.hpp" #include "Worker.hpp" #include "Channel/Notifier.hpp" #include "Channel/UnixStreamSocket.hpp" #include "PayloadChannel/Notifier.hpp" #include "PayloadChannel/UnixStreamSocket.hpp" #include "RTC/DtlsTransport.hpp" #include "RTC/SrtpSession.hpp" #include <uv.h> #include <cerrno> #include <csignal> // sigaction() #include <cstdlib> // std::_Exit(), std::genenv() #include <iostream> // std::cerr, std::endl #include <map> #include <string> static constexpr int ConsumerChannelFd{ 3 }; static constexpr int ProducerChannelFd{ 4 }; static constexpr int PayloadConsumerChannelFd{ 5 }; static constexpr int PayloadProducerChannelFd{ 6 }; void IgnoreSignals(); int main(int argc, char* argv[]) { // Ensure we are called by our Node library. if (std::getenv("MEDIASOUP_VERSION") == nullptr) { MS_ERROR_STD("you don't seem to be my real father!"); std::_Exit(EXIT_FAILURE); } std::string version = std::getenv("MEDIASOUP_VERSION"); // Initialize libuv stuff (we need it for the Channel). DepLibUV::ClassInit(); // Channel socket (it will be handled and deleted by the Worker). Channel::UnixStreamSocket* channel{ nullptr }; // PayloadChannel socket (it will be handled and deleted by the Worker). PayloadChannel::UnixStreamSocket* payloadChannel{ nullptr }; try { channel = new Channel::UnixStreamSocket(ConsumerChannelFd, ProducerChannelFd); } catch (const MediaSoupError& error) { MS_ERROR_STD("error creating the Channel: %s", error.what()); std::_Exit(EXIT_FAILURE); } try { payloadChannel = new PayloadChannel::UnixStreamSocket(PayloadConsumerChannelFd, PayloadProducerChannelFd); } catch (const MediaSoupError& error) { MS_ERROR_STD("error creating the RTC Channel: %s", error.what()); std::_Exit(EXIT_FAILURE); } // Initialize the Logger. Logger::ClassInit(channel); try { Settings::SetConfiguration(argc, argv); } catch (const MediaSoupTypeError& error) { MS_ERROR_STD("settings error: %s", error.what()); // 42 is a custom exit code to notify "settings error" to the Node library. std::_Exit(42); } catch (const MediaSoupError& error) { MS_ERROR_STD("unexpected settings error: %s", error.what()); std::_Exit(EXIT_FAILURE); } MS_DEBUG_TAG(info, "starting mediasoup-worker process [version:%s]", version.c_str()); #if defined(MS_LITTLE_ENDIAN) MS_DEBUG_TAG(info, "little-endian CPU detected"); #elif defined(MS_BIG_ENDIAN) MS_DEBUG_TAG(info, "big-endian CPU detected"); #else MS_WARN_TAG(info, "cannot determine whether little-endian or big-endian"); #endif #if defined(INTPTR_MAX) && defined(INT32_MAX) && (INTPTR_MAX == INT32_MAX) MS_DEBUG_TAG(info, "32 bits architecture detected"); #elif defined(INTPTR_MAX) && defined(INT64_MAX) && (INTPTR_MAX == INT64_MAX) MS_DEBUG_TAG(info, "64 bits architecture detected"); #else MS_WARN_TAG(info, "cannot determine 32 or 64 bits architecture"); #endif Settings::PrintConfiguration(); DepLibUV::PrintVersion(); try { // Initialize static stuff. DepOpenSSL::ClassInit(); DepLibSRTP::ClassInit(); DepUsrSCTP::ClassInit(); DepLibWebRTC::ClassInit(); Utils::Crypto::ClassInit(); RTC::DtlsTransport::ClassInit(); RTC::SrtpSession::ClassInit(); Channel::Notifier::ClassInit(channel); PayloadChannel::Notifier::ClassInit(payloadChannel); // Ignore some signals. IgnoreSignals(); // Run the Worker. Worker worker(channel, payloadChannel); // Free static stuff. DepLibUV::ClassDestroy(); DepLibSRTP::ClassDestroy(); Utils::Crypto::ClassDestroy(); DepLibWebRTC::ClassDestroy(); RTC::DtlsTransport::ClassDestroy(); DepUsrSCTP::ClassDestroy(); // Wait a bit so peding messages to stdout/Channel arrive to the Node // process. uv_sleep(200); std::_Exit(EXIT_SUCCESS); } catch (const MediaSoupError& error) { MS_ERROR_STD("failure exit: %s", error.what()); std::_Exit(EXIT_FAILURE); } } void IgnoreSignals() { #ifndef _WIN32 MS_TRACE(); int err; struct sigaction act; // NOLINT(cppcoreguidelines-pro-type-member-init) // clang-format off std::map<std::string, int> ignoredSignals = { { "PIPE", SIGPIPE }, { "HUP", SIGHUP }, { "ALRM", SIGALRM }, { "USR1", SIGUSR1 }, { "USR2", SIGUSR2 } }; // clang-format on act.sa_handler = SIG_IGN; // NOLINT(cppcoreguidelines-pro-type-cstyle-cast) act.sa_flags = 0; err = sigfillset(&act.sa_mask); if (err != 0) MS_THROW_ERROR("sigfillset() failed: %s", std::strerror(errno)); for (auto& kv : ignoredSignals) { auto& sigName = kv.first; int sigId = kv.second; err = sigaction(sigId, &act, nullptr); if (err != 0) MS_THROW_ERROR("sigaction() failed for signal %s: %s", sigName.c_str(), std::strerror(errno)); } #endif }

    3、Channel Socket的建立

    提取socket这部分的代码,可以看见源码中创建了2个socket,分别是Channel和PayloadChannel

    然后在try{}catch{}表达式中,分别对两个socket对象实例化,这里要注意的是,new中传入的参数Fd,对照JS部分代码的注释可以知道这是固定的3456,而非标准输入0、输出1、错误2

    // Channel socket (it will be handled and deleted by the Worker). Channel::UnixStreamSocket* channel{ nullptr }; // PayloadChannel socket (it will be handled and deleted by the Worker). PayloadChannel::UnixStreamSocket* payloadChannel{ nullptr }; try { channel = new Channel::UnixStreamSocket(ConsumerChannelFd, ProducerChannelFd); } catch (const MediaSoupError& error) { MS_ERROR_STD("error creating the Channel: %s", error.what()); std::_Exit(EXIT_FAILURE); } try { payloadChannel = new PayloadChannel::UnixStreamSocket(PayloadConsumerChannelFd, PayloadProducerChannelFd); } catch (const MediaSoupError& error) { MS_ERROR_STD("error creating the RTC Channel: %s", error.what()); std::_Exit(EXIT_FAILURE); }

    4、UnixStreamSocket.cpp

    上述 socket 在 new 之后会跳转到 UnixStreamSocket.cpp 执行下面这个函数

    UnixStreamSocket::UnixStreamSocket(int consumerFd, int producerFd) : consumerSocket(consumerFd, NsMessageMaxLen, this), producerSocket(producerFd, NsMessageMaxLen) { MS_TRACE_STD(); }

    这个函数的关键不是函数体里的内容,而是在参数后面又创建了一个consumerScoket和producerSocket,并传入参数Fd和消息最大长度

    5、consumerSocket

    接着再进入consumerSocket,可以看到在这个构造函数后面对其父类进行初始化,传入参数fd,缓冲区大小,以及角色

    ConsumerSocket::ConsumerSocket(int fd, size_t bufferSize, Listener* listener) : ::UnixStreamSocket(fd, bufferSize, ::UnixStreamSocket::Role::CONSUMER), listener(listener) { MS_TRACE_STD(); }

    6、UnixStreamSocket

    继续追根溯源,进入UnixStreamSocket的构造函数

    在这部分代码中先构造了一个uv_pipe_t的对象,这个是libuv库中的pipe,赋值给uvHandle。然后把对象指针this赋值给私有定义的data

    接着是对uv_pipe的初始化,有3个参数,分别是

    事件循环中的loop刚才创建的对象ipc,用于指示pipe是否被用于两个进程之间

    初始化结束后,调用uv_pipe_open打开fd所指向的pipe

    打开之后进入uv_read_start,这个函数是用来启动读的操作,它同样也有三个参数:

    uvHandle,里面存放的是这个对象本身onAlloc,当buffer不足时回调这个函数,便能重新创建一个bufferonRead,接收pipe的另一端发送的数据 UnixStreamSocket::UnixStreamSocket(int fd, size_t bufferSize, UnixStreamSocket::Role role) : bufferSize(bufferSize), role(role) { MS_TRACE_STD(); int err; this->uvHandle = new uv_pipe_t; this->uvHandle->data = static_cast<void*>(this); err = uv_pipe_init(DepLibUV::GetLoop(), this->uvHandle, 0); if (err != 0) { delete this->uvHandle; this->uvHandle = nullptr; MS_THROW_ERROR_STD("uv_pipe_init() failed: %s", uv_strerror(err)); } err = uv_pipe_open(this->uvHandle, fd); if (err != 0) { uv_close(reinterpret_cast<uv_handle_t*>(this->uvHandle), static_cast<uv_close_cb>(onClose)); MS_THROW_ERROR_STD("uv_pipe_open() failed: %s", uv_strerror(err)); } if (this->role == UnixStreamSocket::Role::CONSUMER) { // Start reading. err = uv_read_start( reinterpret_cast<uv_stream_t*>(this->uvHandle), static_cast<uv_alloc_cb>(onAlloc), static_cast<uv_read_cb>(onRead)); if (err != 0) { uv_close(reinterpret_cast<uv_handle_t*>(this->uvHandle), static_cast<uv_close_cb>(onClose)); MS_THROW_ERROR_STD("uv_read_start() failed: %s", uv_strerror(err)); } } // NOTE: Don't allocate the buffer here. Instead wait for the first uv_alloc_cb(). }

    7、onRead

    重点是这个onRead函数,接下来看这部分的代码

    onRead有三个参数,分别是:

    handle,刚才创建的对象nread,读取的数据大小buf,存放数据的地方

    在函数体里,因为要访问到另一端JS传过来的数据,所以得使用static全局静态函数。具体怎么做呢?首先对handle->data做了强制类型转换,拿到对象里的socket,这样便可在对象里进行操作,最后再调用socket->OnUvRead这个方法

    inline static void onRead(uv_stream_t* handle, ssize_t nread, const uv_buf_t* buf) { auto* socket = static_cast<UnixStreamSocket*>(handle->data); if (socket) socket->OnUvRead(nread, buf); }

    8、OnUvRead

    我们再进入OnUvRead这个函数

    在这个函数中首先对nread做了一些判断,如果数据不为空则调用UserOnUnixStreamRead(),注意看它的注释 //Notify the subclass 通知子类

    inline void UnixStreamSocket::OnUvRead(ssize_t nread, const uv_buf_t* /*buf*/) { MS_TRACE_STD(); if (nread == 0) return; // Data received. if (nread > 0) { // Update the buffer data length. this->bufferDataLen += static_cast<size_t>(nread); // Notify the subclass. UserOnUnixStreamRead(); } // Peer disconnected. else if (nread == UV_EOF || nread == UV_ECONNRESET) { this->isClosedByPeer = true; // Close local side of the pipe. Close(); // Notify the subclass. UserOnUnixStreamSocketClosed(); } // Some error. else { MS_ERROR_STD("read error, closing the pipe: %s", uv_strerror(nread)); this->hasError = true; // Close the socket. Close(); // Notify the subclass. UserOnUnixStreamSocketClosed(); } }

    9、UserOnUnixStreamRead

    进入子类函数,先是netstring_read()函数进行字符串读取,返回0

    接着做一个判断,若 nsRet !=0 说明出错,没有读取到数据,后面的switch都是在做错误类型的判断

    如果没有出错的话,会先计算读取的字符串,然后再调用OnConsumerSocketMessage()这个函数

    void ConsumerSocket::UserOnUnixStreamRead() { MS_TRACE_STD(); // Be ready to parse more than a single message in a single chunk. while (true) { if (IsClosed()) return; size_t readLen = this->bufferDataLen - this->msgStart; char* msgStart = nullptr; size_t msgLen; int nsRet = netstring_read( reinterpret_cast<char*>(this->buffer + this->msgStart), readLen, &msgStart, &msgLen); if (nsRet != 0) { switch (nsRet) { case NETSTRING_ERROR_TOO_SHORT: { // Check if the buffer is full. if (this->bufferDataLen == this->bufferSize) { // First case: the incomplete message does not begin at position 0 of // the buffer, so move the incomplete message to the position 0. if (this->msgStart != 0) { std::memmove(this->buffer, this->buffer + this->msgStart, readLen); this->msgStart = 0; this->bufferDataLen = readLen; } // Second case: the incomplete message begins at position 0 of the buffer. // The message is too big, so discard it. else { MS_ERROR_STD( "no more space in the buffer for the unfinished message being parsed, " "discarding it"); this->msgStart = 0; this->bufferDataLen = 0; } } // Otherwise the buffer is not full, just wait. return; } case NETSTRING_ERROR_TOO_LONG: { MS_ERROR_STD("NETSTRING_ERROR_TOO_LONG"); break; } case NETSTRING_ERROR_NO_COLON: { MS_ERROR_STD("NETSTRING_ERROR_NO_COLON"); break; } case NETSTRING_ERROR_NO_COMMA: { MS_ERROR_STD("NETSTRING_ERROR_NO_COMMA"); break; } case NETSTRING_ERROR_LEADING_ZERO: { MS_ERROR_STD("NETSTRING_ERROR_LEADING_ZERO"); break; } case NETSTRING_ERROR_NO_LENGTH: { MS_ERROR_STD("NETSTRING_ERROR_NO_LENGTH"); break; } } // Error, so reset and exit the parsing loop. this->msgStart = 0; this->bufferDataLen = 0; return; } // If here it means that msgStart points to the beginning of a message // with msgLen bytes length, so recalculate readLen. readLen = reinterpret_cast<const uint8_t*>(msgStart) - (this->buffer + this->msgStart) + msgLen + 1; this->listener->OnConsumerSocketMessage(this, msgStart, msgLen); // If there is no more space available in the buffer and that is because // the latest parsed message filled it, then empty the full buffer. if ((this->msgStart + readLen) == this->bufferSize) { this->msgStart = 0; this->bufferDataLen = 0; } // If there is still space in the buffer, set the beginning of the next // parsing to the next position after the parsed message. else { this->msgStart += readLen; } // If there is more data in the buffer after the parsed message // then parse again. Otherwise break here and wait for more data. if (this->bufferDataLen > this->msgStart) { continue; } break; } }

    10、OnConsumerSocketMessage

    进入OnConsumerSocketMessage()这个函数

    JS在传输之前会先把数据做成json的格式,然后以字符串的形式传输过来,C++收到字符串后,会把它转化为json对象

    又调用Channel::Request,传入这个json对象

    void UnixStreamSocket::OnConsumerSocketMessage( ConsumerSocket* /*consumerSocket*/, char* msg, size_t msgLen) { MS_TRACE_STD(); try { json jsonMessage = json::parse(msg, msg + msgLen); auto* request = new Channel::Request(this, jsonMessage); // Notify the listener. try { this->listener->OnChannelRequest(this, request); } catch (const MediaSoupTypeError& error) { request->TypeError(error.what()); } catch (const MediaSoupError& error) { request->Error(error.what()); } // Delete the Request. delete request; } catch (const json::parse_error& error) { MS_ERROR_STD("JSON parsing error: %s", error.what()); } catch (const MediaSoupError& error) { MS_ERROR_STD("discarding wrong Channel request"); } }

    11、Request

    进入Channel::Request

    可以看到这个json里面封装的是一个四元组,其包含:

    id——方法的idmethod——字符串的名字internal——自定义的内部格式data——传入的数据(可能有可能没有)

    解析完数据之后,就把它们放入Request对象中的各个数据域 this->id、this->method、this->internal、this->data

    Request::Request(Channel::UnixStreamSocket* channel, json& jsonRequest) : channel(channel) { MS_TRACE(); auto jsonIdIt = jsonRequest.find("id"); if (jsonIdIt == jsonRequest.end() || !Utils::Json::IsPositiveInteger(*jsonIdIt)) MS_THROW_ERROR("missing id"); this->id = jsonIdIt->get<uint32_t>(); auto jsonMethodIt = jsonRequest.find("method"); if (jsonMethodIt == jsonRequest.end() || !jsonMethodIt->is_string()) MS_THROW_ERROR("missing method"); this->method = jsonMethodIt->get<std::string>(); auto methodIdIt = Request::string2MethodId.find(this->method); if (methodIdIt == Request::string2MethodId.end()) { Error("unknown method"); MS_THROW_ERROR("unknown method '%s'", this->method.c_str()); } this->methodId = methodIdIt->second; auto jsonInternalIt = jsonRequest.find("internal"); if (jsonInternalIt != jsonRequest.end() && jsonInternalIt->is_object()) this->internal = *jsonInternalIt; else this->internal = json::object(); auto jsonDataIt = jsonRequest.find("data"); if (jsonDataIt != jsonRequest.end() && jsonDataIt->is_object()) this->data = *jsonDataIt; else this->data = json::object(); }

    12、OnConsumerSocketMessage

    完成上述步骤后,便又返回OnConsumerSocketMessage()

    因为之前已经把数据转存到Request中,所以可以直接对其进行操作,这时候调用

    this->listener->OnChannelRequest(this, request);

    要注意的是,这里的listener实际上是Worker

    13、OnChannelRequest

    当Worker接收到Request的数据后,便能做出相应的处理了,接下来进入Worker.cpp的OnChannelRequest()

    switch里就会根据methodId做出相应的处理,当它为

    WORKER_DUMP,表示将Worker中的Router信息都打印出来WORKER_GET_RESOURCE_USAGE,表示将RU的参数信息打印出来WORKER_UPDATE_SETTINGS,表示更新设置WORKER_CREATE_ROUTER,表示创建RouterROUTER_CLOSE,表示关闭

    如果为其他的,就跳转到router相关的处理函数中,若router处理不了,就再往下传,一层一层传下去

    inline void Worker::OnChannelRequest(Channel::UnixStreamSocket* /*channel*/, Channel::Request* request) { MS_TRACE(); MS_DEBUG_DEV( "Channel request received [method:%s, id:%" PRIu32 "]", request->method.c_str(), request->id); switch (request->methodId) { case Channel::Request::MethodId::WORKER_DUMP: { json data = json::object(); FillJson(data); request->Accept(data); break; } case Channel::Request::MethodId::WORKER_GET_RESOURCE_USAGE: { json data = json::object(); FillJsonResourceUsage(data); request->Accept(data); break; } case Channel::Request::MethodId::WORKER_UPDATE_SETTINGS: { Settings::HandleRequest(request); break; } case Channel::Request::MethodId::WORKER_CREATE_ROUTER: { std::string routerId; // This may throw. SetNewRouterIdFromInternal(request->internal, routerId); auto* router = new RTC::Router(routerId); this->mapRouters[routerId] = router; MS_DEBUG_DEV("Router created [routerId:%s]", routerId.c_str()); request->Accept(); break; } case Channel::Request::MethodId::ROUTER_CLOSE: { // This may throw. RTC::Router* router = GetRouterFromInternal(request->internal); // Remove it from the map and delete it. this->mapRouters.erase(router->id); delete router; MS_DEBUG_DEV("Router closed [id:%s]", router->id.c_str()); request->Accept(); break; } // Any other request must be delivered to the corresponding Router. default: { // This may throw. RTC::Router* router = GetRouterFromInternal(request->internal); router->HandleRequest(request); break; } } }
    Processed: 0.012, SQL: 9