全網首創/純Qt/C++實現國標GB28181服務/實時視頻/云臺控制/預置位/錄像回放和下載/事件訂閱/語音對講

一、前言說明

用純Qt來實現這個GB28181的想法很久了,具體可以追溯到2014年,一晃十年都過去了,總算是整體的框架和邏輯都打通了,總歸還是雜七雜八的事情多,無法靜下心來研究具體的協議,最開始初步了解協議后發現比onvif要復雜不少,索性先擱置一旁,所以先把onvif協議打通了,onvif協議好是好,但是一般在局域網內使用,外網訪問幾乎沒有辦法,而GB28181就是為了解決很多痛點定義的一套視頻監控規范,畢竟現在滿大街都是監控,各個部門機構都要外網遠程取流,這就必須上國標,這其實是網絡通信的弊端,服務端在沒有收到過客戶端的消息的時候,是無法得知客戶端的具體通信地址,也就無法通信,需要先客戶端主動給服務器發過消息才行。

關于sip協議的第三方庫已經很多了,最終還是決定采用Qt底層的udp通信協議來解析,一方面可以大大加深協議的理解,提供友好的使用接口,不用大費周章的去編譯各種第三方庫,一方面考慮到后期的拓展,必須從底層手擼,一定要把兼容性易用性擺在第一位,這個最重要的點,也只有解決了客戶的痛點,才能更好的賣錢。

1.1 個人理解

  1. gb2818協議是基于sip協議的一套協議框架,而sip是和http協議類似的一套基于udp/tcp載體的協議,最終底層通過udp/tcp收發數據。sip是一套多媒體通信框架,而gb28181就是在這套框架上,定義了具體的通信內容,也就是收發數據填充的內容。
  2. sip協議收發通常用的第三方開源庫有osip/exosip,exosip依賴osip,是對osip的二次封裝,帶了具體傳輸層,osip相當于用來構建要收發的數據,本身沒有收發功能。還有個更強大開源庫pjsip,帶了rtp解析。具體可參考 https://blog.csdn.net/weixin_43147845/article/details/144219082
  3. sip協議說復雜也復雜,說簡單也簡單,復雜就是涉及到的具體協議規約特別龐大,和http協議是并列的一種機制。簡單就是如果只是少量的通信,可以直接用udp/tcp通信收發解析,要發送的數據每一行都回車換行,最后一起發出去即可。就是發送和解析不大方便,需要去尋找和取出關鍵字再處理,而開源庫會給你處理好對應的數據結構,比如解析后有枚舉字段直接判斷。
  4. onvif是國際標準協議,gb28181是國家標準協議,各有優缺點,onvif通常是通過搜索到設備再去和設備通信,而gb28181剛好相反,是讓設備主動連服務器,帶上校驗等參數,連上后,服務器再去和設備通信。這樣相當于可以跨網了,而onvif通常只能局域網。gb28181還有個優勢就是組網,可以層層級聯,非常適合國內各種大監控系統的建設。
  5. onvif客戶端是先udp組播獲取到設備,然后再發送http請求到不同的地址來交互,請求中可以獲取到視頻流rtsp地址,然后自己用ffmpeg等框架打開這個地址播放就行。而gb28181服務端是先udp/tcp監聽端口,對連上的設備進行sip協議格式的數據內容的交互,發送請求播放指令后,開啟發送rtp數據包,再用ffmpeg等框架解析這個數據包就行。
  6. gb28181注冊的時候Expires為有效期時間,一般是3600秒,如果是0則表示注銷。

