目錄html
本司智能信息箱產品是管控攝像頭電源,監控攝像頭視頻在線率的一個有效運維工具。由於統計視頻在線率是業主十分關心的問題,因此如何有效地統計視頻在線率是工程師須要努力解決的問題。java
方案 | 是否須要攝像頭密碼 | 是否能與攝像頭交互信息 | 是否能知道攝像頭的網絡狀態 |
---|---|---|---|
ping | 否 | 否 | 是 |
onvif | 是 | 是 | 是 |
ffmpeg | 是 | 是 | 是 |
ping,onvif,ffmpeg三種協議應用場合不一樣,各有優劣。onvif會多出用戶名,密碼字段,方法上會多StartStreaming,StopStreaming,及識別視頻的編碼分辨率等信息,從而從媒體信息地址URI獲取視頻流;ffmpeg則更進一步,能夠直接調用方法分析視頻質量等等。linux
這裏須要聲明本文側重點有兩方面:數據庫
driverContext是用來配置ping驅動軟件(new System.Net.Ping())接口正常工做的上下文配置。這裏主要是interval,timeout,minSuccess三個字段,其中timeout是驅動內配置,interval,minSuccess爲驅動外配置,下文6.1中會有詳細舉例,按下不表。編程
3個字段含義詳見註釋。api
public class PingDriverContext { ///能夠增長name字段,表示驅動名稱。 /// <summary> /// ping間隔 /// </summary> public int interval { get; set; } /// <summary> /// ping超時時間 /// </summary> public int timeout { get; set; } /// <summary> /// ping成功最小次數 /// </summary> public int minSuccess { get; set; } }
Camera表示攝像頭,ping表示檢查攝像頭網絡狀態的驅動,DM表示Domain Model即領域模型。網絡
public enum CameraState { Online = 0, Offline = 1, Unknown = 2 }
private PingDriverContext _driverContext; private Timer _timer; private readonly object _lock = new object(); private readonly ReadOnlyMemory<byte> _buffer; public int CurrentSuccess { get; private set; } /// <summary> /// IP地址 /// </summary> public string Ip { get; set; } ///// <summary> ///// 狀態 ///// </summary> private CameraState cameraState; public CameraState CameraState { get { return cameraState; } set { cameraState = value; } } /// <summary> /// 攝像頭狀態更新時間 /// </summary> public DateTime UpdateTime { get; set; }
interval = 3,//每一個攝像頭間隔3秒ping一次 timeout = 200,//單次ping等待返回結果200ms超時 minSuccess = 0,//一次ping成功便可認爲online
在析構體中,主要傳入建立子領域對象所必須的參數。多線程
#region Constructors /// <summary> /// 根據IP,以及driverContext建立攝像頭領域模型 /// </summary> /// <param name="driverContext">聚合CameraPingBus中的驅動上下文driverContext</param> /// <param name="iP">數據庫中的攝像頭的IP地址</param> public CameraPingDM(PingDriverContext driverContext, string iP) { string data = "ping-pong"; _buffer = Encoding.ASCII.GetBytes(data); Ip = iP; _driverContext = driverContext; LoggerHelper.Debug($"Ping camera IPList every {_driverContext.interval}s"); } //Dapper數據模型須要 public CameraPingDM(){ } #endregion
這裏就是傳入所須要的參數直接new子領域對象。通常是直接調用,因此它是靜態方法。併發
=>這裏是lambda表達式的語法糖,表示返回一個建立好的CameraPingDM子領域對象。app
#region Creations public static CameraPingDM Create(PingDriverContext driverContext, string iP) => new CameraPingDM(driverContext, iP); #endregion
主要表現爲修改屬性值等。這裏CameraStateUpdate方法更新攝像頭網絡狀態,同時保存更新時間。
#region Behaviors /// <summary> /// 更新攝像頭網絡狀態,同時保存更新時間 /// </summary> /// <param name="_cameraState"></param> public void CameraStateUpdate(CameraState _cameraState) { cameraState = _cameraState; UpdateTime = DateTime.Now; } #endregion
#region Behaviors with Ping /// <summary> /// 表示爲ping單個攝像頭,檢查其網絡狀態。 /// </summary> /// <returns></returns> public async Task<bool> Start() { if (_driverContext.interval >= 0) { var interval = Convert.ToInt32(TimeSpan.FromSeconds(_driverContext.interval).TotalMilliseconds); _timer = new Timer(state => { lock (_lock) { DoPing(); } }, null, interval, interval); } return true; } /// <summary> /// 根據ping對應IP返回的結果來對當前ping成功次數計數,知足要求爲online,不然爲offline。 /// </summary> private void DoPing() { var pingSender = new Ping(); var options = new PingOptions { //不分包 DontFragment = true }; try { PingReply reply = pingSender.Send(IPAddress.Parse(Ip), _driverContext.timeout, _buffer.ToArray(), options); LoggerHelper.Debug($"Ping reply for {Ip} is {reply.Status}"); if (reply?.Status == IPStatus.Success) { Increment(); } else { Decrement(); } } catch (Exception) { LoggerHelper.Debug($"Ping reply for {Ip} failed"); Decrement(); } } /// <summary> /// 當前ping成功次數CurrentSuccess減1,CurrentSuccess爲非負數 /// </summary> private void Decrement() { if (CurrentSuccess <= 0) { CurrentSuccess = 0; CameraStateUpdate(CameraState.Offline); } else { CurrentSuccess--; } } /// <summary> /// 當前ping成功次數CurrentSuccess+1,若是大於等於設置的最小ping成功次數,則更新攝像頭的網絡狀態 /// </summary> private void Increment() { if (CurrentSuccess >= _driverContext.minSuccess) { CameraStateUpdate(CameraState.Online); } else { CurrentSuccess++; } } /// <summary> /// 定時ping定時器關閉 /// </summary> /// <returns></returns> public async Task<bool> Stop() { _timer?.Dispose(); return true; } #endregion
全部方法的做用詳見註釋,不明白的能夠在評論區評論,我會耐心解答,有更好建議的懇請提出。
這裏上層聚合CameraPingBus主要調用的就是Start()表示開始ping對應ip的攝像頭,根據ping結果刷新攝像頭網絡狀態更新時間;Stop()方法中止ping。這裏Timer定時器會在4.3.5中詳細介紹,按下不表。
也可稱之爲CameraPingBus領域,也就是須要咱們去解決與攝像頭協議交互查看攝像頭是否在線的問題域。領域是從須要解決的問題域命名,聚合是從功能角度命名,該類是聚合了許多子領域CameraPingDM,它是去ping 攝像頭Camera的行爲,返回的是online/offline網絡狀態值,經過子領域聚合而解決了一整個問題域。
private Timer _dbTimer; ICamera_Services _camera_Services; public IList<CameraPingDM> CameraPingDMList = new List<CameraPingDM>(); ///能夠增長name字段,表示驅動名稱。 ///寫一個IP地址 對應狀態變化的方法,將有變化的ADD進差別集合。 若是差別集合不爲空,再保存進數據庫。 ///經過winform修改pingDriverContext 3個參數 //默認參數5/100/0 static PingDriverContext pingDriverContext = new PingDriverContext() { interval = 3, timeout = 200, minSuccess = 0, };
pingDriverContext
值對象,這裏將全部攝像頭的ping驅動配置爲一樣參數去建立CameraPingDM子領域對象。
public CameraPingBus(ICamera_Services camera_Services) { _camera_Services = camera_Services; }
#region Behaviors with all the CameraPingDMList /// <summary> /// 從數據庫的數據模型獲取全部攝像頭的IP地址加載到CameraPingDM對象集合,由Dapper完成數據模 /// 型的IP到CameraPingDM領域模型IP賦值的轉換工做,啓動全部CameraPingDM對象集合的ping方法 /// </summary> /// <returns></returns> public async Task<bool> CreateAndStartAllCameraPing() { var CameraIpList = await GetCameraIpList(); try { foreach (var item in CameraIpList) { var cameraPingDM = CameraPingDM.Create(pingDriverContext, item.Ip); await cameraPingDM.Start(); CameraPingDMList.Add(cameraPingDM); } } catch { return false; } return true; } /// <summary> /// 中止全部CameraPingDM對象集合的ping方法 /// </summary> /// <returns></returns> public async Task<bool> StopPing() { try { foreach (var item in CameraPingDMList) { await item.Stop(); } } catch { return false; } return true; } /// <summary> /// 異步獲取CameraPingDM對象集合元素數量 /// </summary> /// <returns></returns> public async Task<int> CameraIpCount() { var CameraIpList = await _camera_Services.GetAllCameraIPAsync(); return CameraIpList.Count(); } #endregion
所用方法的做用我都作了詳細的註釋,詳見註釋。有問題可在評論區提出,我會耐心解答。
#region Behaviors with DataBase /// <summary> /// 從數據庫中加載全部攝像頭IP地址到CameraPingDM的IP字段 /// </summary> /// <returns></returns> public async Task<IEnumerable<CameraPingDM>> GetCameraIpList() { return await _camera_Services.GetAllCameraIPAsync(); } /// <summary> /// 將全部Cameara的在線狀態根據IP地址匹配定時5秒更新到數據庫 /// </summary> /// <returns></returns> public async Task<bool> Save2DbTimerStart() { _dbTimer = new Timer(state => { _camera_Services.UpdateList(CameraPingDMList); }, null, 4000, 4000); return true; } /// <summary> /// 關閉數據庫定時保存定時器 /// </summary> /// <returns></returns> public async Task<bool> Save2DbTimerStop() { _dbTimer?.Dispose(); return true; } #endregion
所用方法的做用我都作了詳細的註釋,詳見註釋。有問題可在評論區提出,我會耐心解答。
這裏須要注意的是由Dapper完成數據模型的IP到CameraPingDM領域模型IP賦值的轉換工做,保存也是由Dapper進行了從領域模型的IP,CameraState到數據模型的無縫對接,礙於篇幅過長,時間也很晚了,感興趣的請在評論區留言。筆者將根據讀者反饋狀況看是否有必要另起一篇,寫一下基於Dapper進行數據模型與領域模型之間的互相轉換。
用來指向下面的定時器實例。
using System.Threading; private Timer _dbTimer;
定時器的引用類型指向new Timer()實例,目的是爲了去寫定時器的關閉方法。
_dbTimer = new Timer(state => { _camera_Services.UpdateList(CameraPingDMList); }, null, 4000, 4000);
這裏定時器有4個參數,F12可得以下
// callback: // A System.Threading.TimerCallback delegate representing a method to be executed. // // state: // An object containing information to be used by the callback method, or null. // // dueTime: // The amount of time to delay before callback is invoked, in milliseconds. Specify // System.Threading.Timeout.Infinite to prevent the timer from starting. Specify // zero (0) to start the timer immediately. // // period: // The time interval between invocations of callback, in milliseconds. Specify System.Threading.Timeout.Infinite // to disable periodic signaling.
第四個參數period定時週期。
_dbTimer?.Dispose();
熟悉Java的道友有沒有發現,C#裏的Timer與Java的ScheduledExecutorService很類似,也不知道是誰抄誰,或者是殊途同歸之妙吧。
import java.util.concurrent.ScheduledExecutorService; private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(DeviceSetting.MAX_GROUP_ID); executorService.scheduleWithFixedDelay(() -> { try { BaseMsg baseMsg = deque.take(); Thread.sleep(AWAKE_TO_PROCESS_INTERVAL); Channel channel = touchChannel(channelId); if (channel == null || !channel.isActive()) { logger.warn("「Channel」" + " Channel「" + channelId + "」無效,沒法下發該幀"); removeChannelCompleted(channel); deque.clear(); return; } }, channelId, CommSetting.FRAME_SEND_INTERVAL, TimeUnit.MILLISECONDS);
注意:這裏必需要調用System.Threading庫裏的定時器能夠多線程併發執行回調方法,不然的話,將沒有此功能,System.Timers定時器使用較爲複雜,且沒法多線程併發,須要本身寫多線程併發的方法,System.Timers定時器只能提供定時功能。
//Domain services.AddScoped(typeof(CameraPingBus));
public PingSetting(CameraPingBus cameraPingBus) { _cameraPingBus = cameraPingBus; InitializeComponent(); LoggerHelper.Debug("視頻在線率配置工具啓動"); }
/// <summary> /// 按下啓動按鈕執行操做 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private async void button1_Click(object sender, EventArgs e) { //IP地址從數據庫數據模型賦值到領域模型的IP字段,而且每隔3秒開始ping攝像頭,保存其網絡狀態 await _cameraPingBus.CreateAndStartAllCameraPing(); //每隔4s將攝像頭網絡狀態更新到IP地址相等的數據庫數據模型中去 await _cameraPingBus.Save2DbTimerStart(); } /// <summary> /// 按下中止按鈕執行操做 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private async void button2_Click(object sender, EventArgs e) { //中止攝像頭定時ping行爲 await _cameraPingBus.StopPing(); //中止保存攝像頭網絡狀態到數據庫 await _cameraPingBus.Save2DbTimerStop(); }
各調用方法含義詳見註釋。
工具圖示以下:
驅動即軟件接口,ping是驅動,modbus協議庫也是驅動,驅動配置(driverContext)分爲驅動內配置與驅動外配置。值對象驅動上下文driverContext就是包含了驅動內配置與驅動外配置。
var pingSender = new Ping(); PingReply reply = pingSender.Send(IPAddress.Parse(Ip), _driverContext.timeout, _buffer.ToArray(), options);
_driverContext.timeout即爲ping驅動內配置。
if (CurrentSuccess >= _driverContext.minSuccess) { CameraStateUpdate(CameraState.Online); }
_driverContext.minSuccess即爲驅動外配置。
if (_driverContext.interval >= 0) { var interval = Convert.ToInt32(TimeSpan.FromSeconds(_driverContext.interval).TotalMilliseconds); _timer = new Timer(state => { lock (_lock) { DoPing(); } }, null, interval, interval); }
_driverContext.interval也爲驅動外配置。
攝像頭即設備,這裏驅動跟設備是1對1的關係,驅動是設備的一個被動行爲,SC平臺經過加載驅動的所需配置(driverContext)來獲取對應設備的數據(信號或者說狀態)。
總線就像高速公路,他須要有名稱,是否關閉,起點,終點,限速(接口參數)。因此這裏的IP地址就比如是終點地址,故這裏的攝像頭IP是屬於總線的概念範疇。
具體驅動協議的上一層,一根總線能夠對應多個驅動,也能夠對應多個設備。
設備上的信號值(網絡狀態值)至關因而要寄的快遞,驅動至關因而運快遞的車,保持車間距,按時到達終點,而總線至關因而車開着的高速路。
獲得結果ping行爲爲併發
獲得結果爲多線程ping
ping成功與超時與實際在線離線IP地址結果相符。
一個項目中不必定只有一個聚合像我如今作的智能箱它就須要兩個聚合,就有兩個問題域
一個是智能設備箱(也就是點位),全部的AI,DI,DO,AO(包含歷史數據),攝像頭,A接口配置數據,用戶,角色,升級,運維公司,運維人員,區域,設備箱告警,協議模板,歷史告警都是它的子領域。
攝像頭也是一個聚合,攝像頭的告警(離線,停電告警),歷史告警,攝像頭的型號,攝像頭的廠商,區域,設備箱,運維公司,運維人員,攝像頭驅動,攝像頭總線都是攝像頭的子領域。
var pingSender = new Ping();
驅動上下文driverContext的字段(配置信息)會加載到驅動pingSender上去,去獲取所須要的值,即爲軟件接口
PingReply reply = pingSender.Send(IPAddress.Parse(Ip), _timeout, _buffer.ToArray(), options);
本身對於領域驅動設計的理解並不深入,可是憑着對設備域,以及協議,總線,驅動的甚微瞭解,以及看了很多開源項目,不斷地學習同行的數據庫,硬生生地拼湊成了此文,可能有些概念上或者實現上會有不合適的地方,請路過的高手們不吝賜教。固然若是你有不明白的地方也請提出,我也會耐心解答。