【MediaSoup---源码篇】(五)接收RTP数据的处理

通过前面的文章我们可以了解到,当创建好Transport的时候,socket已经建立好。具备了相应的网络传输能力。我们来看一下socket接收到数据是如何处理的。

UdpSocketHandler::OnUvRecv

Socket接收数据

inline void UdpSocketHandler::OnUvRecv(ssize_t nread, const uv_buf_t* buf, const struct sockaddr* addr, unsigned int flags)
{MS_TRACE();// NOTE: Ignore if there is nothing to read or if it was an empty datagram.if (nread == 0)return;// Check flags.if ((flags & UV_UDP_PARTIAL) != 0u){MS_ERROR("received datagram was truncated due to insufficient buffer, ignoring it");return;}// Data received.if (nread > 0){// Update received bytes.更新接收字节。this->recvBytes += nread;// Notify the subclass.通知子类。UdpSocket 是其子类UserOnUdpDatagramReceived(reinterpret_cast<uint8_t*>(buf->base), nread, addr);}// Some error.else{MS_DEBUG_DEV("read error: %s", uv_strerror(nread));}
}
UserOnUdpDatagramReceived

具体由UdpSocket其子类实现,其中listener是在创建transport创建时的具体transport

	void UdpSocket::UserOnUdpDatagramReceived(const uint8_t* data, size_t len, const struct sockaddr* addr){MS_TRACE();if (!this->listener){MS_ERROR("no listener set");return;}// Notify the reader.通知读者。this->listener->OnUdpSocketPacketReceived(this, data, len, addr);}
OnUdpSocketPacketReceived

以PlainTransport为例

    //从udpsocket获得了接收数据inline void PlainTransport::OnUdpSocketPacketReceived(RTC::UdpSocket* socket, const uint8_t* data, size_t len, const struct sockaddr* remoteAddr){MS_TRACE();//形成元组,记录IP等内容RTC::TransportTuple tuple(socket, remoteAddr);//进入到当前transport处理OnPacketReceived(&tuple, data, len);}
PlainTransport::OnPacketReceived
	inline void PlainTransport::OnPacketReceived(RTC::TransportTuple* tuple, const uint8_t* data, size_t len){MS_TRACE();// Increase receive transmission.增加接收传输。RTC::Transport::DataReceived(len);// Check if it's RTCP.检查它是否是RTCP。if (RTC::RTCP::Packet::IsRtcp(data, len)){OnRtcpDataReceived(tuple, data, len);}// Check if it's RTP.检查它是否是RTP。else if (RTC::RtpPacket::IsRtp(data, len)){OnRtpDataReceived(tuple, data, len);}// Check if it's SCTP.检查它是否是SCTP。else if (RTC::SctpAssociation::IsSctp(data, len)){OnSctpDataReceived(tuple, data, len);}else{MS_WARN_DEV("ignoring received packet of unknown type");}}
RTP数据处理方式

首先来处理是不是加密的RTP数据;然后根据既定格式重构RTP数据为Packet;最后透传整理好的packet到上层Transport

	inline void PlainTransport::OnRtpDataReceived(RTC::TransportTuple* tuple, const uint8_t* data, size_t len){MS_TRACE();if (HasSrtp() && !IsSrtpReady())return;// Decrypt the SRTP packet.解密SRTP报文。auto intLen = static_cast<int>(len);if (HasSrtp() && !this->srtpRecvSession->DecryptSrtp(const_cast<uint8_t*>(data), &intLen)){RTC::RtpPacket* packet = RTC::RtpPacket::Parse(data, static_cast<size_t>(intLen));if (!packet){MS_WARN_TAG(srtp, "DecryptSrtp() failed due to an invalid RTP packet");}else{MS_WARN_TAG(srtp,"DecryptSrtp() failed [ssrc:%" PRIu32 ", payloadType:%" PRIu8 ", seq:%" PRIu16 "]",packet->GetSsrc(),packet->GetPayloadType(),packet->GetSequenceNumber());delete packet;}return;}//解析socket数据,获取格式化后的RtpPacketRTC::RtpPacket* packet = RTC::RtpPacket::Parse(data, static_cast<size_t>(intLen));if (!packet){MS_WARN_TAG(rtp, "received data is not a valid RTP packet");return;}// If we don't have a RTP tuple yet, check whether comedia mode is set.if (!this->tuple){if (!this->comedia){MS_DEBUG_TAG(rtp, "ignoring RTP packet while not connected");// Remove this SSRC.RecvStreamClosed(packet->GetSsrc());delete packet;return;}MS_DEBUG_TAG(rtp, "setting RTP tuple (comedia mode enabled)");auto wasConnected = IsConnected();this->tuple = new RTC::TransportTuple(tuple);if (!this->listenIp.announcedIp.empty())this->tuple->SetLocalAnnouncedIp(this->listenIp.announcedIp);// If not yet connected do it now.if (!wasConnected){// Notify the Node PlainTransport.json data = json::object();this->tuple->FillJson(data["tuple"]);this->shared->channelNotifier->Emit(this->id, "tuple", data);RTC::Transport::Connected();}}// Otherwise, if RTP tuple is set, verify that it matches the origin// of the packet.else if (!this->tuple->Compare(tuple)){MS_DEBUG_TAG(rtp, "ignoring RTP packet from unknown IP:port");// Remove this SSRC.RecvStreamClosed(packet->GetSsrc());delete packet;return;}// Pass the packet to the parent transport.将数据包传递给父传输。RTC::Transport::ReceiveRtpPacket(packet);}
Transport::ReceiveRtpPacket
    //当前调用来源于子类的OnRtpDataReceived中触发了当前接口void Transport::ReceiveRtpPacket(RTC::RtpPacket* packet){MS_TRACE();packet->logger.recvTransportId = this->id;// Apply the Transport RTP header extension ids so the RTP listener can use them.// 应用传输RTP报头扩展id,以便RTP侦听器可以使用它们。packet->SetMidExtensionId(this->recvRtpHeaderExtensionIds.mid);packet->SetRidExtensionId(this->recvRtpHeaderExtensionIds.rid);packet->SetRepairedRidExtensionId(this->recvRtpHeaderExtensionIds.rrid);packet->SetAbsSendTimeExtensionId(this->recvRtpHeaderExtensionIds.absSendTime);packet->SetTransportWideCc01ExtensionId(this->recvRtpHeaderExtensionIds.transportWideCc01);auto nowMs = DepLibUV::GetTimeMs();// Feed the TransportCongestionControlServer.if (this->tccServer){this->tccServer->IncomingPacket(nowMs, packet);}// Get the associated Producer./*根据收到的packet,查找关联的producer。*/RTC::Producer* producer = this->rtpListener.GetProducer(packet);if (!producer){packet->logger.Dropped(RtcLogger::RtpPacket::DropReason::PRODUCER_NOT_FOUND);MS_WARN_TAG(rtp,"no suitable Producer for received RTP packet [ssrc:%" PRIu32 ", payloadType:%" PRIu8 "]",packet->GetSsrc(),packet->GetPayloadType());// Tell the child class to remove this SSRC.告诉子类删除这个SSRC。RecvStreamClosed(packet->GetSsrc());delete packet;return;}// MS_DEBUG_DEV(//   "RTP packet received [ssrc:%" PRIu32 ", payloadType:%" PRIu8 ", producerId:%s]",//   packet->GetSsrc(),//   packet->GetPayloadType(),//   producer->id.c_str());// Pass the RTP packet to the corresponding Producer./*将packet传给指定的producer,进行下一步处理。*/auto result = producer->ReceiveRtpPacket(packet);switch (result)/*根据packet包类型不同,进行不同通道的码率统计。*/{case RTC::Producer::ReceiveRtpPacketResult::MEDIA:this->recvRtpTransmission.Update(packet);/*媒体通道的码率统计*/ break;case RTC::Producer::ReceiveRtpPacketResult::RETRANSMISSION:this->recvRtxTransmission.Update(packet); /*重传通道的码率统计*/   break;case RTC::Producer::ReceiveRtpPacketResult::DISCARDED:// Tell the child class to remove this SSRC.RecvStreamClosed(packet->GetSsrc());break;default:;}/*释放rtp包*/delete packet;}
Producer::ReceiveRtpPacket
    /*接收到transport传入的packet,对packet进行指定的处理。*/Producer::ReceiveRtpPacketResult Producer::ReceiveRtpPacket(RTC::RtpPacket* packet){MS_TRACE();packet->logger.producerId = this->id;// Reset current packet./*重置当前正在处理的packet*/this->currentRtpPacket = nullptr;// Count number of RTP streams.统计当前接收流的数目auto numRtpStreamsBefore = this->mapSsrcRtpStream.size();/*通过packet,获取对应的接收流。*/auto* rtpStream = GetRtpStream(packet);if (!rtpStream)/*没有查找到对应的rtp接收流*/{MS_WARN_TAG(rtp, "no stream found for received packet [ssrc:%" PRIu32 "]", packet->GetSsrc());packet->logger.Dropped(RtcLogger::RtpPacket::DropReason::RECV_RTP_STREAM_NOT_FOUND);return ReceiveRtpPacketResult::DISCARDED;/*将packet丢弃*/}// Pre-process the packet./*对packet进行预处理:如果是视频,则添加头部扩展id。*/PreProcessRtpPacket(packet);ReceiveRtpPacketResult result;bool isRtx{ false };/*packet是否是rtx流中的packet*/// Media packet./*是主流中的rtp包*/if (packet->GetSsrc() == rtpStream->GetSsrc()){   /*设置返回结果,表示是媒体流,视频流或音频流。*/result = ReceiveRtpPacketResult::MEDIA;// Process the packet./*rtp接收流处理接收到的packet*/if (!rtpStream->ReceivePacket(packet)){// May have to announce a new RTP stream to the listener./*如果添加了新的rtp接收流,则通知其订阅者。*/if (this->mapSsrcRtpStream.size() > numRtpStreamsBefore)NotifyNewRtpStream(rtpStream); /*最终通知到的是与producer相关的consumer*/packet->logger.Dropped(RtcLogger::RtpPacket::DropReason::RECV_RTP_STREAM_DISCARDED);return result;}}// RTX packet./*重传流中的rtp包*/else if (packet->GetSsrc() == rtpStream->GetRtxSsrc()){result = ReceiveRtpPacketResult::RETRANSMISSION;isRtx  = true;// Process the packet./*rtp接收流处理重传流中的packet*/if (!rtpStream->ReceiveRtxPacket(packet)){packet->logger.Dropped(RtcLogger::RtpPacket::DropReason::RECV_RTP_STREAM_NOT_FOUND);return result;}}// Should not happen.else{MS_ABORT("found stream does not match received packet");}/*判断packet是否是关键帧中的包*/if (packet->IsKeyFrame()){MS_DEBUG_TAG(rtp,"key frame received [ssrc:%" PRIu32 ", seq:%" PRIu16 "]",packet->GetSsrc(),packet->GetSequenceNumber());// Tell the keyFrameRequestManager.if (this->keyFrameRequestManager)this->keyFrameRequestManager->KeyFrameReceived(packet->GetSsrc()); /*更新关键帧*/}// May have to announce a new RTP stream to the listener.if (this->mapSsrcRtpStream.size() > numRtpStreamsBefore){// Request a key frame for this stream since we may have lost the first packets// (do not do it if this is a key frame).if (this->keyFrameRequestManager && !this->paused && !packet->IsKeyFrame())this->keyFrameRequestManager->ForceKeyFrameNeeded(packet->GetSsrc());// Update current packet.this->currentRtpPacket = packet;NotifyNewRtpStream(rtpStream);// Reset current packet.this->currentRtpPacket = nullptr;}// If paused stop here.if (this->paused)return result;// May emit 'trace' event.EmitTraceEventRtpAndKeyFrameTypes(packet, isRtx);// Mangle the packet before providing the listener with it./*在将packet发布至其订阅者之前,对其进行倾轧。主要进行payload type,ssrc,header extension的处理*/if (!MangleRtpPacket(packet, rtpStream))return ReceiveRtpPacketResult::DISCARDED;// Post-process the packet./*最后再对packet进行一次处理*/PostProcessRtpPacket(packet);/*将处理后的packet,发送到其订阅者transport中。*/this->listener->OnProducerRtpPacketReceived(this, packet);return result;}
向上传递到Transport层
	inline void Transport::OnProducerRtpPacketReceived(RTC::Producer* producer, RTC::RtpPacket* packet){MS_TRACE();//listener是上层的Routerthis->listener->OnTransportProducerRtpPacketReceived(this, producer, packet);}
向上传递到Router层
	inline void Router::OnTransportProducerRtpPacketReceived(RTC::Transport* /*transport*/, RTC::Producer* producer, RTC::RtpPacket* packet){MS_TRACE();packet->logger.routerId = this->id;//通过生产者,所以出订阅者列表auto& consumers = this->mapProducerConsumers.at(producer);//如果存在对应的订阅者if (!consumers.empty()){// Cloned ref-counted packet that RtpStreamSend will store for as long as// needed avoiding multiple allocations unless absolutely necessary.// Clone only happens if needed.std::shared_ptr<RTC::RtpPacket> sharedPacket;for (auto* consumer : consumers){// Update MID RTP extension value.const auto& mid = consumer->GetRtpParameters().mid;if (!mid.empty())packet->UpdateMid(mid);//发送RTP数据consumer->SendRtpPacket(packet, sharedPacket);}}auto it = this->mapProducerRtpObservers.find(producer);if (it != this->mapProducerRtpObservers.end()){auto& rtpObservers = it->second;for (auto* rtpObserver : rtpObservers){rtpObserver->ReceiveRtpPacket(producer, packet);}}}
具体transport通道转发数据
void PlainTransport::SendRtpPacket(RTC::Consumer* /*consumer*/, RTC::RtpPacket* packet, RTC::Transport::onSendCallback* cb){MS_TRACE();if (!IsConnected()){if (cb){(*cb)(false);delete cb;}return;}const uint8_t* data = packet->GetData();auto intLen         = static_cast<int>(packet->GetSize());if (HasSrtp() && !this->srtpSendSession->EncryptRtp(&data, &intLen)){if (cb){(*cb)(false);delete cb;}return;}auto len = static_cast<size_t>(intLen);//使用元组获发送RTP数据this->tuple->Send(data, len, cb);// Increase send transmission.增加发送传输的数据大小。RTC::Transport::DataSent(len);}
		void Send(const uint8_t* data, size_t len, RTC::TransportTuple::onSendCallback* cb = nullptr){if (this->protocol == Protocol::UDP)this->udpSocket->Send(data, len, this->udpRemoteAddr, cb);elsethis->tcpConnection->Send(data, len, cb);}

底层实际发送

void UdpSocketHandler::Send(const uint8_t* data, size_t len, const struct sockaddr* addr, UdpSocketHandler::onSendCallback* cb)
{MS_TRACE();if (this->closed){if (cb){(*cb)(false);delete cb;}return;}if (len == 0){if (cb){(*cb)(false);delete cb;}return;}// First try uv_udp_try_send(). In case it can not directly send the datagram// then build a uv_req_t and use uv_udp_send().uv_buf_t buffer = uv_buf_init(reinterpret_cast<char*>(const_cast<uint8_t*>(data)), len);const int sent  = uv_udp_try_send(this->uvHandle, &buffer, 1, addr);// Entire datagram was sent. Done.if (sent == static_cast<int>(len)){// Update sent bytes.this->sentBytes += sent;if (cb){(*cb)(true);delete cb;}return;}else if (sent >= 0){MS_WARN_DEV("datagram truncated (just %d of %zu bytes were sent)", sent, len);// Update sent bytes.this->sentBytes += sent;if (cb){(*cb)(false);delete cb;}return;}// Any error but legit EAGAIN. Use uv_udp_send().else if (sent != UV_EAGAIN){MS_WARN_DEV("uv_udp_try_send() failed, trying uv_udp_send(): %s", uv_strerror(sent));}auto* sendData = new UvSendData(len);sendData->req.data = static_cast<void*>(sendData);std::memcpy(sendData->store, data, len);sendData->cb = cb;buffer = uv_buf_init(reinterpret_cast<char*>(sendData->store), len);int err = uv_udp_send(&sendData->req, this->uvHandle, &buffer, 1, addr, static_cast<uv_udp_send_cb>(onSend));if (err != 0){// NOTE: uv_udp_send() returns error if a wrong INET family is given// (IPv6 destination on a IPv4 binded socket), so be ready.MS_WARN_DEV("uv_udp_send() failed: %s", uv_strerror(err));if (cb)(*cb)(false);// Delete the UvSendData struct (it will delete the store and cb too).delete sendData;}else{// Update sent bytes.this->sentBytes += len;}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/100663.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

TDengine时序数据库学习使用

数据库要求&#xff1a; 1.目前服务器只能在linux运行&#xff0c;先安装服务器版本v1 2.下载与v1完全匹配的客户端版本v1&#xff08;客户端与服务器的版本号不匹配可能访问不了服务器&#xff09; 第一步 安装 安装服务器注意&#xff0c;安装教程&#xff1a; 使用安装…

Linux中scp命令复制文件

scp命令是在Linux中用于在本地主机和远程主机之间进行安全传输文件的命令。下面是使用scp命令的语法&#xff1a; scp [参数] [来源路径] [目标路径]参数&#xff1a; -r&#xff1a;递归复制整个目录。-P&#xff1a;指定远程主机的端口。-p&#xff1a;保留原文件的修改时间…

(蓝宝书)网络安全——CTF那些事儿

欢迎关注博主 Mindtechnist 或加入【智能科技社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和技术。关…

CocosCreator 面试题(七)优化cocos creator 包体体积

优化 Cocos Creator 包体体积是一个重要的任务&#xff0c;可以通过以下几个方面进行优化&#xff1a; 图片文件体积优化&#xff1a; 压缩图片&#xff1a;使用工具如 TinyPNG、ImageOptim 等对图片进行压缩&#xff0c;减小文件大小而保持可接受的质量。图片格式选择&#…

WPF 控件分辨率自适应问题

WPF 控件分辨率自适应时&#xff0c;我首先想到的是使用ViewBox控件来做分辨率自适应。 ViewBox这个控件通常和其他控件结合起来使用&#xff0c;是WPF中非常有用的控件。定义一个内容容器。ViewBox组件的作用是拉伸或延展位于其中的组件&#xff0c;以填满可用空间&#xff0…

深度学习问答题(更新中)

1. 各个激活函数的优缺点&#xff1f; 2. 为什么ReLU常用于神经网络的激活函数&#xff1f; 在前向传播和反向传播过程中&#xff0c;ReLU相比于Sigmoid等激活函数计算量小&#xff1b;避免梯度消失问题。对于深层网络&#xff0c;Sigmoid函数反向传播时&#xff0c;很容易就…

mp4音视频分离技术

文章目录 问题描述一、分离MP3二、分离无声音的MP4三、结果 问题描述 MP4视频想拆分成一个MP3音频和一个无声音的MP4文件 一、分离MP3 ffmpeg -i C:\Users\Administrator\Desktop\一个文件夹\我在财神殿里长跪不起_完整版MV.mp4 -vn C:\Users\Administrator\Desktop\一个文件…

分布式数据库HBase(林子雨慕课课程)

文章目录 4. 分布式数据库HBase4.1 HBase简介4.2 HBase数据模型4.3 HBase的实现原理4.4 HBase运行机制4.5 HBase的应用方案4.6 HBase安装和编程实战 4. 分布式数据库HBase 4.1 HBase简介 HBase是BigTable的开源实现 对于网页搜索主要分为两个阶段 1.建立整个网页索引&#xf…

如何在 Android 中完成一个 APT 项目的开发?

前言 APT(Annotation Processing Tool)即注解处理器&#xff0c;是一种处理注解的工具。 APT在编译时期扫描处理源代码中的注解&#xff0c;开发中可以根据注解&#xff0c;利用APT自动生成Java代码&#xff0c;减少冗余的代码和手动的代码输入过程&#xff0c;提升了编码效率…

pytest + yaml 框架 -55. raw 不转义模板语法

前言 在yaml 文件中&#xff0c;设置的引用变量语法是${var}, 最近有小伙伴提到一个需求&#xff1a;请求参数的内容需要有特殊符号${var}, 希望不被转义&#xff0c;不要引用变量&#xff0c;直接用原始数据即可。 raw 忽略模板语法 Jinja2提供了 “raw” 语句来忽略所有模…

Gralloc ION DMABUF in Camera Display

目录 Background knowledge Introduction ia pa va and memory addressing Memory Addressing Page Frame Management Memory area management DMA IOVA and IOMMU Introduce DMABUF What is DMABUF DMABUF 关键概念 DMABUF APIS –The Exporter DMABUF APIS –The…

上海亚商投顾:沪指探底回升 华为汽车概念股集体大涨

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一.市场情绪 三大指数昨日探底回升&#xff0c;早盘一度集体跌超1%&#xff0c;随后震荡回暖&#xff0c;深成指、创业板指…

交通物流模型 | 基于多重时空信息融合网络的城市网约车需求预测

交通物流模型 | 基于多重时空信息融合网络的城市网约车需求预测 城市网约车需求预测是网约车系统决策、出租车调度和智能交通建设的一项长期且具有挑战性的任务。准确的城市网约车需求预测可以提升车辆的利用和调度,减少等待时间和交通拥堵。现有的交通流预测方法大部分采用基…

LeetCode二叉树OJ

目录 剑指 Offer 55 - I. 二叉树的深度 - 力扣&#xff08;LeetCode&#xff09; 965. 单值二叉树 - 力扣&#xff08;LeetCode&#xff09; 100. 相同的树 - 力扣&#xff08;LeetCode&#xff09; 101. 对称二叉树 - 力扣&#xff08;LeetCode&#xff09; 二叉树遍历_牛客题…

Vue组件化开发步骤

Vue组件化开发的步骤可以简单概括为以下几步&#xff1a; 划分组件&#xff1a;根据页面的布局和功能需求&#xff0c;将页面划分成若干个组件&#xff0c;每个组件具备独立的功能和样式。 编写组件&#xff1a;针对每个组件&#xff0c;编写组件的模板、样式和逻辑代码&#…

uniapp 点击 富文本元素 图片 可以预览(非nvue)

我使用的是uniapp 官方推荐的组件 rich-text&#xff0c;一般我能用官方级用官方&#xff0c;更有保障一些。 一、整体逻辑 1. 定义一段html标签字符串&#xff0c;里面包含图片 2. 将字符串放入rich-text组件中&#xff0c;绑定点击事件itemclick 3. 通过点击事件获取到图片ur…

TensorFlow学习:使用官方模型进行图像分类、使用自己的数据对模型进行微调

前言 上一篇文章 TensorFlow案例学习&#xff1a;对服装图像进行分类 中我们跟随官方文档学习了如何进行预处理数据、构建模型、训练模型等。但是对于像我这样的业余玩家来说训练一个模型是非常困难的。所以为什么我们不站在巨人的肩膀上&#xff0c;使用已经训练好了的成熟模…

VIT(Vision Transformer)学习-模型理解(一)

VIT (Vision Transformer) 模型论文代码(源码)从零详细解读&#xff0c;看不懂来打我_哔哩哔哩_bilibili VIT模型架构图 1.图片切分为patch 2. patch转化为embedding 1&#xff09;将patch展平为一维长度 2&#xff09;token embedding&#xff1a;将拉平之后的序列映射…

【ARM AMBA5 CHI 入门 12.1 -- CHI 链路层详细介绍 】

文章目录 CHI 版本介绍1.1 CHI 链路层介绍1.1.1 Flit 切片介绍1.1.2 link layer credit(L-Credit)机制1.1.3 Channel1.1.4 Port1.1. RN Node 接口定义1.1.6 SN Node 接口定义1.2 Channel interface signals1.2.1 Request, REQ, channel1.2.2 Response, RSP, channel1.2.3 Snoop…

如何找到新媒体矩阵中存在的问题?

随着数字媒体的发展&#xff0c;企业的新媒体矩阵已成为品牌推广和营销的重要手段之一。 然而&#xff0c;很多企业在搭建新媒体矩阵的过程中&#xff0c;往往会忽略一些问题&#xff0c;导致矩阵发展存在潜在风险&#xff0c;影响整个矩阵运营效果。 因此&#xff0c;找到目前…