1.2 要點總結

  1. gb28181協議一般會選擇udp通信,默認也是udp,早期國標設備都是只支持udp。
  2. 服務端開啟端口監聽,設備端填寫好對應參數后,會嘗試往對應端口發數據進行連接。
  3. 設備端間隔(心跳間隔默認是60s)發送REGISTER信令,服務端收到后,分析數據中是否帶了鑒權信息(也就是用戶認證相關信息),沒有帶的話則應答Unauthorized,帶了的話,可以取出認證的信息,和要求的參數對比,比如國標服務端編號、認證密碼、域編碼信息,不一致則應答信息錯誤,叫客戶端重新發。都沒問題則表示認證通過。
  4. 認證后服務端發送MESSAGE信令,帶上xml數據,獲取設備信息,收到設備信息后,再去查閱目錄也就是獲取通道列表。
  5. 設備端在注冊成功后,每隔一段時間發送一次心跳信息。方便服務端判斷是否離線。
  6. 設備端一旦注冊成功,在有效期(一般是3600s)內,不會再去注冊,默認就是認為已經注冊成功。所以有時候服務端這邊開啟服務后,未必先收到REGISTER注冊指令,而是可能先收到的心跳指令,所以服務端這邊要做個特殊處理,收到心跳后,先判斷該設備在系統中是否已經存在,不存在則先獲取設備信息,再去獲取通信列表。
  7. 服務端支持多個設備注冊,通過設備編號區分,嚴格要求同一個系統中設備編號不能重復,否則容易錯亂。
  8. 每個設備都可以有多個視頻通道,一般攝像頭IPC只有1個通道,錄像機NVR有多個通道。如果是國標級聯,相當于把服務端當做一臺NVR設備,每個設備的通道都轉換成唯一標識的通道。
  9. 點播視頻和云臺控制等,都有個前提是要先獲取到對應的通道列表,因為下發的數據中就要指定是哪個通道。
  10. 點播視頻是服務端向設備端通過發送INVITE信令,帶上sdp數據(具體sdp格式規范在gb28181-2016文檔的第100頁),sdp數據中包含了通道編號、音視頻格式、音視頻數據如何交互等信息。
  11. 服務端點播視頻前,先要打開一個空閑的端口,這個端口號在sdp數據中帶上,設備端收到點播指令后,會將音視頻數據發給這個指定的端口,收到這些數據后再去用ffmpeg解碼播放即可。sip這邊只負責信令交互,并不負責音視頻數據的通信。
  12. 關閉視頻很關鍵,因為可能開了多個點播窗口,所以需要在點播視頻后應答的ACK指令數據中,記住當時信令中的from/to/callid數據,在關閉視頻的時候用播放時對應的這幾個數據發給設備端,才能真正停止。
  13. 一個設備可能有多個通道,一個通道可能存在多個點播流,每個流都對應唯一的端口,所以需要有個隊列記住這些點播流對應的ssrc/from/to/callid數據,可以指定關閉某一路流。
  14. 點播流需要對應端口接收流,一般這個端口需要動態分配,也可以不同流公用一個端口,公用端口不用擔心數據會沖突,里面都是rtp的數據包,通過ssrc區分是哪一個流的數據包,這個ssrc是由點播發起者下發的,在sip指令中附加在subject屬性上,sdp中有個y屬性專門放這個ssrc字符串。在端口數量允許的情況下,一般建議每一路流都不同的端口,方便區分管理。
  15. 點播流的過程,一般第一步是先打開監聽端口成功后,然后才將這個端口通過sip指令發給設備,因為端口有可能被占用,所以只有當打開監聽端口成功的時候,再去點播流,這樣才是通的,不然也是白搭。
  16. 語音對講和點播視頻流程不一樣,是反著來的,先服務端發送語音廣播指令Broadcast到設備,設備返回是否支持語音對講,如果支持,會主動發送INVITE信令,帶上sdp數據,服務端搜到這個sdp數據后解析,然后服務端主動往設備對應的端口發送帶了語音數據的RTP數據包,設備端的聲音會通過之前的音視頻流傳輸過來。
  17. 云臺控制和預置位相關處理就簡單一些,因為都是單向操作。通過MESSAGE信令帶上xml數據,數據中包含了要執行的通道編號和動作,這個動作的數據,是一個標準的固定長度8字節,16進制字符串數據格式,比如A5 0F 01 00 00 00 00 00,將要執行的動作替換對應的數據位即可,停止云臺也是一個單獨的動作。
  18. ffmpeg中并不能直接解碼RTP數據包,需要解包后才是PS流才可以正確的解碼,一般會用第三方開源庫jrtp去實現解包,當然他也支持封包,發送語音數據的時候也要用到,jrtp直接就是帶了網絡通信,比如監聽UDP端口收數據。
  19. 在信令交互過程中,可以多一些無關的數據,但是不能少一些必要的字段數據,比如invite信令必須帶有Subject,缺少的話無法正常解析導致失敗。

二、效果圖

在這里插入圖片描述
在這里插入圖片描述

三、相關代碼

