目录
本司智能信息箱产品是管控摄像头电源,监控摄像头视频在线率的一个有效运维工具。因为统计视频在线率是业主十分关心的问题,所以如何有效地统计视频在线率是工程师需要努力解决的问题。
方案 | 是否需要摄像头密码 | 是否能与摄像头交互信息 | 是否能知道摄像头的网络状态 |
---|---|---|---|
ping | 否 | 否 | 是 |
onvif | 是 | 是 | 是 |
ffmpeg | 是 | 是 | 是 |
ping,onvif,ffmpeg三种协议应用场合不同,各有优劣。onvif会多出用户名,密码字段,方法上会多startstreaming,stopstreaming,及识别视频的编码分辨率等信息,从而从媒体信息地址uri获取视频流;ffmpeg则更进一步,可以直接调用方法分析视频质量等等。
这里需要声明本文侧重点有两方面:
drivercontext是用来配置ping驱动软件(new system.net.ping())接口正常工作的上下文配置。这里主要是interval,timeout,minsuccess三个字段,其中timeout是驱动内配置,interval,minsuccess为驱动外配置,下文6.1中会有详细举例,按下不表。
3个字段含义详见注释。
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子领域对象。
#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);
自己对于领域驱动设计的理解并不深刻,但是凭着对设备域,以及协议,总线,驱动的甚微了解,以及看了不少开源项目,不断地学习同行的数据库,硬生生地拼凑成了此文,可能有些概念上或者实现上会有不合适的地方,请路过的高手们不吝赐教。当然如果你有不明白的地方也请提出,我也会耐心解答。
如对本文有疑问, 点击进行留言回复!!
Asp.Net Core Identity 骚断腿的究极魔改实体类
你一定看得懂的 DDD+CQRS+EDA+ES 核心思想与极简可运行代码示例
网友评论