资讯动态

C#基于海康SDK构建实时视频监控与报警系统实战

发布时间:2026/10/3 4:56:36 来源:尧图企业网站定制
做安防和工业上位机开发的朋友海康的设备基本是绕不开的存在。不管是工厂车间、园区出入口还是仓库、养殖场的远程看护手里有一批海康摄像头之后需求通常都会指向同一个方向把分散的实时画面统一收进自己的软件里同时做到出现异常能立刻报警。我的一位老同事甚至说过一句很真实的话——“海康的iVMS-4200拿来单机用挺好但一旦需要联动自己的业务系统最后还是得自己写。”这篇文章要聊的就是这件事用C#基于海康设备网络SDKHCNetSDK从零搭一套实时视频流监控与报警系统。核心解决两个问题画面怎么流畅稳定地显示异常事件怎么及时准确地报警。我会从SDK封装、实时预览、报警回调、联动存储到上线排障完整讲一遍适合正在接海康SDK做WinForm/WPF上位机的开发者也适合想自己搭建一套轻量视频监控平台的朋友参考。别以为这就是“调几个DLL方法”的事。海康SDK本身功能很完善但挪到C#环境里从P/Invoke结构体定义、回调线程调度、多路画面的内存管理到报警事件的分流处理每一个环节都有真实的坑。这篇文章不写虚的全部是实操层面的经验。1. 项目需求分析与方案选型1.1 这个项目到底要解决什么问题开始写代码之前先把需求问清楚。我遇到过的监控项目表面上是“把摄像头画面接到软件里”实际拆开之后通常包含四层需求实时预览多路摄像头同时播放画面切换流畅延迟尽量低。录像回放要么把录像存到本地要么调用设备SDK按时间段回放。报警监控移动侦测、设备断线、IO信号量等异常事件能被程序捕获。业务联动报警产生后自动截图、开始录像、弹窗提醒、写数据库甚至联动短信或企业微信通知。这四层里实时预览和报警联动是最核心的两块也是开发量最大的部分。录像回放如果只是自用用设备SDK自带的接口就能搞定如果要做专业平台那还要考虑流媒体网关、分布式存储复杂度会跳好几个数量级。本文先围绕中小规模场景以“单机运行、同时监控16路以下”为目标来展开。1.2 取流方式对比海康SDK、RTSP、ONVIF做视频接入首先要决定的就是取流方案。很多人一上来就纠结但其实方案选择直接决定了后面的工作量我做了个对比表供参考。方案延迟功能深度开发量适用场景海康私有SDK低100-300ms最全支持云台控制、报警布防、设备参数设置中单一海康设备的中大型系统RTSP FFmpeg中300-800ms仅视频流报警需另拉通道大需要统一接入多品牌摄像机ONVIF中标准协议覆盖多品牌基本功能大平台化项目需要兼容异构设备我的选择是主链路走海康SDK原因很简单报警信息、设备状态、云台控制这些能力通过SDK拿到是成本最低的而且预览延迟极低。如果你后续需要做AI图像识别可以再单独拉一路RTSP子码流给算法用两条路不冲突。这里多说一句很多人建议直接用RTSP流OpenCV来播放这在原型阶段确实快但到了项目后期会遇到三个尴尬一是画面延迟不稳定二是拿不到设备侧的移动侦测事件三是断线重连、录像联动全部要自己造轮子。所以除非你的系统要兼容多个摄像头品牌否则第一选择一定是用设备自己的SDK。1.3 系统整体架构整个系统的模块划分可以非常直观地分成四层接入层负责设备登录、连接管理维护设备在线状态。预览层负责实时视频渲染、多画面布局、抓图和本地录像。报警层负责报警回调接收、报警类型解析、事件分发。业务层负责报警截图保存、数据库记录、UI展示、通知发送。实际操作中接入层和报警层往往在一个后台服务里完成预览层则和界面线程耦合较紧。所以在代码层面我建议从一开始就做好“UI线程与SDK线程分离”的约定——SDK的回调线程绝不直接操作控件而是把事件放到队列中由后台线程处理后再通过BeginInvoke更新UI。这个约定看着不起眼但能避免大量诡异的卡顿和闪退。2. 开发环境搭建与SDK封装2.1 SDK下载与DllImport引入海康的设备网络SDK可以直接从海康官网的“服务支持-下载中心”获取选择Windows版解压后重点关注HCNetSDK.dll、HCCore.dll、hlog.dll等文件。这里有个容易被坑的点HCNetSDK.dll 本身还依赖同目录下的一堆配套DLL如果只拷贝主DLL到运行目录程序会在启动时抛DllNotFoundException但提示的是HCNetSDK.dll找不到很容易让新手原地发懵。正确的做法是把整个SDK文件夹至少包含HCNetSDK.dll、hlog.dll、HCCore.dll、hpr.dll和库目录下的PlayCtrl.dll等放到运行目录或者用绝对路径注册。C#这边用DllImport去引用[DllImport(HCNetSDK.dll)] public static extern bool NET_DVR_Init(); [DllImport(HCNetSDK.dll)] public static extern bool NET_DVR_Cleanup(); [DllImport(HCNetSDK.dll)] public static extern bool NET_DVR_SetConnectTime(uint dwWaitTime, uint dwTryTimes);另外要特别注意位数匹配。64位系统如果装的是64位SDK那整个进程必须编译为x64反过来32位SDK只能用于x86进程。我们项目里就遇到过开发机是64位、部署机是32位系统结果换了SDK版本后才发现位号不一致导致的初始化失败。建议解决方案很简单全公司统一使用64位SDK 64位部署。2.2 核心结构体的C#定义C#调用海康SDK最麻烦的地方不是函数而是结构体的内存布局。C结构体里的char数组在C#里处理不好轻则字段解析乱码重则内存越界直接崩溃。最稳妥的方式就是用固定大小的字节数组而不是string。先看登录信息结构体[StructLayout(LayoutKind.Sequential)] public struct NET_DVR_USER_LOGIN_INFO { [MarshalAs(UnmanagedType.ByValArray, SizeConst 129)] public byte[] sDeviceAddress; public byte byUseTransport; public ushort wPort; [MarshalAs(UnmanagedType.ByValArray, SizeConst 64)] public byte[] sUserName; [MarshalAs(UnmanagedType.ByValArray, SizeConst 64)] public byte[] sPassword; public IntPtr pLoginFunc; public bool bUseAsynLogin; public IntPtr pUser; public IntPtr iDeviceVersion; public uint iSockPort; public IntPtr iReserved; public uint iReserved2; }注意sDeviceAddress、sUserName、sPassword都用了字节数组赋值的时候用Encoding.ASCII.GetBytes最后补一个0字节表示字符串结束。有些老教程直接用[MarshalAs(UnmanagedType.ByValTStr)]加string这样一个是容易出现中文密码编码问题二是如果SDK升级改了缓冲区长度直接就是内存破坏。用字节数组至少不会越界。给字段赋值封装一个辅助方法会方便很多public static byte[] StrToByteArray(string str, int bufferSize) { byte[] bytes new byte[bufferSize]; byte[] temp Encoding.ASCII.GetBytes(str); Array.Copy(temp, bytes, Math.Min(temp.Length, bufferSize - 1)); return bytes; }设备信息结构体NET_DVR_DEVICEINFO_V40也要定义不过它主要用来读取能力集字段比较多我一般只取byChanNum通道数、byStartChan起始通道号和byIPChanNumIP通道数。这块在使用V40接口时通道数量获取方式比老接口NET_DVR_DEVICEINFO_V30靠谱得多。2.3 登录模块实现登录是后续所有操作的前提。我建议封装一个CameraDevice类把设备IP、端口、用户名、密码、登录句柄这些信息全部封装起来后续预览、布防、截图都基于这个类的句柄来操作。public class CameraDevice { public string Ip { get; set; } public ushort Port { get; set; } public string UserName { get; set; } public string Password { get; set; } public int UserId { get; private set; } -1; public int ChannelCount { get; private set; } public bool Login() { NET_DVR_USER_LOGIN_INFO loginInfo new NET_DVR_USER_LOGIN_INFO(); loginInfo.sDeviceAddress StrToByteArray(Ip, 129); loginInfo.wPort Port; loginInfo.sUserName StrToByteArray(UserName, 64); loginInfo.sPassword StrToByteArray(Password, 64); loginInfo.bUseAsynLogin false; NET_DVR_DEVICEINFO_V40 deviceInfo new NET_DVR_DEVICEINFO_V40(); UserId NET_DVR_Login_V40(ref loginInfo, ref deviceInfo); if (UserId -1) { int errorCode NET_DVR_GetLastError(); // 记录错误码便于排查 return false; } ChannelCount deviceInfo.byChanNum 0 ? deviceInfo.byChanNum : deviceInfo.byIPChanNum; return true; } public void Logout() { if (UserId 0) { NET_DVR_Logout(UserId); UserId -1; } } }这里有两个细节一是bUseAsynLogin这个字段同步登录时直接置false就好异步登录会触发回调逻辑更复杂二是登录失败后一定要调用NET_DVR_GetLastError获取错误码这是排查问题的第一手信息。常见的错误130表明网络不通或者设备离线错误67多半是账号密码错错误16可能是设备连接数满。3. 实时视频预览的实现3.1 画面初始化与设备登录实时预览是整个系统的脸面画面能不能稳、秒开率如何直接影响用户观感。海康SDK的预览流程可以说非常“经典”先初始化SDK再登录设备然后进入预览。初始化这块我通常会在程序入口处一次性完成NET_DVR_Init(); NET_DVR_SetConnectTime(3000, 3); // 超时3秒重试3次 NET_DVR_SetReconnect(10000, true); // 断线自动重连间隔10秒NET_DVR_SetReconnect这个方法可能很多教程里不会突出讲但对长时间运行的监控程序来说太关键了。它能让SDK在网络抖动后自动重新连接设备不需要你写复杂的心跳逻辑。不过要提醒一句自动重连只是恢复连接预览句柄还是需要你重新建立。3.2 实时预览的完整代码预览的核心是NET_DVR_RealPlay_V40它接收两个关键参数用户ID和预览参数结构体。预览参数里最重要的就是hPlayWnd也就是用来显示画面的控件句柄。public bool StartPreview(IntPtr playWndHandle, int channel) { NET_DVR_PREVIEWINFO previewInfo new NET_DVR_PREVIEWINFO(); previewInfo.hPlayWnd playWndHandle; previewInfo.lChannel channel; previewInfo.dwStreamType 0; // 0-主码流1-子码流 previewInfo.dwLinkMode 0; // TCP方式 previewInfo.bBlocked 1; // 阻塞取流 previewHandle NET_DVR_RealPlay_V40(UserId, ref previewInfo, IntPtr.Zero, IntPtr.Zero); return previewHandle ! -1; }这里有个大坑hPlayWnd必须是窗口句柄但WinForm里的Panel在创建句柄之前Handle属性可能为0。解决办法是在设置预览目标之前先强制创建控件句柄一般用Handle属性访问一次即可或者调用CreateControl方法。另外WPF项目里不能用Panel直接拿句柄要么用WindowsFormsHost嵌套WinForm控件要么自己用HwndHost封装这个后面踩坑环节再细说。码流选择上默认用主码流清晰但带宽占用高在局域网内没问题。如果做公网远程访问建议用子码流预览分辨率下降但流畅度提升很明显。我通常把主码流留给录像子码流用于多画面预览两全其美。3.3 多画面布局与画面切换监控系统一般要求支持1、4、9、16画面切换。实现方式不复杂核心思想是动态创建Panel数组把每个Panel的句柄传给对应的预览方法。布局的关键点是画面从一种分割模式切换到另一种时必须先停止所有旧预览再重新创建Panel并恢复新预览。粗暴停止再重建会闪黑一下体验不好。我的做法是先把各个通道的子码流预览句柄保留下来切换布局的时候不重新登录设备只重设预览的显示窗口速度会快很多。关于画面比例海康SDK会默认拉伸填满窗口所以Panel的尺寸比例最好跟摄像头分辨率比例一致比如169的画面对应169的Panel区域。否则画面会被拉伸变形。如果不允许改变Panel尺寸也可以在SDK内部设置显示模式为“适应窗口等比缩放”。这块我习惯在界面初始化时统一计算好每个网格的变比保证画面不变形。这里分享一个实测经验做单画面全屏时把hPlayWnd设置为一个覆盖主区域的大Panel再调用一次NET_DVR_RealPlay_V40传入新句柄即可不需要重新登录。切换回多画面时同样处理。整个过程几十毫秒比销毁重建控件方案顺滑得多。4. 报警监控与联动处理4.1 报警消息回调机制报警是监控系统的灵魂。海康SDK的报警消息通过MSGCallBack回调函数从底层线程返回。先定义委托public delegate void MSGCallBack(int lCommand, ref NET_DVR_ALARMER pAlarmer, IntPtr pAlarmInfo, uint dwBufLen, IntPtr pUser);然后调用NET_DVR_SetDVRMessageCallBack_V30注册回调再针对每个设备调用NET_DVR_SetupAlarmChan_V41完成布防NET_DVR_SETUPALARM_PARAM alarmParam new NET_DVR_SETUPALARM_PARAM(); alarmParam.dwSize Marshal.SizeOf(alarmParam); alarmParam.byLevel 1; alarmParam.byAlarmInfoType 1; int alarmHandle NET_DVR_SetupAlarmChan_V41(userId, ref alarmParam);这里我有两个深刻教训。第一个是回调委托必须保存为类级别的字段否则会被GC回收然后下一个报警事件到来时程序直接崩掉异常还特别难查。第二个是回调是在SDK的线程池里执行的任何耗时操作都不要放在回调体内。截图、写库、弹窗这类动作轻则卡住SDK线程重则导致报警堆积、程序崩溃。我的标准做法是回调只做最简单的事件封装放进ConcurrentQueue 里由后台线程统一消费和处理。private ConcurrentQueueAlarmEvent _alarmQueue new ConcurrentQueueAlarmEvent(); private void AlarmCallBack(int lCommand, ref NET_DVR_ALARMER pAlarmer, IntPtr pAlarmInfo, uint dwBufLen, IntPtr pUser) { AlarmEvent evt new AlarmEvent { Command lCommand, DeviceIp Encoding.ASCII.GetString(pAlarmer.sDeviceIP).TrimEnd(\0), Channel pAlarmer.byChannel, AlarmTime DateTime.Now }; _alarmQueue.Enqueue(evt); }后台线程用ASP.NET Core的BackgroundService或者普通的Timer轮询队列都行我是用的一个独立的消费线程加上AutoResetEvent做信号通知CPU占用几乎为0。4.2 报警类型解析与处理海康报警命令码相当多我们最常用的几个可以整理成一张速查表命令常量数值以SDK头文件为准含义COMM_ALARM_MOTION_DETECT0x1103移动侦测报警COMM_ALARM_VIDEO_LOST0x1102视频丢失报警COMM_ALARM_VIDEO_HIDE0x1104视频遮挡报警COMM_ALARM_IO_ALARM0x1105IO信号量报警COMM_ALARM_DISK_FULL0x1026硬盘满报警COMM_ALARM_DEVICE_OFFLINE0x2005设备离线自定义居多注意不同SDK版本的命令码宏定义可能略有差异动手开发前一定以自己下载的SDK里“头文件/文档”为准。我的项目里就是用这些头文件的值直接定义C#常量的。报警信息结构体的解析要看命令码。比如信号量报警时pAlarmInfo指向NET_DVR_ALARMINFO_V30移动侦测报警时通常指向NET_DVR_MOTION_DETECTION或者更复杂的结构体。最稳妥的方式是用Marshal.PtrToStructure按已知类型解析如果SDK版本升级导致发包变化至少要捕获解析异常不能直接crashprivate void HanleAlarmMessage(AlarmEvent evt, IntPtr pAlarmInfo) { switch (evt.Command) { case COMM_ALARM_IO_ALARM: NET_DVR_ALARMINFO_V30 alarmInfo Marshal.PtrToStructureNET_DVR_ALARMINFO_V30(pAlarmInfo); // 处理IO报警 break; case COMM_ALARM_MOTION_DETECT: // 部分摄像头将移动侦测上报为通用规则事件 break; } }4.3 报警联动截图、录像、弹窗报警事件确认之后要做的事情很多核心是“取证留痕”和“即时通知”。我实现的联动动作按顺序是这样的自动截图调用NET_DVR_CapturePictureBlock把当前画面保存为JPG文件。本地录像如果报警持续可以从报警时刻开始录制30秒或60秒的视频段。界面弹窗在主界面右上角弹出一个视频浮窗自动播放报警通道的画面。语音提醒用Windows自带的声音播放接口播报警音或者调用TTS语音。持久化记录把报警时间、设备、通道、截图路径、录像路径写入数据库。这里的关键是截图和录像不能直接在回调线程里执行否则会阻塞SDK内部线程。我在消费线程里统一处理一次报警事件从入队到截图完成实测延迟在200毫秒以内完全满足实时性要求。截图调用方式我贴一下public bool CapturePicture(string filePath) { // 参数依次是用户ID、通道号、保存路径、是否异步阻塞 return NET_DVR_CapturePictureBlock(UserId, 1, filePath, 3000); }这里的通道号要和预览时的一致。对IPC来说通常是1对NVR接入的通道就要用实际的物理通道号。截图路径建议按“设备IP/日期/yyyyMMddHHmmssfff.jpg”的目录结构存储后面查记录会很顺手。4.4 报警去重与布防撤防策略报警系统如果不去重晚上一扇门被风吹动移动侦测可以每秒钟触发一次一分钟之内数据库能塞进几十条无效记录。报警去重是所有监控系统上线前必须解决的问题。我的策略是“时间窗口事件标签”双重去重同一个设备、同一个通道、同一种报警类型在设定的时间窗口内一般是30秒或60秒只保留第一条。实现非常简单用Dictionary时间戳就能搞定private Dictionarystring, DateTime _lastAlarmTimeMap new Dictionarystring, DateTime(); private bool IsDuplicate(AlarmEvent evt, int windowSeconds) { string key ${evt.DeviceIp}_{evt.Channel}_{evt.Command}; if (_lastAlarmTimeMap.TryGetValue(key, out DateTime lastTime) (DateTime.Now - lastTime).TotalSeconds windowSeconds) { return true; } _lastAlarmTimeMap[key] DateTime.Now; return false; }布防撤防的设计也很重要。实际项目里有些报警类型在特定时间段内不希望打扰人比如上班时间不需要对办公区的移动侦测报警。可以在配置里给每种报警类型设置“布防时间段”只有落在时间段内的事件才触发联动时间外的只记录不通知。5. 录像存储与报警查询5.1 录像文件与截图的保存策略报警联动生成的截图和录像文件如果不定期清理再大的磁盘也会被写满。我一般按“配额保留天数”双策略管理每天检查一次录像目录超过设定的最大保留天数比如30天就删除同时限制总占用空间上限比如500GB达到上限后从最早的文件开始删。这里要提醒一句删除录像文件不能只删数据库记录要保证文件系统和数据库的一致性。最靠谱的做法是把文件路径作为数据库表的主键逻辑删除操作先查记录、再删文件、最后清记录顺序不能反。如果先删文件再删数据库记录一旦删记录失败就会留下“有记录但是文件找不到”的死链。5.2 报警记录数据库设计报警记录表我通常这样设计字段类型说明IdBIGINT IDENTITY主键DeviceIpVARCHAR(32)设备IPChannelINT通道号AlarmTypeINT报警类型枚举AlarmTimeDATETIME报警时间SnapshotPathVARCHAR(255)截图路径VideoPathVARCHAR(255)录像路径HandledBIT是否已处理RemarkVARCHAR(255)备注索引方面AlarmTime和DeviceIp是查询频率最高的条件建议建联合索引。报警记录增长速度很快一张大表积攒一年可能会有几十万条数据查询性能会明显下降所以还应该做按月分表或定时归档。5.3 历史记录查询界面报警查询界面算是这套系统最后一个门面。我的实现是用一个DataGridView绑定查询结果顶部放设备下拉框、报警类型下拉框、时间范围选择器。点击某条记录后右侧立即显示对应的截图双击还能弹窗播放关联的录像片段。这里有个UI体验细节查询结果超过500条时不要让DataGridView一次性加载所有数据否则假死几秒很常见。我通常用“只加载前200条记录”用户翻页或者滚动到底部时再加载下一页。虽然代码多了一点点但客户体验提升非常明显。我在这个模块还加了一个小功能报警记录支持导出Excel。这功能看着小但客户验收的时候都喜欢。用.NET原生的方式导出会很繁琐我直接用了开源库NPOI一个表格模版类就能搞定。6. 常见问题与实战避坑6.1 登录失败与连接不上的排查思路登录失败是接入阶段最频繁的问题别总是怀疑代码不对先看错误码。我把排查顺序整理成了一个简单模板错误码130或错误码7/9网络不通先ping设备IP再telnet端口默认8000通不通。错误码67用户名密码错误用海康官方的SADP工具确认设备当前账号密码。错误码16设备连接数已满海康部分设备默认只允许6路并发连接关掉iVMS-4200或者调整设备端最大连接数。错误码181用户不存在或者是用户权限不足需要去设备Web管理端检查用户权限。另外还要注意海康SDK登录端口默认是8000但有些设备改过端口登录时用的不是Web访问端口。极限情况下拿SADP工具扫描一下设备当前状态是最快的确认方式。6.2 画面卡顿与花屏的处理方法画面卡顿通常跟网络和码流有关。局域网带宽一般不是瓶颈要重点排查的是SDK取流方式和WPF渲染之间的矛盾。WPF项目里如果你直接把预览句柄设置到WindowsFormsHost内部控件上有时候会出现画面闪烁、卡顿。我现在的标准做法是预览界面使用Image控件承载视频数据通过实时预览回调拿到YUV数据后用WriteableBitmap渲染。虽然代码复杂度高一点但WPF里画面表现最稳定。不过如果只是做WinForm直接用Panel句柄就够了性能没有任何问题。还有花屏问题多半是码流类型和解码参数不匹配。主码流可能是H.265编码如果SDK播放库版本太老不支持就会花屏或黑屏。解决办法是升级播放库PlayCtrl.dll或者把设备编码改为H.264。6.3 内存泄漏与资源释放监控系统通常7x24小时运行内存泄漏是硬伤。海康SDK最容易被忽视的资源释放包括预览句柄NET_DVR_StopRealPlay之后句柄才真正释放。报警布防句柄NET_DVR_CloseAlarmChan_V30要成对使用。登录句柄退出系统时要NET_DVR_Logout所有设备。播放库资源使用播放库时还要调用PlayM4_Close关闭解码通道。我遇到过最隐蔽的一次内存泄漏在报警联动里每触发一次就new一个位图对象忘记Dispose跑了两天内存涨了1个多GB。排查思路很简单用dotMemory或WinDbg抓dump看托管堆立马就能定位。这里强烈建议所有截图、录像操作都套上try-finally或者using块别嫌麻烦。6.4 回调线程与UI交互的坑UI线程和SDK回调线程的冲突是开发期最容易踩的坑。在WinForm中跨线程操作控件会抛InvalidOperationException这是初学者最容易碰到的问题。虽然设置Control.CheckForIllegalCrossThreadCallsfalse可以强制压下去但这不是长久之计画面刷新和用户操作仍然可能莫名卡死。正确姿势始终是回调线程只负责收集数据UI更新统一通过SynchronizationContext或者Control.BeginInvoke回到UI线程执行。为了简化代码我封装了一个UiHelperpublic static class UiHelper { private static SynchronizationContext _context; public static void Initialize() { _context SynchronizationContext.Current; } public static void Post(Action action) { _context?.Post(_ action(), null); } }在回调或后台线程里更新状态栏、报警列表时调用UiHelper.Post即可不用每次判断InvokeRequired代码看着干净不少。6.5 设备断电重连后的自恢复设备断电再上电之后原有的预览句柄和布防句柄都会失效。很多项目上线后出现“设备断电重启后画面变成黑块”的问题就是因为没有处理自恢复。我的经验是分两层解决第一层SDK层打开NET_DVR_SetReconnect自动重连第二层应用层启动一个巡检定时器每隔30秒检查每个设备的连接状态。如果发现登录句柄失效就重新登录、重新预览、重新布防。这个巡检逻辑虽然简单但在实际项目里极大提升了系统的稳定性。我还习惯记录一条“设备重连成功”的日志方便事后排查。另外报警布防在重连之后需要重新调用这是不少人会忽略的。因为布防句柄是跟登录句柄绑定的登录句柄变了原布防句柄自然失效。我在巡检里对这些状态做了完整的重建动作。6.6 常见异常速查表最后整理一张速查表方便开发过程中直接对号入座现象可能原因解决思路DllNotFoundExceptionSDK配套DLL缺失复制整个SDK目录到运行目录登录错误130设备不可达或端口错误ping、telnet、SADP扫描确认预览黑屏码流编码不受支持升级播放库或改设备编码为H.264报警不触发未布防或回调委托被GC检查布防句柄委托用字段保存程序崩溃回调内操作UI改为队列消费BeginInvoke长时间运行卡顿内存泄漏检查截图、位图未释放设备重启后无画面连接未自动恢复应用层定期巡检重建会话把这些经验预先写进项目文档里日后不管是自己维护还是交给同事接手都能少踩很多坑。监控和报警系统本质上是个“在线服务系统”稳定性永远比花哨的界面重要得多。我个人在反复调试回调和线程模型的过程中最大的体会是把“回调只做一件事——入队”这个原则坚持到底后面所有复杂的联动逻辑都会变得很好写。如果你手里也正有类似的海康接入需求希望这篇文章能帮你少走一些弯路。照着这套思路先把单路预览和单类报警跑通再横向扩展多路布防和业务联动整个系统的骨架就会非常扎实。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