WebRTC服务质量(05)- 重传机制(02) NACK判断丢包
WebRTC服务质量(01)- Qos概述
WebRTC服务质量(02)- RTP协议
WebRTC服务质量(03)- RTCP协议
WebRTC服务质量(04)- 重传机制(01) RTX NACK概述
WebRTC服务质量(05)- 重传机制(02) NACK判断丢包
WebRTC服务质量(06)- 重传机制(03) NACK找到真正的丢包
一、前言:
上一篇介绍了NACK/RTX这种机制,注意,NACK是一种RTCP消息而已,本文结合代码看下WebRtc如何实现NACK机制的。
二、NACK格式:
2.1、RTPFB 消息头统一格式:
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+|V=2|P| FMT | PT | length |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| SSRC of packet sender |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| SSRC of media source |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+: Feedback Control Information (FCI) :
-
V: 版本号,占2位。
-
P: 填充位,占1位。
-
FMT(Feedback message type): 反馈消息类型,这里设为15表示 NACK。
-
PT: Payload Type,占8位,指示 RTPFB 包类型,205 表示 RTPFB。
-
Length: 长度字段,指示反馈消息长度,以 32 位字为单位。
-
Sender SSRC: 发送者同步信源,4字节。
-
Media SSRC: 媒体同步信源,4字节。
-
FCI(Feedback Control Information,反馈控制信息): 是RTCP报文的核心部分,包含各种反馈信息,可以帮助发送端及时调整或重传数据。
-
格式如下:
-
按照内容大致分为两大类:
-
RTPFB (RTP Feedback Messages): 针对RTP层的丢包检测和重传。
-
PSFB (Payload-Specific Feedback): 针对RTP净荷(Payload)层的增强反馈,主要用于处理更高层的问题,比如视频帧或切片的丢失。
-
-
2.2、RTPFB和PSFB:
- 典型的RTPFB控制消息 —— NACK(Negative Acknowledgement):
- 功能:接收端检测到有RTP数据包丢失后,通过NACK通知发送端重新发送丢失的RTP数据包。一般啥都不写就是这种传输机制。
- 应用场景:当网络质量较差但延迟要求比较高的场景,比如视频通话、实时流媒体等。
- 优点:粒度较小,可以精确地指出哪些RTP序列号丢失,有利于快速、精准地补偿丢包。
- 处理流程:
- 接收端发现一定范围的RTP序列号有丢失。
- 接收端发送RTCP报文中的NACK消息,带有丢失的RTP序列号信息。
- 发送端收到NACK后,针对性地重传丢失的RTP包。
- PSFB 是RTCP中的一个用于反馈净荷内容的框架,主要针对编码和媒体数据层面的重传控制。相比RTPFB,PSFB通常涉及更高层的媒体内容,比如整个视频帧或某种编码参考信息。PSFB进一步细化为以下三种主要类型:
- PLI (Picture Loss Indication) - 视频帧丢失重传
- 功能:
- 当接收端检测到关键帧(如I帧)丢失或破坏时,发送PLI消息给发送端,要求它重发一个完整的视频关键帧。
- 用途尤其体现在视频传输中,避免多帧由于关键帧丢失而无法解码。
- 处理流程:
- 接收端检测关键帧丢失或解码错误(比如画面突然坏块增多)。
- 接收端发送PLI信息给发送方。
- 发送端在收到PLI后,发送一个新的关键帧(通常是I帧)。
- 特点:
- 粒度较大,通常用于重要内容的恢复,比如视频关键帧丢失。
- 可能消耗更多带宽,因为完整的关键帧通常较大。
- 功能:
- SLI (Slice Loss Indication) - Slice丢失重传
- 功能:
- 反馈RTP流中某个视频切片(Slice)丢失的信息。
- 通常用于视频传输中某些特定的片段(非整个帧)的丢失导致部分画面无法解码。
- 处理流程:
- 接收端判断某个Slice数据丢失或破坏,发送SLI给对端。
- 发送端针对丢失Slice,通过数据包重传或替换的方式修复。
- 特点:
- 较之PLI,SLI的作用范围更小,仅针对部分切片,而不需要整帧重传。
- 带宽开销较低,但可能延迟较大,因为重传的粒度较细。
- 功能:
- RPSI (Reference Picture Selection Indication) - 参考帧丢失重传
- 功能:
- 当接收端检测到参考帧丢失(或者它依赖的解码参考无法使用时,如P帧无法解码),会反馈RPSI消息,建议发送端选择新的参考帧。
- 发送端可以根据RPSI调整编码或重发相关参考信息。
- 处理流程:
- 接收端通过解码检测或分析发现P帧等数据依赖的参考帧丢失或损毁。
- 接收端发送RPSI消息,建议使用新的参考帧。
- 发送端参考RPSI调整后续的编码策略,跳过丢失的参考帧并发送新的参考帧数据。
- 特点:
- 聚焦于“参考帧”的问题,对于影响范围有限的解码错误更加有效,避免过多的重传和带宽开销。
- 在视频编码中(如H.264、H.265),参考帧是P帧和B帧的编码基础,丢失的影响可能尤为严重。
- 功能:
- PLI (Picture Loss Indication) - 视频帧丢失重传
2.2.1、PLI和SLI和RPSI比较:
类型 | 粒度 | 应用场景 | 带宽开销 | 延迟影响 |
---|---|---|---|---|
PLI | 整帧 | 关键帧丢失,恢复整体画面 | 高 | 中等,需整帧重传 |
SLI | 切片 | 部分画面丢失,快速修复 | 低到中 | 较低,粒度更细 |
RPSI | 参考帧 | 参考帧错误,影响解码链 | 低 | 非重传型,调整编码策略 |
三、Call、Channel、Stream:
之前说过,Call、Channel、Stream这几个概念你是否还记得?
-
Session层:
- 一个
Stream
对应的是一个完整的媒体流,可以包含多个Track
。 - 一个
Track
表示流中的单一媒体轨道,例如音频轨道或视频轨道(类似于 WebRTC API 中的MediaStreamTrack
)。
- 一个
-
MediaEngine层:
- Channel 是进行音视频分类管理的基础单元。通常,音频和视频会分属于不同的 Channel(AudioChannel 和 VideoChannel)。
- Stream是音视频数据在 Channel 层中的更细化管理单元。
- 一个 Channel 通常会包含多个 Stream。
- 每个 Stream 不仅负责具体的音频或视频数据处理,还可以进一步分为发送(send)和接收(recv)的数据流。
MediaEngine
层中的 Stream 是底层实现,不再对应 Session 层中的逻辑 Stream,而是为传输和解码服务的独立实体。
关键点:
- 一个 Channel 的核心目的是管理一种媒介类型(音频或视频)。例如,一个音频 Channel 可以包含多个音频 Stream;一个视频 Channel 可以包含多个视频 Stream。
- 这些 Stream 分别表示 传输和接收方向的数据流。
-
Call层:
- 对于音频,引擎层的一个Stream就对应Call层的一个
SendStream
或者ReceiveStream
; - 对于音频,一个Stream中又有Channel,来连接编解码器;
- 对于视频,只有Stream对应引擎层的Stream,并没有channel的概念;
- 对于音频,引擎层的一个Stream就对应Call层的一个
三者的总结关系:
层次 | 作用 | 音频之间关系 | 视频之间关系 |
---|---|---|---|
Session | 管理逻辑 Stream 和其包含的 Track | 一个 Track (音轨)对应一个 Channel 。 | 一个 Track 映射为一个 Stream 。 |
MediaEngine | 处理底层音视频流管理,区分发送与接收 | 一个 Stream 对应 Call 层的 SendStream 或 ReceiveStream 。每个 Channel 中有若干 Stream 。 | 一个Stream 直接传到Call 层。 |
Call | 与用户操作逻辑一致,将 Stream 转化为最终发送/接收流 | Stream 对应 SendStream 或ReceiveStream ,还有Channel 连接编解码器。 | Stream 与用户的发送流或接收流一一对应。 |
视频 Channel 与 Stream图示:
Session 层:
+------------------+
| Stream |--- 同一个对等会话上的视频流逻辑。
| - Track (Video) |
+------------------+|
MediaEngine 层:
+------------------------+
| Channel (VideoChannel) |--- 管理多路视频数据
+------------------------+|+----> Stream 1 (Send方向)+----> Stream 2 (Recv).
Call 层:
+-------------------+
| SendStream |
| ReceiveStream |---- 视频输流的最外层封装接口
+-------------------+
四、NACK调用关系:
调用关系如下图:
- 看下调用顺序基本是:Channel -> Call -> Stream;
- 音频引擎那一节介绍过
RtpDemuxer
,就是数据分发器,总共两个地方用到:- 当收到RTP数据包的时候,通过
RtpDemuxer
分发给不同Channel
(音频是Channel或者视频是Stream); - 就是当前这个地方,分发给不同的Stream(每个Stream又连接着解码器);
- 分发给不同Stream时候,如果是正常包就分发给
RtpVideoStreamReceiver
,如果是RTX数据包,那么就分发给RtxReceiveStream
,当然,处理完之后还得继续发给RtpVideoStreamReceiver
;
- 当收到RTP数据包的时候,通过
- 当
OnReceivedPayloadData
收到数据包之后,就会判断数据包的间隔,如果间隔很大,那么就会调用NackModule::OnReceivedPacket
方法请求重传这部分包。
4.1、ReceivePacket:
我们直接从上述的RtpVideoStreamReceiver
模块看代码,在这个函数的时候,我们已经拿到的是视频的RTP包了。
- 得到Payload:
void RtpVideoStreamReceiver::ReceivePacket(const RtpPacketReceived& packet) {// ...// 正常数据包走下面// 从 payload_type_map_ 中根据pt找出RTP包的解包器const auto type_it = payload_type_map_.find(packet.PayloadType());if (type_it == payload_type_map_.end()) {return;}// 调用解包器的Parse方法对RTP数据包进行解析absl::optional<VideoRtpDepacketizer::ParsedRtpPayload> parsed_payload =type_it->second->Parse(packet.PayloadBuffer());if (parsed_payload == absl::nullopt) {RTC_LOG(LS_WARNING) << "Failed parsing payload.";return;}// 这样就拿到了Rtp的Payload,对payload进行处理OnReceivedPayloadData(std::move(parsed_payload->video_payload), packet,parsed_payload->video_header);
}
根据PT找到解包器,然后解包得到Payload。
- 处理Payload:
void RtpVideoStreamReceiver::OnReceivedPayloadData(rtc::CopyOnWriteBuffer codec_payload,const RtpPacketReceived& rtp_packet,const RTPVideoHeader& video) {// 根据入参构造一个Packet(后面会往里填其他项)auto packet = std::make_unique<video_coding::PacketBuffer::Packet>(rtp_packet, video, clock_->TimeInMilliseconds());// ...// 获取Video Header// 将视频的:角度、视频类型、是否为最后一个包,这几个参数设置到video_header当中RTPVideoHeader& video_header = packet->video_header;// ...// 如果是视频帧的最后一个包,获取并存储颜色空间(颜色空间信息只存在于最后一个包)if (video_header.is_last_packet_in_frame) {// ...}// 处理丢失找回的包if (loss_notification_controller_) {// ...}// 检测是否有丢包,将丢失的包记录下来(只是记录不会发送NACK,SendBufferedRtcpFeedback才会发送NACK)if (nack_module_) {// ...}// 处理H264的数据需要先更新pt以及pps和sps,可能还涉及"请求关键帧"、"丢包"、"正常拷贝数据"三个动作if (packet->codec() == kVideoCodecH264) {packet->video_payload = std::move(fixed.bitstream);} else {// 非H264的直接将payload拷贝到packet的payload即可packet->video_payload = std::move(codec_payload);}// 发送NACK给发送端rtcp_feedback_buffer_.SendBufferedRtcpFeedback();frame_counter_.Add(packet->timestamp);// 将payload data插入某一个帧当中(为组帧做好准备),packet_buffer_.InsertPacket会包含一帧的所有packetOnInsertedPacket(packet_buffer_.InsertPacket(std::move(packet)));
}
我删除了非常多的代码,否则,很难读明白,精简之后思路:
- 这个函数主要就是构造了一个packet,然后根据rtp_packet里面的信息来完善这个packet;
- 检测是否有丢包,将丢的包记录下来;
- 拷贝payload数据到packet里面;(注意,移动语义允许在不复制数据的情况下将资源所有权从一个对象转移到另一个对象,并非传统拷贝)
- 给发送端发送NACK;
- 最后将packet插入到packet_buffer_当中,凑齐了一帧所有packet,就可以给解码器去解码了;
- 重点看下刚才的
if (nack_module_)
部分:
// 检测是否有丢包,将丢失的包记录下来(只是记录不会发送NACK,SendBufferedRtcpFeedback才会发送NACK)if (nack_module_) {// 判断这个RTP包是否属于关键帧当中的一个包(是一个帧当中的第一个包,同时帧类型是视频关键帧)const bool is_keyframe =video_header.is_first_packet_in_frame &&video_header.frame_type == VideoFrameType::kVideoFrameKey;// 当知道了这个packet是否属于关键帧的包之后,在下面函数判断是否丢了包packet->times_nacked = nack_module_->OnReceivedPacket(rtp_packet.SequenceNumber(), is_keyframe, rtp_packet.recovered());} else {packet->times_nacked = -1;}
里面nack_module_->OnReceivedPacket
会判断是否有丢包。接下来看看。
4.2、OnReceivedPacket:
这函数又很长,咱拆飞机,研究零件吧。
1. 初始化模块
/*** 里面会判断是否有丢包*/
int DEPRECATED_NackModule::OnReceivedPacket(uint16_t seq_num,bool is_keyframe) {return OnReceivedPacket(seq_num, is_keyframe, false);
}int DEPRECATED_NackModule::OnReceivedPacket(uint16_t seq_num,bool is_keyframe,bool is_recovered) {if (!initialized_) {newest_seq_num_ = seq_num;if (is_keyframe)keyframe_list_.insert(seq_num);initialized_ = true;return 0;}
}
- 主要功能: 只在接收到的第一个包时执行。初始化
newest_seq_num_
为当前包的序列号,同时保存第一个关键帧(如果该包是关键帧)。 - 关键点:第一次初始化函数,只需要简化处理,本次包被记录后直接退出,不做其它操作。
2. 乱序包处理模块
if (seq_num == newest_seq_num_)return 0;if (AheadOf(newest_seq_num_, seq_num)) {auto nack_list_it = nack_list_.find(seq_num);if (nack_list_it != nack_list_.end()) {nacks_sent_for_packet = nack_list_it->second.retries;nack_list_.erase(nack_list_it);}if (!is_retransmitted)UpdateReorderingStatistics(seq_num);return nacks_sent_for_packet;
}
- 主要功能:
- 检查收到的包是否为重复包(
seq_num == newest_seq_num_
)或者乱序包(AheadOf
函数判断包是否比最新序列号旧)。 - 乱序包的行动:如果乱序包在
nack_list_
中,说明之前被判断为丢失,已请求重传,此时从nack_list_
中删除,因为该丢包实际上已经被恢复。
- 检查收到的包是否为重复包(
- 关键点:
- 当接收到乱序包时,通过清理对应的 NACK 请求可防止无意义的重传。
3. 新包到达模块
if (is_keyframe)keyframe_list_.insert(seq_num);auto it = keyframe_list_.lower_bound(seq_num - kMaxPacketAge);
if (it != keyframe_list_.begin())keyframe_list_.erase(keyframe_list_.begin(), it);
- 主要功能:
- 当新包到达时,如果是关键帧,则记录关键帧的序号。
- 同时清理超出
kMaxPacketAge
(值是10000)范围的历史关键帧序号,避免列表的无限增长。
4. 丢包列表管理
AddPacketsToNack(newest_seq_num_ + 1, seq_num);
newest_seq_num_ = seq_num;
- 主要功能:
- 检查当前包与上次接收的包之间是否存在包丢失(通过包序号差距判断)。调用
AddPacketsToNack
将中间的丢包插入到nack_list_
中。 - 更新
newest_seq_num_
,确保下次处理时以最新接收的包为基准。
- 检查当前包与上次接收的包之间是否存在包丢失(通过包序号差距判断)。调用
5. NACK 批量发送
std::vector<uint16_t> nack_batch = GetNackBatch(kSeqNumOnly);
if (!nack_batch.empty()) {nack_sender_->SendNack(nack_batch, /*buffering_allowed=*/true);
}
- 主要功能:
- 构造一个丢失包序号的批量列表(
nack_batch
),并将这些序号通过 NACK 消息发送给远端。 GetNackBatch
函数会筛选出真正需要 NACK(仍未恢复)的丢失包。
- 构造一个丢失包序号的批量列表(
小结:
有点复杂,小结一下:
- 初始化:
- 在接收到的第一个 RTP 数据包时,初始化
newest_seq_num_
(记录最近成功接收的 RTP 包序列号),同时判断该包是否为关键帧(Keyframe),如果是则记录在keyframe_list_
中。 - 这是 NACK 模块的第一步,之后才能对后续到达的 RTP 数据包构建更加完整和正确的包状态跟踪。
- 在接收到的第一个 RTP 数据包时,初始化
- 重复包检查:
- 对于重复收到的包(当前序列号等于
newest_seq_num_
),可以直接忽略,因为它已经被记录为接收成功。
- 对于重复收到的包(当前序列号等于
- 乱序包处理:
- 如果接收到的包编号比上一次记录的最新序列号小,说明该包是一个迟到的乱序包。如果乱序的包在
nack_list_
(NACK 缓存列表)中,说明它之前被认为是丢包并请求重传,此时需要从nack_list_
中删除,因为它已经补到了。
- 如果接收到的包编号比上一次记录的最新序列号小,说明该包是一个迟到的乱序包。如果乱序的包在
- 新包处理和 NACK 填充:
- 对于序号比
newest_seq_num_
更新的包,需要更新newest_seq_num_
,并检查中间遗漏的包(即从上一包到当前包之间的差值),将这些丢包插入到nack_list_
中。 - 同时,只保留一定范围(
kMaxPacketAge
)内的 NACK 请求,过于久远的序列号被认为无法恢复,注意,虽然这个宏是10000,但是指的是RTP包的序列号差距,并不是差这么多视频帧,差这么多视频帧体验就很差了。
- 对于序号比
- 关键帧和恢复包:
- 关键帧和恢复包(FEC 或 RTX 恢复的包)被单独处理和记录,不会请求 NACK,因为这些包有特殊的作用。
- 最终丢包确认(NACK 批量发送):
- 分析确定哪些包是真正丢失没有恢复的,通过调用
GetNackBatch
构造 NACK 请求批量发送,通知发送端重传这些丢失的包。
- 分析确定哪些包是真正丢失没有恢复的,通过调用
4.3、AddPacketsToNack:
这个是根据包序号判断哪些包丢了,归纳功能如下:
- 记录丢包: 当检测到某些数据包丢失时,将这些丢包的序号记录进
nack_list_
,等待后续判断和处理。 - 限制管理: 控制
nack_list_
的尺寸,防止超出最大容量,同时根据策略清理不必要的条目。 - 识别特殊包: 对于通过其他方式(例如 FEC/RTX)找回的包无需生成 NACK 条目。
// 拿到这个包到上一次的包newest_seq_num_中间所有丢失的包;
// 这些包有可能是乱序(假丢包),也有可能是真丢包,就可以用 GetNackBatch 判断
void DEPRECATED_NackModule::AddPacketsToNack(uint16_t seq_num_start,uint16_t seq_num_end) {// Remove old packets.auto it = nack_list_.lower_bound(seq_num_end - kMaxPacketAge);nack_list_.erase(nack_list_.begin(), it);// If the nack list is too large, remove packets from the nack list until// the latest first packet of a keyframe. If the list is still too large,// clear it and request a keyframe.uint16_t num_new_nacks = ForwardDiff(seq_num_start, seq_num_end);if (nack_list_.size() + num_new_nacks > kMaxNackPackets) {while (RemovePacketsUntilKeyFrame() &&nack_list_.size() + num_new_nacks > kMaxNackPackets) {}if (nack_list_.size() + num_new_nacks > kMaxNackPackets) {nack_list_.clear();RTC_LOG(LS_WARNING) << "NACK list full, clearing NACK"" list and requesting keyframe.";keyframe_request_sender_->RequestKeyFrame();return;}}// 接下来就是将可疑丢包找到for (uint16_t seq_num = seq_num_start; seq_num != seq_num_end; ++seq_num) {// Do not send nack for packets that are already recovered by FEC or RTXif (recovered_list_.find(seq_num) != recovered_list_.end())continue;NackInfo nack_info(seq_num, seq_num + WaitNumberOfPackets(0.5),clock_->TimeInMilliseconds());RTC_DCHECK(nack_list_.find(seq_num) == nack_list_.end());nack_list_[seq_num] = nack_info;}
}
老规矩,分段看下:
1. 清理老旧记录(防止 nack_list 过大):
auto it = nack_list_.lower_bound(seq_num_end - kMaxPacketAge);
nack_list_.erase(nack_list_.begin(), it);
使用 lower_bound
找到 seq_num_end - kMaxPacketAge
的边界,将过于久远的丢包条目从 nack_list_
中删除。
2. 限制丢包列表大小:
uint16_t num_new_nacks = ForwardDiff(seq_num_start, seq_num_end);
if (nack_list_.size() + num_new_nacks > kMaxNackPackets) {while (RemovePacketsUntilKeyFrame() && nack_list_.size() + num_new_nacks > kMaxNackPackets) { }if (nack_list_.size() + num_new_nacks > kMaxNackPackets) {nack_list_.clear();keyframe_request_sender_->RequestKeyFrame();return;}
}
- 检查
nack_list_
的当前大小和即将添加的条目是否会超过最大容量。 - 策略:
- 优先通过
RemovePacketsUntilKeyFrame()
清理到最新关键帧之前的 NACK 条目。 - 如果清理后仍超上限,则完全清空
nack_list_
并请求关键帧。
- 优先通过
3. 记录丢包(创建 NackInfo 条目):
for (uint16_t seq_num = seq_num_start; seq_num != seq_num_end; ++seq_num) {if (recovered_list_.find(seq_num) != recovered_list_.end()) continue;NackInfo nack_info(seq_num, seq_num + WaitNumberOfPackets(0.5),clock_->TimeInMilliseconds());nack_list_[seq_num] = nack_info;
}
- 根据
seq_num
逐个检查这些包是否已通过 FEC 或 RTX 恢复。如果恢复过,则跳过。 - 创建
NackInfo
记录丢包的序号和请求重传的时间等信息。
小结:
是 NACK 机制的核心,用于记录丢包并追踪其状态,并限制 NACK 列表的大小。
五、总结:
本文主要介绍了NACK的格式,以及NACK的调用栈,并且介绍了如何判断丢包,但请记住,这些包都是“可疑丢包”,真正的丢包下一节介绍。
相关文章:

WebRTC服务质量(05)- 重传机制(02) NACK判断丢包
WebRTC服务质量(01)- Qos概述 WebRTC服务质量(02)- RTP协议 WebRTC服务质量(03)- RTCP协议 WebRTC服务质量(04)- 重传机制(01) RTX NACK概述 WebRTC服务质量(…...
修改ubuntu apt 源及apt 使用
视频教程:修改ubuntu apt 源和apt 使用方法_哔哩哔哩_bilibili 1 修改apt源 1.1 获取阿里云ubuntu apt 源 https://developer.aliyun.com/mirror/ubuntu?spma2c6h.13651102.0.0.3e221b11mqqLBC 1.2 修改apt 源 vim /etc/apt/sources.list deb https://mirrors.aliyun.com/ub…...
深入解析 `DataFrame.groupby` 和 `agg` 的用法及使用场景
深入解析 DataFrame.groupby 和 agg 的用法及使用场景 1. groupby 的基本用法语法:示例: 2. agg 的基本用法语法:示例: 3. first、sum、lambda 的用法3.1 first示例: 3.2 sum示例: 3.3 lambda示例ÿ…...
MySQL 的锁
MySQL有哪些锁?各种锁的作用与使用场景全局锁表级锁表锁元素锁意向锁AUTO-INC 锁 行级锁记录锁间隙锁临键锁 其他共享锁排他锁乐观锁悲观锁 MySQL有哪些锁? 全局锁表级锁 a. 表锁 b. 元素锁 c. 意向锁 d. AUTO-INC 锁行级锁 a. 记录锁 b. 间隙锁 c. 临键锁 各种锁的作用与使…...

二、使用langchain搭建RAG:金融问答机器人--数据清洗和切片
选择金融领域的专业文档作为源文件 这里选择 《博金大模型挑战赛-金融千问14b数据集》,这个数据集包含若干公司的年报,我们将利用这个年报搭建金融问答机器人。 具体下载地址 这里 git clone https://www.modelscope.cn/datasets/BJQW14B/bs_challenge_…...
【Linux】-- linux 配置用户免密登录本机
比如我们要配置用户 app_tom 免密登录本机(SSH 登录自己机器时无需输入密码),你可以按照以下步骤操作: 步骤 1:切换到 app_tom 用户 首先,确保你已经以 app_tom 用户登录,或者切换到该用户&…...

泷羽sec学习打卡-brupsuite8伪造IP和爬虫审计
声明 学习视频来自B站UP主 泷羽sec,如涉及侵权马上删除文章 笔记的只是方便各位师傅学习知识,以下网站只涉及学习内容,其他的都 与本人无关,切莫逾越法律红线,否则后果自负 关于brupsuite的那些事儿-Brup-FaskIP 伪造IP配置环境brupsuite导入配置1、扩展中先配置python环境2、安…...
【uniapp蓝牙】基于native.js链接ble和非ble蓝牙
【uniapp蓝牙】基于native.js链接ble和非ble蓝牙 uniapp不是仅支持低功耗蓝牙(基础蓝牙通讯不支持),有些可能需要基础蓝牙。我现在同步我的手机蓝牙列表低功耗,基础蓝牙都支持 /*** author wzj* 通用蓝牙模块封装* 搜索 ble 和非…...
.NET Core 各版本特点、差异及适用场景详解
随着 .NET Core 的不断发展,微软推出了一系列版本来满足不同场景下的开发需求。这些版本随着时间的推移逐渐演变为统一的 .NET 平台(从 .NET 5 开始)。本文将详细说明每个版本的特点、差异以及适用场景,帮助开发者更好地选择和使用…...
Linux中自动检测并定时关闭KDialog程序
自动检测并关闭对话框的程序示例 创建并打开KDialog的脚本自动检测并定时关闭KDialog的脚本 创建并打开KDialog的脚本 #!/bin/bash kdialog --msgbox "demo"自动检测并定时关闭KDialog的脚本 #!/bin/bash# Continuously check for kdialog dialog while true; do# …...

CSS学习记录12
CSS浮动 CSSfloat属性规定元素如何浮动 CSSclear属性规定哪些元素可以在清除的元素旁边以及在哪一侧浮动。 float属性 float属性用于定位和格式化内容,例如让图像向左浮动到容器的文本那里。 float属性可以设置以下值之一: left - 元素浮动到其容器…...
【Java基础面试题016】JavaObject类中有什么主要方法,作用是什么?
equals() 作用:用于比较两个对象是否相等。默认实现比较对象的内存地址,即判断两个引用是否指向同一个对象 使用:通常会重写此方法来比较对象的内容 hashCode() 作用:返回对象的哈希值,用整数表示对象。 使用&…...

实践环境-docker安装mysql8.0.40步骤
一、docker安装mysql 8.0.40版本 1、检索镜像版本 docker search mysql:8.0.40 NAME DESCRIPTION STARS OFFICIAL mysql MySQL is a widely used, open-source relation… …...

边缘智能创新应用大赛获奖作品系列一:智能边缘计算✖软硬件一体化,开启全场景效能革命新征程
边缘智能技术快速迭代,并与行业深度融合。它正重塑产业格局,催生新产品、新体验,带动终端需求增长。为促进边缘智能技术的进步与发展,拓展开发者的思路与能力,挖掘边缘智能应用的创新与潜能,高通技术公司联…...

决策树的生成与剪枝
决策树的生成与剪枝 决策树的生成生成决策树的过程决策树的生成算法 决策树的剪枝决策树的损失函数决策树的剪枝算法 代码 决策树的生成 生成决策树的过程 为了方便分析描述,我们对上节课中的训练样本进行编号,每个样本加一个ID值,如图所示…...
蓝桥杯算法训练 黑色星期五
题目描述 有些西方人比较迷信,如果某个月的13号正好是星期五,他们就会觉得不太吉利,用古人的说法,就是“诸事不宜”。请你编写一个程序,统计出在某个特定的年份中,出现了多少次既是13号又是星期五的情形&am…...

MySQL存储引擎-存储结构
Innodb存储结构 Buffer Pool(缓冲池):BP以Page页为单位,页默认大小16K,BP的底层采用链表数据结构管理Page。在InnoDB访问表记录和索引时会在Page页中缓存,以后使用可以减少磁盘IO操作,提升效率。 ○ Page根据状态可以分…...
理解torch函数bmm
基本信息 功能描述 torch.bmm 是 PyTorch 中的一个函数,用于执行批量矩阵乘法(Batch Matrix Multiplication)。它适用于处理一批矩阵的乘法操作,特别适合于深度学习任务中的场景,比如卷积神经网络中的某些层。 参数…...

2024 年的科技趋势
2024 年在科技领域有着诸多重大进展与突破。从人工智能、量子计算到基因组医学、可再生能源以及新兴技术重塑了众多行业。随着元宇宙等趋势的兴起以及太空探索取得的进步,未来在接下来的岁月里有望继续取得进展与突破。让我们来探讨一下定义 2024 年的一些关键趋势&…...

win服务器的架设、windows server 2012 R2 系统的下载与安装使用
文章目录 windows server 2012 R2 系统的下载与安装使用1 windows server 2012 的下载2 打开 VMware 虚拟机软件(1)新建虚拟机(2)设置虚拟机(3)打开虚拟机 windows server 2012(4)进…...

Flask RESTful 示例
目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题: 下面创建一个简单的Flask RESTful API示例。首先,我们需要创建环境,安装必要的依赖,然后…...

Unity3D中Gfx.WaitForPresent优化方案
前言 在Unity中,Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染(即CPU被阻塞),这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案: 对惹,这里有一个游戏开发交流小组&…...

基于Flask实现的医疗保险欺诈识别监测模型
基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施,由雇主和个人按一定比例缴纳保险费,建立社会医疗保险基金,支付雇员医疗费用的一种医疗保险制度, 它是促进社会文明和进步的…...

dedecms 织梦自定义表单留言增加ajax验证码功能
增加ajax功能模块,用户不点击提交按钮,只要输入框失去焦点,就会提前提示验证码是否正确。 一,模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...

全球首个30米分辨率湿地数据集(2000—2022)
数据简介 今天我们分享的数据是全球30米分辨率湿地数据集,包含8种湿地亚类,该数据以0.5X0.5的瓦片存储,我们整理了所有属于中国的瓦片名称与其对应省份,方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...

【CSS position 属性】static、relative、fixed、absolute 、sticky详细介绍,多层嵌套定位示例
文章目录 ★ position 的五种类型及基本用法 ★ 一、position 属性概述 二、position 的五种类型详解(初学者版) 1. static(默认值) 2. relative(相对定位) 3. absolute(绝对定位) 4. fixed(固定定位) 5. sticky(粘性定位) 三、定位元素的层级关系(z-i…...

ElasticSearch搜索引擎之倒排索引及其底层算法
文章目录 一、搜索引擎1、什么是搜索引擎?2、搜索引擎的分类3、常用的搜索引擎4、搜索引擎的特点二、倒排索引1、简介2、为什么倒排索引不用B+树1.创建时间长,文件大。2.其次,树深,IO次数可怕。3.索引可能会失效。4.精准度差。三. 倒排索引四、算法1、Term Index的算法2、 …...

自然语言处理——循环神经网络
自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元(GRU)长短期记忆神经网络(LSTM)…...
Mobile ALOHA全身模仿学习
一、题目 Mobile ALOHA:通过低成本全身远程操作学习双手移动操作 传统模仿学习(Imitation Learning)缺点:聚焦与桌面操作,缺乏通用任务所需的移动性和灵活性 本论文优点:(1)在ALOHA…...

算法:模拟
1.替换所有的问号 1576. 替换所有的问号 - 力扣(LeetCode) 遍历字符串:通过外层循环逐一检查每个字符。遇到 ? 时处理: 内层循环遍历小写字母(a 到 z)。对每个字母检查是否满足: 与…...