#include "frmconfig.h"
#include "frmserver.h"
#include "ui_frmserver.h"
#include "qthelper.h"
#include "gb28181server.h"
#include "gb28181helper.h"frmServer::frmServer(QWidget *parent) : QWidget(parent), ui(new Ui::frmServer)
{ui->setupUi(this);this->initForm();this->initConfig();
}frmServer::~frmServer()
{delete ui;
}void frmServer::closeEvent(QCloseEvent *)
{if (server) {server->stop();}qApp->quit();
}bool frmServer::eventFilter(QObject *watched, QEvent *event)
{if (watched == ui->txtData->viewport() && event->type() == QEvent::MouseButtonDblClick) {this->appendMsg(0, "", true);}return QWidget::eventFilter(watched, event);
}void frmServer::initForm()
{QtHelper::replaceCRLF = false;ui->widgetControl->setEnabled(false);ui->widget->setFixedWidth(AppData::RightWidth);ui->txtData->viewport()->installEventFilter(this);ui->treeWidget->setAnimated(false);ui->treeWidget->setIndentation(15);ui->treeWidget->setExpandsOnDoubleClick(false);connect(ui->tabPreview, SIGNAL(selectVideo(QString, QString)), this, SLOT(selectVideo(QString, QString)));//立即啟動服務server = NULL;if (AppConfig::ServerStart) {on_btnStart_clicked();}
}void frmServer::initConfig()
{ui->tabWidget->setCurrentIndex(AppConfig::TabIndex);connect(ui->tabWidget, SIGNAL(currentChanged(int)), this, SLOT(saveConfig()));ui->cboxDevice->addItem("0.0.0.0");ui->cboxDevice->lineEdit()->setText(AppConfig::FilterHost);connect(ui->cboxDevice->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(saveConfig()));
}void frmServer::saveConfig()
{AppConfig::TabIndex = ui->tabWidget->currentIndex();AppConfig::FilterHost = ui->cboxDevice->lineEdit()->text();AppConfig::writeConfig();
}void frmServer::appendMsg(int type, const QString &data, bool clear, bool pause)
{//最大行數和當前行數static int maxCount = 200;static int currentCount = 0;QtHelper::appendMsg(ui->txtData, type, data, maxCount, currentCount, clear, pause);
}void frmServer::sendData(const QString &host, int port, const QString &data)
{if (AppConfig::FilterHost != "0.0.0.0" && AppConfig::FilterHost != host) {return;}this->appendMsg(0, data);
}void frmServer::receiveData(const QString &host, int port, const QString &data)
{if (AppConfig::FilterHost != "0.0.0.0" && AppConfig::FilterHost != host) {return;}this->appendMsg(1, data);
}void frmServer::receiveInfo(const QString &host, int port, const QString &info)
{if (AppConfig::FilterHost != "0.0.0.0" && AppConfig::FilterHost != host) {return;}this->appendMsg(2, info);ui->txtData->append("\n");
}void frmServer::deviceChanged(const QString &deviceId, bool online)
{QList<GB28181Device> devices = server->getDevices();GB28181Device device = GB28181Helper::getDevice(deviceId, devices);QString text = deviceId + " [" + device.deviceName + "]";int count = ui->treeWidget->topLevelItemCount();for (int i = 0; i < count; ++i) {QTreeWidgetItem *item = ui->treeWidget->topLevelItem(i);if (item->data(0, Qt::UserRole).toString() == deviceId) {item->setText(0, text);item->setDisabled(!online);return;}}//不存在則添加頂層節點QTreeWidgetItem *item = new QTreeWidgetItem;item->setText(0, text);item->setData(0, Qt::UserRole, deviceId);ui->treeWidget->insertTopLevelItem(0, item);//添加到下拉框QString ip = device.deviceIp;if (ui->cboxDevice->findText(ip) < 0) {ui->cboxDevice->addItem(ip);}
}void frmServer::channelChanged(const QString &deviceId)
{//每次都清空通道再重新添加QList<GB28181Device> devices = server->getDevices();int count = ui->treeWidget->topLevelItemCount();for (int i = 0; i < count; ++i) {QTreeWidgetItem *item = ui->treeWidget->topLevelItem(i);if (item->data(0, Qt::UserRole).toString() != deviceId) {continue;}qDeleteAll(item->takeChildren());QStringList ids, names;GB28181Helper::getChannelInfo(deviceId, devices, ids, names);for (int j = 0; j < ids.count(); ++j) {QTreeWidgetItem *child = new QTreeWidgetItem(item);child->setText(0, ids.at(j) + " [" + names.at(j) + "]");child->setData(0, Qt::UserRole, ids.at(j));}break;}//展開所有節點ui->treeWidget->expandAll();//自動調整列寬ui->treeWidget->resizeColumnToContents(0);
}void frmServer::on_btnStart_clicked()
{this->appendMsg(0, "", true);if (ui->btnStart->text() == "啟動服務") {server = new GB28181Server;connect(server, SIGNAL(sendData(QString, int, QString)), this, SLOT(sendData(QString, int, QString)));connect(server, SIGNAL(receiveData(QString, int, QString)), this, SLOT(receiveData(QString, int, QString)));connect(server, SIGNAL(receiveInfo(QString, int, QString)), this, SLOT(receiveInfo(QString, int, QString)));connect(server, SIGNAL(deviceChanged(QString, bool)), this, SLOT(deviceChanged(QString, bool)));connect(server, SIGNAL(channelChanged(QString)), this, SLOT(channelChanged(QString)));GB28181ServerPara para;para.serverId = AppConfig::ServerId;para.serverArea = AppConfig::ServerArea;para.serverHost = AppConfig::ServerHost;para.serverIp = AppConfig::ServerIp;para.serverPort = AppConfig::ServerPort;para.serverPwd = AppConfig::ServerPwd;server->setServerPara(para);ui->btnStart->setText("停止服務");ui->tabPreview->setServer(server);ui->widgetControl->setEnabled(true);ui->widgetControl->setServer(server);} else {server->stop();server->deleteLater();server = NULL;ui->treeWidget->clear();ui->treeWidget->resizeColumnToContents(0);ui->btnStart->setText("啟動服務");ui->widgetControl->setEnabled(false);ui->widgetControl->setId("", "");GB28181Server::port = 6900;}AppConfig::ServerStart = (ui->btnStart->text() == "停止服務");AppConfig::writeConfig();
}void frmServer::on_btnConfig_clicked()
{static frmConfig *config = new frmConfig;config->show();config->activateWindow();
}void frmServer::selectVideo(const QString &deviceId, const QString &channelId)
{//先取消所有選中QTreeWidgetItemIterator it(ui->treeWidget);while (*it) {(*it)->setSelected(false);++it;}//視頻通道按下自動選中設備數通道節點int count = ui->treeWidget->topLevelItemCount();for (int i = 0; i < count; ++i) {QTreeWidgetItem *item = ui->treeWidget->topLevelItem(i);if (item->data(0, Qt::UserRole).toString() != deviceId) {continue;}for (int j = 0; j < item->childCount(); ++j) {QTreeWidgetItem *itemChild = item->child(j);if (itemChild->data(0, Qt::UserRole).toString() == channelId) {itemChild->setSelected(true);on_treeWidget_itemClicked(itemChild, 0);break;}}}
}void frmServer::getId(QTreeWidgetItem *item, QString &deviceId, QString &channelId)
{if (item->parent()) {deviceId = item->parent()->data(0, Qt::UserRole).toString();channelId = item->data(0, Qt::UserRole).toString();} else {deviceId = item->data(0, Qt::UserRole).toString();//自動取第一個子節點if (item->childCount() > 0) {channelId = item->child(0)->data(0, Qt::UserRole).toString();}}
}void frmServer::on_treeWidget_itemClicked(QTreeWidgetItem *item, int)
{QString deviceId, channelId;this->getId(item, deviceId, channelId);ui->widgetControl->setId(deviceId, channelId);
}void frmServer::on_treeWidget_itemDoubleClicked(QTreeWidgetItem *item, int)
{QString deviceId, channelId;this->getId(item, deviceId, channelId);ui->widgetControl->setId(deviceId, channelId);ui->tabPreview->openVideo(deviceId, channelId);
}

四、相關地址

  1. 國內站點:https://gitee.com/feiyangqingyun
  2. 國際站點:https://github.com/feiyangqingyun
  3. 個人作品:https://blog.csdn.net/feiyangqingyun/article/details/97565652
  4. 文件地址:https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g 提取碼:01jf 文件名:bin_video_gb28181。

五、功能特點

  1. 支持設備注冊、注銷、用戶認證、獲取設備狀態和信息等。
  2. 視頻點播,可以分別點播主碼流和子碼流,內置rtp解包線程,解包后發給視頻播放組件解碼播放。
  3. 每個設備每個通道支持點播多個視頻,通過ssrc區分,支持公用端口和不同端口。
  4. 云臺控制,各個方位移動,鏡頭放大縮小,光圈放大縮小,鏡頭聚焦放焦。
  5. 純Qt底層代碼實現,udp通信交互,原創代碼解析,不依賴任何第三方。
  6. 代碼量少,gb28181交互部分共幾千行代碼,注釋詳細,接口友好,使用極其簡單,提供非常詳細的使用示例。
  7. 支持所有Qt版本和編譯器以及操作系統,包括但不限于win、linux、mac、android、嵌入式linux、國產os等。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/898466.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/898466.shtml
英文地址,請注明出處:http://en.pswp.cn/news/898466.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Django+celery+flower

Djangoceleryflower Django的定時任務及可視化監控Django Django的定時任務及可視化監控 Django的定時任務&#xff0c;以及可視化監控。 Django Django&#xff1b; 首先在python中新建虛擬環境并激活 pip install virtualenv python -m venv venv source venv/bin/activa…

Python 編程題 第十一節:選擇排序、插入排序、刪除字符、目標移動、尾部的0

選擇排序 假定第一個為最小的為已排序序列&#xff0c;與后面的比較&#xff0c;找到未排序序列中最小的后&#xff0c;交換位置&#xff0c;獲得最小元素&#xff0c;依次往后 lst[1,14,25,31,21,13,6,8,14,9,7] def selection_sort(lst):for i in range(len(lst)):min_inde…

組態王Kingview配置為OPCUA服務器的一些問題處理

一、問題描述 1、組態王【運行配置】界面沒有【服務配置】的選項&#xff0c;無法將組態王Kingview配置為OPCUA服務器&#xff1b; 2、點擊組態王【運行配置界面】的【服務配置】選項彈窗警告提示【試圖執行的操作不受支持】&#xff0c;如下圖所示&#xff1a; 二、問題分析 …

模塊二 單元4 安裝AD+DC

模塊二 單元4 安裝ADDC 兩個任務&#xff1a; 1.安裝AD活動目錄 2.升級當前服務器為DC域控制器 安裝前的準備工作&#xff1a; 確定你要操作的服務器系統&#xff08;Windows server 2022&#xff09;&#xff1b; 之前的服務器系統默認是工作組的模式workgroup模式&#xff08…

git clone項目報錯fatal: fetch-pack: invalid index-pack output問題

前情回顧&#xff1a;git項目放在公司服務器上面&#xff0c;克隆等操作需要連接VPN才能操作。由于項目比較大&#xff0c;網速比較慢&#xff0c;克隆項目經常出現fetch-pack: invalid index-pack output。在網上查找各種解決方法。也就這一種有點效果。僅供參考&#xff0c;不…

前端Tailwind CSS面試題及參考答案

解釋 Tailwind CSS 中 w-1/2 和 max-w-md 的區別及適用場景 在 Tailwind CSS 里&#xff0c;w-1/2 和 max-w-md 屬于不同類型的寬度控制類&#xff0c;它們的功能和適用場景存在明顯差異。 w-1/2 是用來設定元素寬度的類。它把元素寬度設定為其父元素寬度的一半。例如&#xff…

《深度剖析:BERT與GPT——自然語言處理架構的璀璨雙星》

在自然語言處理&#xff08;NLP&#xff09;的廣袤星空中&#xff0c;BERT&#xff08;Bidirectional Encoder Representations from Transformers&#xff09;與GPT&#xff08;Generative Pretrained Transformer&#xff09;系列模型宛如兩顆最為耀眼的星辰&#xff0c;引領…

VMware主機換到高配電腦,高版本系統的問題

原來主機是i3 ,windows7系統&#xff0c;vmware 14.0,虛機系統是ubuntu 14.04。目標新機是i7 14700KF,windows11系統。原以為安裝虛擬機&#xff0c;將磁盤文件&#xff0c;虛擬機配置文件拷貝過去可以直接用。 新目標主機先安裝了vmware 15&#xff0c;運行原理虛機&#xff0…

后端框架模塊化

后端框架的模塊化設計旨在簡化開發流程、提高可維護性&#xff0c;并通過分層解耦降低復雜性。以下是常見的后端模塊及其在不同語言&#xff08;Node.js、Java、Python&#xff09;中的實現方式&#xff1a; 目錄 1. 路由&#xff08;Routing&#xff09;2. 中間件&#xff08;…

MDG實現BP客商復雜邏輯校驗的方法

引言 項目中可能常用的增強點是USMD_RULE_SERVICE來實現復雜的校驗邏輯&#xff0c;除此之外&#xff0c;SAP對BP主數據還提供了以下的實現方式。 方法1-替換ERP校驗類 眾所周知&#xff0c;BP存在復雜的ERP校驗&#xff0c;主要通過類CL_MDG_BS_FND_BP_CHECK&#xff08;子…

基于springboot的教務系統(源碼+lw+部署文檔+講解),源碼可白嫖!

摘要 這些年隨著Internet的迅速發展&#xff0c;我們國家和世界都已經進入了互聯網大數據時代&#xff0c;計算機網絡已經成為了整個社會以及經濟發展的巨大動能&#xff0c;各個高校的教務工作成為了學校管理事務的重要目標和任務&#xff0c;因此運用互聯網技術來提高教務的…

TDengine 中的流式計算

簡介 TDengine 中的流計算&#xff0c;功能相當于簡化版的 FLINK &#xff0c; 具有實時計算&#xff0c;計算結果可以輸出到超級表中存儲&#xff0c;同時也可用于窗口預計算&#xff0c;加快查詢速度。 創建流式計算 CREATE STREAM [IF NOT EXISTS] stream_name [stream_o…

代碼隨想錄day23 回溯part2

39.組合總和 給你一個 無重復元素 的整數數組 candidates 和一個目標整數 target &#xff0c;找出 candidates 中可以使數字和為目標數 target 的 所有 不同組合 &#xff0c;并以列表形式返回。你可以按 任意順序 返回這些組合。 candidates 中的 同一個 數字可以 無限制重…

回調函數中 qsort 函數的使用

目錄 一.冒泡排序 二.指針類型 void* 三. qsort 1.簡介 2.研究函數參數 3.怎么用&#xff1f; (1)排數組&#xff0c;升序 (2)排序結構體 四.用冒泡排序思想&#xff0c;模擬實現 qsort (可排序任意類型數據) 1.函數參數設計 2.在 if (cmp( )>0) 怎么傳參&#x…

電機控制常見面試問題(十四)

文章目錄 一.電機信噪比二.電機零點偏移校正和極對數自適應1.零點偏移量檢測?2. 極對數識別三.交流電機電流紋波怎么產生的1.電源相關因素2.電機本體特性3.?PWM逆變器諧波4.負載與環境干擾5.診斷流程建議 四.談談對諧波的理解1.諧波定義2.次諧波產生源3.次諧波的檢測與分析4.…

axios和fetch的對比

axios 和 fetch 是用于發起 HTTP 請求的兩種常見工具&#xff0c;它們的主要區別如下&#xff1a; 1. 瀏覽器兼容性 axios&#xff1a;基于 XMLHttpRequest&#xff0c;兼容性較好&#xff0c;支持較舊的瀏覽器&#xff08;如 IE11&#xff09;。fetch&#xff1a;現代瀏覽器…

Java Timer定時任務源碼分析

前言 Java 提供的java.util.Timer類可以用來執行延時任務&#xff0c;任務可以只執行一次&#xff0c;也可以周期性的按照固定的速率或延時來執行。 實現一個延時任務調度器&#xff0c;核心有兩點&#xff1a; 如何存儲延時任務如何調度執行延時任務 源碼分析 TimerTask …

【安全運營】用戶與實體行為分析(UEBA)淺析

目錄 用戶與實體行為分析&#xff08;UEBA&#xff09;簡介一、UEBA的核心概念1. 行為基線建立2. 異常檢測3. 風險評分4. 上下文關聯 二、UEBA的應用場景1. 內部威脅檢測2. 外部威脅應對3. 合規性和審計支持 三、UEBA的技術實現1. 大數據技術2. 機器學習算法3. 可視化工具 四、…

系統思考—啤酒游戲經營決策沙盤模擬

再次感謝文華學院的邀請&#xff0c;為經緯集團管理層帶來 《啤酒游戲經營決策沙盤》&#xff01; 很多朋友問&#xff1a;“最近是不是啤酒游戲上的少了&#xff1f;” 其實&#xff0c;真正的關鍵不是游戲本身&#xff0c;而是——如何讓大家真正看見復雜系統中的隱性結構。 …

排序算法實現:插入排序與希爾排序

目錄 一、引言 二、代碼整體結構 三、宏定義與頭文件 四、插入排序函數&#xff08;Insertsort&#xff09; 函數作用 代碼要點分析 五、希爾排序函數&#xff08;ShellSort&#xff09; 函數作用 代碼要點分析 六、打印數組函數&#xff08;PrintSort&#x…