想找公司做网站,闸北东莞网站建设,苏州网站开发公司济南兴田德润o厉害吗,设计网站酷WebRTC 同时使用基于丢包的带宽估计算法和基于延迟的带宽估计算法那#xff0c;能够实现更加全面和准确的带宽评估和控制。基于丢包的带宽估计算法主要依据网络中的丢包情况来动态调整带宽估计#xff0c;以适应网络状况的变化。本文主要讲解最新 LossBasedBweV2 的实现。
1…WebRTC 同时使用基于丢包的带宽估计算法和基于延迟的带宽估计算法那能够实现更加全面和准确的带宽评估和控制。基于丢包的带宽估计算法主要依据网络中的丢包情况来动态调整带宽估计以适应网络状况的变化。本文主要讲解最新 LossBasedBweV2 的实现。
1. 静态结构
LossBasedBweV2 的静态结构比较简单如下图所示。LossBasedBweV2 被包含在 SendSideBandwidthEstimation 之中GoogCcNetworkController 不直接与 LossBasedBweV2 打交道而是通过 SendSideBandwidthEstimation 获取最终带宽估计值。LossBasedBweV2 静态结构虽然简单但其内部实现一点都不简单做好心理准备。 2. 重要属性
1current_best_estimate_
从候选估计值中选择的当前最佳估计值包含带宽估计值和链路固有丢包率。
struct ChannelParameters {// 链路固有丢包率非带宽受限导致的丢包率double inherent_loss 0.0;// 丢包限制下的带宽DataRate loss_limited_bandwidth DataRate::MinusInfinity();
};
2observations_
历史观测值集合。一个 Observation 代表一个观测值发送码率和丢包率估计算法中会用到。
struct Observation {bool IsInitialized() const { return id ! -1; }// 报文总数int num_packets 0;// 丢包数量int num_lost_packets 0;// 接收数量int num_received_packets 0;// 根据观察时间计算DataRate sending_rate DataRate::MinusInfinity();// 报文总大小DataSize size DataSize::Zero();// 丢包总大小DataSize lost_size DataSize::Zero();int id -1;
};
3loss_based_result_
基于丢包的带宽估计值和状态。
struct Result {// 估算的带宽DataRate bandwidth_estimate DataRate::Zero();// 如果处于kIncreasing状态则需要做带宽探测LossBasedState state LossBasedState::kDelayBasedEstimate;
};enum class LossBasedState {// 使用丢包估计带宽正在增加码率kIncreasing 0,// 使用丢包估计带宽正在使用padding增加带宽探测kIncreaseUsingPadding 1,// 使用丢包估计带宽由于丢包增大正在降低码率kDecreasing 2,// 使用延迟估计带宽kDelayBasedEstimate 3
};
3. 重要方法
1SetAcknowledgedBitrate
设置 ACK 码率ACK 码率在很多地方都会被用到比如计算基于丢包带宽估计值的上限和下限生成候选者带宽
2SetMinMaxBitrate
设置基于丢包带宽估计的上限值和下限值。
3UpdateBandwidthEstimate
SendSideBandwidthEstimation 调用此接口传入 TransportFeedback、延迟估计带宽和 ALR 状态等参数。
4GetLossBasedResult
获取基于丢包带宽估计结果。
4. 源码分析
4.1. UpdateBandwidthEstimate
UpdateBandwidthEstimate 是丢包估计的主函数代码非常多其主体流程如下图所示 4.1.1. 搜索最佳候选者
搜索最佳候选者的逻辑如下图所示解释如下
1基于 TransportFeedback 构建观测值每一组观测值设置了最小观测时长。如果产生了新的观测值则进人新一轮的带宽估计。
2使用一定算法生成一系列候选者candidate只需确定候选者带宽即可。
3基于观测数据使用牛顿方法计算候选者的最优固有丢包率。
4基于观测数据对每个候选者计算目标函数值取目标函数值最大者为最佳候选者。 // 尝试将新的观测数据加入到历史数据中如果没有产生新的observation则返回
if (!PushBackObservation(packet_results)) {return;
}// 初始化最佳带宽估计如果没有有效的丢包限制带宽估计则使用基于延迟的估计
if (!IsValid(current_best_estimate_.loss_limited_bandwidth)) {if (!IsValid(delay_based_estimate)) {return;}current_best_estimate_.loss_limited_bandwidth delay_based_estimate;loss_based_result_ {.bandwidth_estimate delay_based_estimate,.state LossBasedState::kDelayBasedEstimate};
}ChannelParameters best_candidate current_best_estimate_;
double objective_max std::numeric_limitsdouble::lowest();// 生成并遍历所有candidate找到最优candidate
for (ChannelParameters candidate : GetCandidates(in_alr)) {// 使用牛顿法搜索最优固有丢包率NewtonsMethodUpdate(candidate);// 基于带宽和固有丢包率计算收益值const double candidate_objective GetObjective(candidate);// 找到收益值最大的Candidateif (candidate_objective objective_max) {objective_max candidate_objective;best_candidate candidate;}
}
4.1.2. 调整丢包限制带宽
通过算法计算得到的最佳候选者还不可靠需要进行调整。 在丢包限制状态下如果带宽增加过快则限制带宽增长并使用爬坡因子来调整带宽估计。增加带宽过快可能会再次引发丢包。
// best_candidate 的估计带宽与其固有丢包率是匹配的如果 best_candidate 的估计带宽大于
// 上一次的估计带宽但真实丢包率大于 best_candidate 的固有丢包率那么有理由认为
// best_candidate 的估计带宽是不可靠的。
if (GetAverageReportedLossRatio() best_candidate.inherent_loss config_-not_increase_if_inherent_loss_less_than_average_loss current_best_estimate_.loss_limited_bandwidth best_candidate.loss_limited_bandwidth) {best_candidate.loss_limited_bandwidth current_best_estimate_.loss_limited_bandwidth;
}// 下面这一坨都是在调整best_candidate.loss_limited_bandwidth
if (IsInLossLimitedState()) {if (recovering_after_loss_timestamp_.IsFinite() recovering_after_loss_timestamp_ config_-delayed_increase_window last_send_time_most_recent_observation_ best_candidate.loss_limited_bandwidth bandwidth_limit_in_current_window_) {best_candidate.loss_limited_bandwidth bandwidth_limit_in_current_window_;}bool increasing_when_loss_limited IsEstimateIncreasingWhenLossLimited(/*old_estimate*/current_best_estimate_.loss_limited_bandwidth,/*new_estimate*/best_candidate.loss_limited_bandwidth);// Bound the best candidate by the acked bitrate.if (increasing_when_loss_limited IsValid(acknowledged_bitrate_)) {// 爬坡因子double rampup_factor config_-bandwidth_rampup_upper_bound_factor;// 使用更保守的爬坡因子if (IsValid(last_hold_info_.rate) acknowledged_bitrate_ config_-bandwidth_rampup_hold_threshold * last_hold_info_.rate) {rampup_factor config_-bandwidth_rampup_upper_bound_factor_in_hold;}// 保证带宽估计值不会低于当前的最佳估计值。// 同时限制在不超过新计算的候选值和基于 ACK 码率计算的增长上限之间的较小值。best_candidate.loss_limited_bandwidth std::max(current_best_estimate_.loss_limited_bandwidth,std::min(best_candidate.loss_limited_bandwidth,rampup_factor * (*acknowledged_bitrate_)));// 为了避免估计值长时间停滞导致算法无法切换到kIncreasing这里将带宽估计增加1kbps。if (loss_based_result_.state LossBasedState::kDecreasing best_candidate.loss_limited_bandwidth current_best_estimate_.loss_limited_bandwidth) {best_candidate.loss_limited_bandwidth current_best_estimate_.loss_limited_bandwidth DataRate::BitsPerSec(1);}}
}4.1.3. 计算有界带宽估计
取丢包估计带宽和延迟估计带宽的较小者并将估计值限制在合理范围获得 bounded_bandwidth_estimate。
// 施加了范围限制的带宽估计值
DataRate bounded_bandwidth_estimate DataRate::PlusInfinity();if (IsValid(delay_based_estimate_)) {// 取丢包估计带宽和延迟估计带宽中的较小者bounded_bandwidth_estimate std::max(GetInstantLowerBound(),std::min({best_candidate.loss_limited_bandwidth,GetInstantUpperBound(), delay_based_estimate_}));
} else {// 没有延迟估计值则使用丢包估计值bounded_bandwidth_estimate std::max(GetInstantLowerBound(), std::min(best_candidate.loss_limited_bandwidth, GetInstantUpperBound()));
}
4.1.4. 更新当前最佳估计
根据配置和估计结果更新当前最佳估计值。
if (config_-bound_best_candidate bounded_bandwidth_estimate best_candidate.loss_limited_bandwidth) {// 如果配置了对 best_candidate 进行约束则限制 // best_candidate.loss_limited_bandwidth 不能大于 bounded_bandwidth_estimatecurrent_best_estimate_.loss_limited_bandwidth bounded_bandwidth_estimate;current_best_estimate_.inherent_loss 0;
} else {// 没有配置就等于筛选出来的最优值current_best_estimate_ best_candidate;
}
4.1.5. 设置带宽估计结果
获取 bounded_bandwidth_estimate 后接下来需要更新 loss_based_result.state并设置估计带宽。以下代码逻辑异常复杂条件一大堆是 LossBasedBweV2 最难理解的部分。
// 当前是在 kDecreasing 状态此次丢包估计带宽低于延迟估计带宽不允许估计带宽
// 立即上升到可能引起丢包的水平。
if (loss_based_result_.state LossBasedState::kDecreasing last_hold_info_.timestamp last_send_time_most_recent_observation_ bounded_bandwidth_estimate delay_based_estimate_) {loss_based_result_.bandwidth_estimate std::min(last_hold_info_.rate, bounded_bandwidth_estimate);return; // 直接返回状态保持LossBasedState::kDecreasing
}// 带宽增加
if (IsEstimateIncreasingWhenLossLimited(/*old_estimate*/loss_based_result_.bandwidth_estimate,/*new_estimate*/bounded_bandwidth_estimate) CanKeepIncreasingState(bounded_bandwidth_estimate) bounded_bandwidth_estimate delay_based_estimate_ bounded_bandwidth_estimate max_bitrate_) {if (config_-padding_duration TimeDelta::Zero() bounded_bandwidth_estimate last_padding_info_.padding_rate) {// 开启一个新的填充周期last_padding_info_.padding_rate bounded_bandwidth_estimate;last_padding_info_.padding_timestamp last_send_time_most_recent_observation_;}loss_based_result_.state config_-padding_duration TimeDelta::Zero()? LossBasedState::kIncreaseUsingPadding: LossBasedState::kIncreasing;
}
// 带宽减少
else if (bounded_bandwidth_estimate delay_based_estimate_ bounded_bandwidth_estimate max_bitrate_) {if (loss_based_result_.state ! LossBasedState::kDecreasing config_-hold_duration_factor 0) {last_hold_info_ {.timestamp last_send_time_most_recent_observation_ last_hold_info_.duration,.duration std::min(kMaxHoldDuration, last_hold_info_.duration *config_-hold_duration_factor),.rate bounded_bandwidth_estimate};}last_padding_info_ PaddingInfo();loss_based_result_.state LossBasedState::kDecreasing;
} else {// 如果以上条件都不满足表明基于延迟的估计应该被采纳// 或者当前状态需要重置以避免带宽被错误地限制在低水平。last_hold_info_ {.timestamp Timestamp::MinusInfinity(),.duration kInitHoldDuration,.rate DataRate::PlusInfinity()};last_padding_info_ PaddingInfo();loss_based_result_.state LossBasedState::kDelayBasedEstimate;
}// 更新丢包限制的评估带宽
loss_based_result_.bandwidth_estimate bounded_bandwidth_estimate;
4.2. 相关算法
基于丢包带宽估计的核心问题可以表述为带宽和固有丢包率是链路的两个属性现在我们有一组观测值每个观测值记录了码率和丢包率如何通过这些观测值反推链路的带宽和固有丢包率
WebRTC 假定链路丢包符合二项分布先生成一组候选者candidate根据经验设置候选者的带宽然后用牛顿方法在观测值上搜索候选者的最优固有丢包率最后用候选者带宽和固有丢包率计算一个收益函数取收益函数最大的候选者作为最佳估计。
4.2.1. 搜集观测值
Observation 基于 TransportFeedback 生成收集足够时长报文成为一个观测值。
bool LossBasedBweV2::PushBackObservation(rtc::ArrayViewconst PacketResult packet_results) {if (packet_results.empty()) {return false;}// 获取报文数组的统计信息PacketResultsSummary packet_results_summary GetPacketResultsSummary(packet_results);// 累加报文数量partial_observation_.num_packets packet_results_summary.num_packets;// 累加丢包数量partial_observation_.num_lost_packets packet_results_summary.num_lost_packets;// 累加报文大小partial_observation_.size packet_results_summary.total_size;// 累加丢包大小partial_observation_.lost_size packet_results_summary.lost_size;// This is the first packet report we have received.if (!IsValid(last_send_time_most_recent_observation_)) {last_send_time_most_recent_observation_ packet_results_summary.first_send_time;}// 报文组中最晚发包时间const Timestamp last_send_time packet_results_summary.last_send_time;// 距离上一组 last_send_time 时间差const TimeDelta observation_duration last_send_time - last_send_time_most_recent_observation_;// 两组报文时间差要达到阈值才能创建一个完整的 observationif (observation_duration TimeDelta::Zero() ||observation_duration config_-observation_duration_lower_bound) {return false;}// 更新last_send_time_most_recent_observation_ last_send_time;// 创建 oberservationObservation observation;observation.num_packets partial_observation_.num_packets;observation.num_lost_packets partial_observation_.num_lost_packets;observation.num_received_packets observation.num_packets - observation.num_lost_packets;observation.sending_rate GetSendingRate(partial_observation_.size / observation_duration);observation.lost_size partial_observation_.lost_size;observation.size partial_observation_.size;observation.id num_observations_;// 保存 observationobservations_[observation.id % config_-observation_window_size] observation;// 重置 partialpartial_observation_ PartialObservation();CalculateInstantUpperBound();return true;
}
4.2.2. 生成候选者
搜索最佳后选择之前需要生成一系列候选者。由于是有限集合搜索候选者带宽的选取要考虑上界、下界以及分布的合理性以形成一个有效的搜索空间获得准确的搜索结果。
std::vectorLossBasedBweV2::ChannelParameters LossBasedBweV2::GetCandidates(bool in_alr) const {// 当前的最佳带宽估计中提取信息ChannelParameters best_estimate current_best_estimate_;// 用于存储即将生成的候选带宽std::vectorDataRate bandwidths;// 基于当前最佳估计带宽和生成因子生成一系列候选带宽值: 1.02, 1.0, 0.95// 新的带宽在当前最佳估计带宽左右的概率比较高带宽不会瞬变for (double candidate_factor : config_-candidate_factors) {bandwidths.push_back(candidate_factor *best_estimate.loss_limited_bandwidth);}// ACK码率是链路容量的一个真实测量值添加一个基于ACK码率但进行了回退因子调整的候选带宽if (acknowledged_bitrate_.has_value() config_-append_acknowledged_rate_candidate) {if (!(config_-not_use_acked_rate_in_alr in_alr) ||(config_-padding_duration TimeDelta::Zero() last_padding_info_.padding_timestamp config_-padding_duration last_send_time_most_recent_observation_)) {bandwidths.push_back(*acknowledged_bitrate_ *config_-bandwidth_backoff_lower_bound_factor);}}// 满足以下条件延迟估计带宽也作为带宽候选者之一// 1延迟估计带宽有效// 2配置允许// 3延迟估计带宽高于当前最佳估计丢包限制带宽if (IsValid(delay_based_estimate_) config_-append_delay_based_estimate_candidate) {if (delay_based_estimate_ best_estimate.loss_limited_bandwidth) {bandwidths.push_back(delay_based_estimate_);}}// 满足以下条件当前带宽上界也作为带宽候选者之一// 1处于ALR状态// 2配置允许时// 3最佳估计丢包限制带宽大于当前带宽上界if (in_alr config_-append_upper_bound_candidate_in_alr best_estimate.loss_limited_bandwidth GetInstantUpperBound()) {bandwidths.push_back(GetInstantUpperBound());}// 计算一个候选带宽的上界用于限制生成的候选带宽值不超过这个上界。const DataRate candidate_bandwidth_upper_bound GetCandidateBandwidthUpperBound();std::vectorChannelParameters candidates;candidates.resize(bandwidths.size());for (size_t i 0; i bandwidths.size(); i) {ChannelParameters candidate best_estimate;// 丢包限制带宽设置为当前最佳估计的丢包限制带宽与候选带宽值、上界之间的最小值candidate.loss_limited_bandwidth std::min(bandwidths[i], std::max(best_estimate.loss_limited_bandwidth,candidate_bandwidth_upper_bound));// 使用最佳估计的丢包率candidate.inherent_loss GetFeasibleInherentLoss(candidate);candidates[i] candidate;}return candidates;
}
4.2.3. 牛顿方法
牛顿方法要解决的问题是在候选者的估计带宽下基于当前观测值求最大似然概率下的固有丢包率。可以这么理解已知当前链路的带宽测得一组观测值观测值描述了收发数据和丢包情况现在需要计算一个最优的固定丢包率使得当前观测值出现的联合概率最大。
观测数据可以简化描述为在一段时间内统计丢失了 n 个报文接收到 m 个报文。假设链路的固有丢包率为p由于观测结果属于二项分布其概率密度函数可以表示为 现在测得一组观测数据要求链路固有丢包率的最大似然概率。我们可以将 k 次观测数据的似然函数相乘得到联合似然函数因为每次实验是独立的 直接最大化上述似然函数可能比较复杂可以先对似然函数取自然对数转换为对数似然函数方便计算 由于 不依赖于 在求导时会消失因此在最大化对数似然函数时可以忽略这一项。对 关于 求导并令导数等于0可以找到 的最大似然估计值 。 理论上代入观测数据就可以求得最优固有丢包率。但这里不能这么计算原因有两个
1这里的丢包率并不是固有丢包率 inherent_loss而是丢包概率 loss_probabilityloss_probability 除 inherent_loss 之外还包括发送速率超出链路带宽导致的丢包。
2即使计算得到 loss_probability 的最大似然估计值仍然不能直接求得 inherent_loss 的最大似然估计值因为 inherent_loss 与 loss_probability 之间并不是简单的线性关系如下所示。
double GetLossProbability(double inherent_loss, DataRate loss_limited_bandwidth,DataRate sending_rate) {if (inherent_loss 0.0 || inherent_loss 1.0) {inherent_loss std::min(std::max(inherent_loss, 0.0), 1.0);}double loss_probability inherent_loss;// 如果发送速率大于丢包限制带宽真实丢包率会更高if (IsValid(sending_rate) IsValid(loss_limited_bandwidth) (sending_rate loss_limited_bandwidth)) {loss_probability (1 - inherent_loss) *(sending_rate - loss_limited_bandwidth) / sending_rate;}// 限制范围[1.0e-6, 1.0 - 1.0e-6]return std::min(std::max(loss_probability, 1.0e-6), 1.0 - 1.0e-6);
}
既然如此WebRTC 就通过计算似然函数的一阶导数和二阶导数然后使用牛顿方法来搜索 inherent_loss 的最优值。代码如下所示标准的牛顿方法。
void LossBasedBweV2::NewtonsMethodUpdate(ChannelParameters channel_parameters) const {// 没有可用的观测值if (num_observations_ 0) {return;}// 指定带宽下根据观测值求得最大似然丢包率for (int i 0; i config_-newton_iterations; i) {// 计算一阶导数和二阶导数const Derivatives derivatives GetDerivatives(channel_parameters);// 基于一阶导数和二阶导数进行迭代搜索newton_step_size 0.75channel_parameters.inherent_loss -config_-newton_step_size * derivatives.first / derivatives.second;// 固有丢包率的界限约束channel_parameters.inherent_loss GetFeasibleInherentLoss(channel_parameters);}
}
一阶导数和二阶导数的计算如下所示不过这里有两个需要注意的点
1这里计算的并不是 inherent_loss 而是 loss_probability 的最大似然函数的导数由于 loss_probability 是 inherent_loss 的函数根据链式法则使用 loss_probability 的导数来计算 inherent_loss 的最优值是有效的。
2这里的一阶导数和二阶导数是多个观测值计算的累加值由于多个观测值之间是独立同分布的所以这也是没问题的。
LossBasedBweV2::Derivatives LossBasedBweV2::GetDerivatives(const ChannelParameters channel_parameters) const {Derivatives derivatives;for (const Observation observation : observations_) {// 无效的观测值if (!observation.IsInitialized()) {continue;}// 计算在给定通道参数下的丢包概率如果发送速率超过丢包限制带宽// 则很可能会产生链路拥塞从而导致真实丢包率高于链路固有丢包率double loss_probability GetLossProbability(channel_parameters.inherent_loss,channel_parameters.loss_limited_bandwidth, observation.sending_rate);// 施加一个时间权重距当前时间越近数据越“新鲜”权重越高double temporal_weight temporal_weights_[(num_observations_ - 1) - observation.id];// 基于丢失和接收到的数据量分别计算一阶导数和二阶导数的累加项if (config_-use_byte_loss_rate) {// derivatives.first w*((lost/p) - (total-lost)/(1-p))derivatives.first temporal_weight *((ToKiloBytes(observation.lost_size) / loss_probability) -(ToKiloBytes(observation.size - observation.lost_size) /(1.0 - loss_probability)));// derivatives.second - w*((lost/p^2) (total-lost)/(1-p)^2)derivatives.second -temporal_weight *((ToKiloBytes(observation.lost_size) /std::pow(loss_probability, 2)) (ToKiloBytes(observation.size - observation.lost_size) /std::pow(1.0 - loss_probability, 2)));// 基于丢失和接收到的数据包数量分别计算一阶导数和二阶导数的累加项} else {derivatives.first temporal_weight *((observation.num_lost_packets / loss_probability) -(observation.num_received_packets / (1.0 - loss_probability)));derivatives.second -temporal_weight *((observation.num_lost_packets / std::pow(loss_probability, 2)) (observation.num_received_packets /std::pow(1.0 - loss_probability, 2)));}}// 理论上二阶导数应为负表示带宽估计函数的凸性// 若出现非预期的正值进行校正以避免数学异常或不合理的进一步计算。if (derivatives.second 0.0) {derivatives.second -1.0e-6;}return derivatives;
}
4.2.4. 目标函数
经牛顿方法搜索后的固有丢包率加上链路带宽带入目标函数进行计算目标值越大则结果越可信。
目标函数分为两部分第一部分是似然概率代表了模型对观测数据的解释能力其中 是时间权重因子数据越“新鲜”权重越高。 目标函数的第二部分是高带宽偏置鼓励算法探索更高带宽的潜在收益其他项相同的前提下带宽越高越受青睐。其中 是时间权重因子数据越“新鲜”权重越高。 double LossBasedBweV2::GetObjective(const ChannelParameters channel_parameters) const {double objective 0.0;// 计算高带宽偏置鼓励探索更高带宽const double high_bandwidth_bias GetHighBandwidthBias(channel_parameters.loss_limited_bandwidth);for (const Observation observation : observations_) {if (!observation.IsInitialized()) {continue;}// 考虑发送码率高于限制码率情况导致的拥塞丢包double loss_probability GetLossProbability(channel_parameters.inherent_loss,channel_parameters.loss_limited_bandwidth, observation.sending_rate);// 应用一个时间权重给每个观测新近的观测通常会有更大的影响double temporal_weight temporal_weights_[(num_observations_ - 1) - observation.id];if (config_-use_byte_loss_rate) {// 固有丢包率收益objective temporal_weight *((ToKiloBytes(observation.lost_size) * std::log(loss_probability)) (ToKiloBytes(observation.size - observation.lost_size) *std::log(1.0 - loss_probability)));// 带宽收益objective temporal_weight * high_bandwidth_bias * ToKiloBytes(observation.size);} else {objective temporal_weight *((observation.num_lost_packets * std::log(loss_probability)) (observation.num_received_packets *std::log(1.0 - loss_probability)));objective temporal_weight * high_bandwidth_bias * observation.num_packets;}}return objective;
}
5. 总结
与带宽一样固有丢包率inherent loss也是网路链路的一个属性而且是动态变化的。当观察到丢包的时候我们如何判断这是由于链路固有丢包率导致的丢包还是由于网络拥塞导致的丢包除非我们知道链路的固有丢包率和带宽但显然这是无法办到的。WebRTC 为解决这个问题打开了一扇窗其思路是建立网络丢包的二项式分布模型通过搜集足够多的观测值构造目标函数使用牛顿方法去搜索链路带宽和固有丢包率的最佳组合。然后对这个最佳组合进行必要的校正与调整。不过从 WebRTC 的实现来看调整算法太过复杂有理由相信通过算法得到的估计值可靠性不是非常高如何优化和简化这一部分的实现逻辑是一个挑战。