☰
海康抓拍机C#回调实现车牌识别:VS2010工程实战解析
2026/10/19 7:27:33 网站建设 项目流程

简介:面向VS2010开发者的C#海康抓拍机回调示例项目,基于海康威视DS-TCG227A型号抓拍机实现车牌实时抓拍与号码识别,已在真实设备上完成测试,可在捕获车牌图像后由回调弹窗实时显示识别结果,适用于交通监控、停车场管理等需要自动识别车牌的安防场景,适合具备一定C#基础与Win32开发经验的读者。压缩包共84个文件、约21.59MB,其中以dll动态库和lib库为主(包含HCNetSDK等设备通信依赖),cs源码文件涵盖PlayCtrl、HCNetSDK等关键调用,另含exe可执行程序、pdb调试符号、resx界面资源、xml配置文件以及完整的sln/csproj工程文件,可直接编译运行或二次开发。已有1182人学习,目录中提供VS2010解决方案、升级报告及SDK示例,开发者可从中学习设备连接、图像获取、车牌检测、字符分割识别到回调展示的完整流程,掌握海康SDK在C#环境下的封装与调用方式。

1. 海康抓拍机 C# 回调:一个把车牌号码从相机推到界面的 VS2010 工程

某停车场改造项目要把海康抓拍机的车牌识别结果接进 C# 上位机,我一开始觉得「相机都把车牌算好了,我只需要收结果就行」。真正动手才发现,麻烦全在回调这条链路:海康 SDK 是 Win32 动态库,数据结构是 C 的,回调跑在 SDK 内部线程,车牌字符串还是 GB2312 编码。这份 VS2010 工程把设备登录、回调注册、车牌解析、图片保存完整串了起来,核心价值就是把相机侧的车牌识别能力翻译成 C# 能直接调用的代码。适合正在做停车场出入口、卡口监控、车辆出入统计的 .NET 开发者,尤其是手里只有老 SDK、又必须在 WinForms 工程里跑的人。

2. 技术选型与工程结构:为什么 VS2010 工程敢直接吃 Win32 回调

2.1 从相机到 C#:中间隔着 HCNetSDK 动态库

抓拍机和普通网络摄像机不一样,设备端本身就集成了车辆检测和车牌识别算法。相机在车辆经过的瞬间完成抓拍、识别,再通过 SDK 主动把结果推给上位机。对应的海康 Windows SDK 主体是 HCNetSDK.dll,纯 C 接口、Win32 调用约定,C# 端没有官方封装库,只能通过 DllImport 平台调用把 C 结构体翻译成 C# 类型。

用 VS2010 来接这个工程是很务实的选择。VS2010 默认编译目标 .NET Framework 4.0,对应 C# 4.0,在老设备上位机上兼容性最好;工程里没有 NuGet 依赖,外部依赖只有一个 HCNetSDK.dll,部署到现场机器时不用装一堆运行库。反过来,如果拿 .NET 8 重写,光是结构体 Marshal 和高版本运行时部署就够现场维护人员喝一壶。

为什么用回调而不是自己轮询?抓拍机是事件驱动设备,车辆没来就没有数据,轮询效率低而且拿不准抓拍时机。回调是 SDK 内部线程监听设备报警,数据到了主动调用 C# 注册的委托。代价是这个回调方法运行在 SDK 线程上,不能碰 UI、不能做阻塞操作,这是整个工程最核心的约束,第三章会展开讲。

2.2 解压后的目录布局

源码包解压后是按「工程 + 依赖 + 输出」三层摆好的,不是一堆文件堆根目录:

目录/文件职责
.sln / .csprojVS2010 解决方案与工程文件,双击直接打开
MainForm.cs主窗体,负责登录设备、注册回调、显示车牌
NativeMethods.cs集中放 DllImport 声明和结构体定义
AlertProcess.cs回调处理逻辑,解析车牌与图片数据
bin/Debug编译输出目录,HCNetSDK.dll 要放这里

把 P/Invoke 声明集中到一个静态类的做法是老工程里最常见的姿势。NativeMethods 只放平台调用,MainForm 只放界面逻辑,回调解析单独一个类,三层分离。换 SDK 版本时只改 NativeMethods 和 AlertProcess,界面不用动。我第一次接触这套代码时觉得麻烦,后来换了三次 SDK 版本才体会到这种分层有多省事。

2.3 SDK 调用链:从初始化到注销的完整生命周期

海康 SDK 的调用顺序必须是固定的,顺序错了会出各种玄学问题:

NET_DVR_Init 初始化 SDK 内部资源 → NET_DVR_Login_V30 登录设备拿句柄 → NET_DVR_SetDVRMessageCallBack_V30 注册回调 → 回调里处理报警数据 → 退出时先 NET_DVR_Logout 注销登录,再 NET_DVR_Cleanup 释放 SDK。

这几个环节都有坑。Init 必须最先调用,否则登录直接失败;登录句柄要保存好,退出注销要用它;回调注册要在登录成功之后,而且建议只注册一次。海康 SDK 是单例模型,多次 Init/Cleanup 会让内部句柄混乱,程序退出时也容易崩。回调关心的命令常量是 COMM_ALARM_PLATE(0x1009),对应「网络抓拍机上传车牌识别结果」这个报警类型。回调收到这个命令后,把报警指针按结构体布局解析成车牌结果,下一章就拆这条链路。

2.4 结构体翻译策略:PtrToStructure 与手动布局

C 结构体转 C# 有几种常见做法:Marshal.PtrToStructure 一次性转换、unsafe 指针直接读、手工按字节偏移拼。这个工程用的是 PtrToStructure,简单直接。但代价是你必须在 C# 侧把结构体字段排列和 C 侧完全对齐,字段顺序、字节宽度、字符串长度都不能错。

对齐这件事在老 SDK 上特别容易翻车。C 的 char 数组对应 C# 的 byte[] 加 MarshalAs(UnmanagedType.ByValArray, SizeConst=...),C 的指针对应 IntPtr。海康很多结构体里字符串是固定长度数组,比如车牌号码 sLicense[16],翻译成 C# 就是 16 字节的 byte 数组,读取时再交给 Encoding 去解码。这部分我在下面第三章结合代码细说,先记住一个原则:结构体定义必须逐字段对照 SDK 头文件抄,少一个保留字段后续解析就可能错位。

3. 核心代码拆解:从登录设备到抓拍回调返回车牌号码的完整链路

3.1 P/Invoke 声明与设备登录

先看 NativeMethods 里的 DllImport 声明。这里用 V30 登录接口,参数直接传 IP、端口、用户名、密码,不用定义复杂的登录结构体,老 SDK 版本兼容性也更好。

public class NativeMethods { // 初始化 SDK,整个进程只需要调用一次 [DllImport("HCNetSDK.dll")] public static extern bool NET_DVR_Init(); // 释放 SDK 内部资源,程序退出前调用 [DllImport("HCNetSDK.dll")] public static extern bool NET_DVR_Cleanup(); // 获取最近一次 SDK 调用失败的错误码 [DllImport("HCNetSDK.dll")] public static extern int NET_DVR_GetLastError(); // 登录设备,成功返回句柄,失败返回 -1 [DllImport("HCNetSDK.dll", EntryPoint = "NET_DVR_Login_V30", CharSet = CharSet.Ansi)] public static extern int NET_DVR_Login_V30( string sDVRIP, ushort wDVRPort, string sUserName, string sPassword, IntPtr lpDeviceInfo); }

NET_DVR_Login_V30 的最后一个参数本应是 NET_DVR_DEVICEINFO_V30 结构体指针,这里用 IntPtr 接收。登录成功后 SDK 会把设备的通道数、设备类型等基本信息写进这个缓冲区,如果调用方拿到的句柄不是 -1,说明登录成功,缓冲区里的内容其实没那么关键。用 IntPtr 的好处是绕开了超过 100 字节的大结构体定义,降低字段对齐出错的概率。

3.2 登录设备:256 字节缓冲区接收设备信息

登录这段代码在整个工程里属于「抄过来就能用」的部分,但有几个细节值得注意:

int LoginAndInit() { if (!NativeMethods.NET_DVR_Init()) throw new Exception("SDK 初始化失败"); // 分配 256 字节缓冲区,接收设备通道信息 // 足够容纳 NET_DVR_DEVICEINFO_V30,避免逐字段定义 IntPtr pDevInfo = Marshal.AllocHGlobal(256); try { int userId = NativeMethods.NET_DVR_Login_V30( "192.168.1.64", 8000, "admin", "your_password", pDevInfo); if (userId == -1) { int err = NativeMethods.NET_DVR_GetLastError(); throw new Exception("登录失败,错误码: " + err); } return userId; // 这个句柄要保存好,注销时用 } finally { Marshal.FreeHGlobal(pDevInfo); } }

登录失败时 NET_DVR_GetLastError 返回的错误码很有用:常见的有 17(密码错误)、29(设备不在线)、72(设备忙)。现场排查时把错误码记录下来,比瞎猜配置管用得多。另外,IP、端口、用户名、密码这四项是硬编码在代码里的,跑通之后建议移到配置文件里,不然每次换设备都要重新编译。

3.3 回调委托与注册:把 C# 方法交给 SDK

登录成功后就要注册报警回调。注意海康回调的报警参数是一个裸指针,因为在 SDK 的设计里,不同报警命令对应不同结构体,用 IntPtr 可以在回调内部按命令灵活转换:

public delegate bool AlarmCallback(int lCommand, IntPtr pAlarmInfo, uint dwBufLen, IntPtr pUser); [DllImport("HCNetSDK.dll", EntryPoint = "NET_DVR_SetDVRMessageCallBack_V30")] public static extern bool NET_DVR_SetDVRMessageCallBack_V30(AlarmCallback cb, IntPtr pUser); // 类成员变量,防止 delegate 被 GC 回收 private AlarmCallback _callback; void StartListen(int userId) { _callback = OnAlarm; bool ok = NativeMethods.NET_DVR_SetDVRMessageCallBack_V30(_callback, IntPtr.Zero); if (!ok) { int err = NativeMethods.NET_DVR_GetLastError(); // 注册失败,错误码记录日志 } }

回调委托必须保存在类字段里,不能是局部变量。这是老海康工程里最容易翻车的地方:局部 delegate 在方法返回后会被垃圾回收,SDK 内部还持有这个函数指针,下次报警一触发就直接访问非法内存,程序可能崩在莫名的地方。所以在 StartListen 里用 _callback 字段把委托引用钉住,回调才能稳定工作。

3.4 回调解析:车牌号码从哪里来

回调一旦注册,SDK 内部线程就会在报警到达时调用 OnAlarm。车牌识别结果就在 pAlarmInfo 指向的内存里:

private bool OnAlarm(int lCommand, IntPtr pAlarmInfo, uint dwBufLen, IntPtr pUser) { if (lCommand == COMM_ALARM_PLATE) // 0x1009 车牌识别报警 { // 把报警内存按车牌结果结构体解析 NET_DVR_PLATE_RESULT plate = (NET_DVR_PLATE_RESULT)Marshal.PtrToStructure( pAlarmInfo, typeof(NET_DVR_PLATE_RESULT)); // 车牌号码是 GB2312 编码,不能用 ASCII 直接解 string plateNo = Encoding.GetEncoding("GB2312") .GetString(plate.struPlateInfo.sLicense) .Trim('\0', ' '); int imgLen = (int)plate.struPlateInfo.dwPicLen; IntPtr pImg = plate.struPlateInfo.pPicData; // 回调线程只做数据搬运,界面更新交给 BeginInvoke BeginInvokeIfNeeded(() => ShowPlate(plateNo)); // 图片数据必须立即复制,回调返回后指针失效 if (imgLen > 0 && pImg != IntPtr.Zero) SavePlateImage(CopyFromPointer(pImg, imgLen), plateNo); } return true; }

这个函数是整条数据链的咽喉。lCommand 用来区分报警类型,只有 0x1009 才处理;pAlarmInfo 是报警数据起始地址,PtrToStructure 按结构体布局把它解析成 NET_DVR_PLATE_RESULT。车牌号码存在 struPlateInfo.sLicense 里,是 16 字节的定长数组,GB2312 编码,所以解码后要 Trim 掉结尾的空字符。图片数据是指针加长度,回调返回后这块内存就会被 SDK 复用,必须立刻复制走。

3.5 结构体定义与指针拷贝

上面用到的两个结构体定义如下,注意字段顺序必须和 SDK 头文件一致:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public struct NET_DVR_PLATE_INFO { public byte byPlateType; // 车牌类型 public byte byColor; // 车牌颜色 public byte byBright; // 亮度 public byte byLicenseLen; // 车牌字符个数 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] public byte[] sLicense; // 车牌号码,定长 16 字节 // 中间还有若干保留字段,按实际 SDK 头文件补全 public uint dwPicLen; // 车牌小图长度 public IntPtr pPicData; // 车牌小图数据 } [StructLayout(LayoutKind.Sequential)] public struct NET_DVR_PLATE_RESULT { public uint dwSize; // 结构体大小 public uint dwMatchPicLen; // 抓拍大图长度 public IntPtr pMatchPicData; // 抓拍大图数据 public NET_DVR_PLATE_INFO struPlateInfo; // 车牌信息 }

中间省略的保留字段在真实 SDK 里是存在的,如果直接照抄这份代码,PtrToStructure 解析出来的车牌可能对不上。正确做法是打开你本地 HCNetSDK.h,找到 NET_DVR_PLATE_INFO,把每个字段按顺序照抄进 C#。字节对齐的问题不用太担心,LayoutKind.Sequential 会按默认规则对齐,C 侧也是自然对齐,两边一致就行。

图片指针转字节数组的逻辑很直接:

private byte[] CopyFromPointer(IntPtr src, int len) { if (src == IntPtr.Zero || len <= 0) return null; byte[] buf = new byte[len]; Marshal.Copy(src, buf, 0, len); return buf; } private void SavePlateImage(byte[] jpgData, string plateNo) { if (jpgData == null || jpgData.Length == 0) return; string path = @"D:\capture\" + plateNo + "_" + DateTime.Now.ToString("yyyyMMddHHmmss") + ".jpg"; File.WriteAllBytes(path, jpgData); }

Marshal.Copy 把非托管内存复制到托管字节数组,后续写文件、HTTP 上传、转 Bitmap 都基于这个数组。回调里图片数据是 JPEG 格式,直接落盘就是一张可查看的抓拍图。这里有个环节容易忽略:车牌号码里的特殊符号,比如新能源车牌是 8 个字符,16 字节的数组完全放得下,但旧式车牌的首字母如果被算法识别成「O」而不是「0」,保存文件名时就会出现两个相似记录,要结合置信度字段判断,这部分就是业务层面的二次加工了。

3.6 数据流全景:从相机触发到图片落盘

把整条链路串起来看是这样的:相机检测到车辆经过 → 设备端曝光抓拍 → 内嵌算法识别车牌 → 设备推报警给 SDK → SDK 内部线程调度 C# 委托 → OnAlarm 解析出车牌字符串和图片指针 → 文本走 BeginInvoke 刷新界面,图片字节复制后存成 JPG 文件。唯一的时间瓶颈在回调线程内部,任何耗时操作都会阻塞 SDK 的消息分发,报警量大时会导致后续报警丢失或延迟。所以我在回调里只做结构体解析、字符串解码、内存复制三件事,写文件和 UI 更新都丢出回调线程。

UI 更新的代码长这样:

private void BeginInvokeIfNeeded(Action action) { if (IsHandleCreated && InvokeRequired) BeginInvoke(action); else action(); }

InvokeRequired 判断当前是否在 UI 线程,如果在 SDK 回调线程就通过 BeginInvoke 切回界面线程。注意用的是异步版本 BeginInvoke,不会阻塞回调线程等待 UI 处理完。如果不做这个切换,回调里直接操作 TextBox 会抛跨线程异常,这也是第五章要讲的坑之一。

4. 编译运行与部署:VS2010 工程从解压到跑通的全过程

4.1 打开工程与首次编译

解压源码包后直接用 VS2010 打开 .sln,如果本机装的是更高版本(比如 VS2015 或 VS2019),打开时提示工程迁移一律选「否」,保持原有工程格式不动。工程本身不依赖 NuGet 包,首次编译失败的概率很低,最常见的报错是目标框架不对:检查一下工程属性里的 Target Framework,确认是 .NET Framework 4.0 或更低。VS2010 默认就是 4.0,一般不用改。

编译通过后,exe 会生成在 bin\Debug 或 bin\Release 下。让我觉得省心的是这个工程没有第三方 UI 库、没有日志组件,外部依赖就是 HCNetSDK.dll,编译产物很小,拷到现场机器上只要放好 DLL 就能跑,不用装运行库。

4.2 三个必查配置点

跑通之前先检查三个地方,顺序不能乱:

检查项设置值为什么必须这样
平台目标x86HCNetSDK.dll 是 32 位,AnyCPU 在 64 位系统会加载失败
HCNetSDK.dll放到 exe 同目录SDK 依赖搜索顺序里 exe 目录最优先
设备参数与相机实际一致IP/端口/密码任意一项不对就登录失败

平台目标这个坑我踩过不止一次。VS2010 新建工程默认 AnyCPU,在 64 位 Windows 上 .NET 进程会以 64 位运行,而 HCNetSDK.dll 是 32 位,加载时直接抛 BadImageFormatException。解决办法是在工程属性 → 生成 → 平台目标里选 x86,硬编码成 32 位进程。DLL 位置也很关键,海康 SDK 运行时是动态加载的,放系统目录不如放 exe 目录省心,还能避免多版本 SDK 互相干扰。设备参数其实指相机 Web 端的网络配置,默认端口 8000,用户名 admin,密码是你自己设的,别拿 SDK 示例里的默认密码硬套。

4.3 启动流程与回调验证

程序启动后的完整流程是:点击界面上的「初始化并登录」按钮 → 内部依次执行 NET_DVR_Init、NET_DVR_Login_V30、NET_DVR_SetDVRMessageCallBack_V30 → 登录按钮变成「已连接」状态,回调处于待命状态。

验证回调是否生效,最直接的办法是在相机上手动触发抓拍。海康抓拍机的 Web 管理端一般有「手动抓拍」或「远程抓拍」按钮,点一下,相机会立刻抓一张图并推报警,相当于模拟车辆经过。如果回调链路是通的,界面上会立刻刷新出车牌号,D:\capture 目录下多出一张 JPG。没有车经过时,这是最靠谱的模拟测试方式。

我还习惯在 OnAlarm 入口加一行日志输出,记录回调触发时间、命令号和缓冲区长度。日志不一定要落地文件,用 Debug.WriteLine 写在输出窗口就够了,排查「回调到底有没有进来」这类问题,一条带时间戳的输出比断点更高效。

4.4 现场部署的环境问题

现场机器比开发机复杂得多,几个高频环境问题值得提前预防。杀毒软件可能拦截 HCNetSDK.dll 的加载,表现是登录前一切正常,NET_DVR_Init 返回 false,把 DLL 加白名单即可。部分老版本 SDK 依赖 VC++ 运行库(比如 VC2008 运行库),目标机器没装的话,DLL 加载会报缺少 msvcr90.dll,这时候补齐对应版本运行库就行。还有防火墙,抓拍机和上位机之间的 8000 端口如果被防火墙拦,登录会超时,表现为 NET_DVR_Login_V30 返回 -1 且错误码是网络相关错误,把端口放通或者加一条入站规则即可。

5. 常见问题与排查:回调线程、GC 引用与编码乱码的五个坑

5.1 回调偶尔触发,跑一段时间就不再触发

现象:程序刚启动时一切正常,抓拍能出车牌,运行十几分钟或几个小时后,回调彻底没反应,界面不再刷新,但相机那边确认还在抓拍。

原因:这是委托被垃圾回收的典型表现。SDK 内部保存的是非托管函数指针,C# 的 delegate 对象如果没有被托管引用着,GC 会在合适时机把它回收,函数指针变成野指针,回调自然就断了。发生在运行一段时间后是因为 GC 压力是累积的,内存越紧张回收越早。

解决:把回调委托存到类字段,用 static 或实例字段持有都可以,核心是引用要活着。我一般直接在声明处初始化private AlarmCallback _callback;,在注册回调前赋值。如果工程里有多处注册,还要确保不会重复赋值覆盖掉之前的引用。

5.2 回调里更新 TextBox 抛 InvalidOperationException

现象:回调触发时,代码执行到 textBox1.Text = plateNo 这一行,抛异常「线程间操作无效,从不是创建控件的线程访问它」。

原因:WinForms 控件只能在创建它的线程(UI 线程)上操作,回调跑在 SDK 的内部线程,跨线程访问控件是不安全的,运行时直接拦下。

解决:在回调里用 Control.BeginInvoke 把 UI 更新动作切回界面线程,就是我前面代码里的 BeginInvokeIfNeeded 方法。注意 4.0 之后 WinForms 默认禁止跨线程访问,但有一种情况例外:如果你在 Main 方法里手动设置了 Control.CheckForIllegalCrossThreadCalls = false,异常不会抛但界面刷新仍然不可靠,隐藏的竞态还在,不建议这么做。

5.3 车牌汉字变成乱码

现象:车牌号码里的汉字部分(比如「京」「沪」「粤」)显示成「?」或一串乱码,字母数字正常。

原因:车牌字符串是 GB2312 编码,用了 Encoding.ASCII 或 Encoding.Default。ASCII 只能解英文字母和数字,碰到汉字直接变问号;Encoding.Default 在中文系统上是 GB2312,但换到非中文系统(比如英文版 Windows)就变成系统默认编码,结果完全不可控。

解决:显式用 Encoding.GetEncoding("GB2312") 解码。注意如果目标机器是精简版 Windows,可能没有 GB2312 代码页,这时候需要先判断代码页是否存在,或者改用 Encoding.GetEncoding(936) 并捕获异常。老工程里我一般写一个静态只读的 Encoding 字段,初始化时做兼容检查。

5.4 64 位系统双击就崩,调试报 BadImageFormatException

现象:32 位系统上编译运行正常,拷到 64 位系统上启动就报 BadImageFormatException,或者直接弹错误对话框。

原因:HCNetSDK.dll 是 32 位,exe 被编译成 AnyCPU 后,在 64 位系统上以 64 位进程运行,加载 32 位 DLL 失败。

解决:工程属性 → 生成 → 平台目标选 x86。改完之后项目文件 .csproj 里会多出<PlatformTarget>x86</PlatformTarget>,重新编译发布的 exe 就是 32 位进程,部署到 64 位机器上也能正常跑。这也是为什么我在第四章里把它列为必查配置的第一项。

5.5 登录失败或回调完全没有反应

现象:NET_DVR_Login_V30 返回 -1,或者登录成功但回调从没触发过,手动抓拍也没用。

原因:分为两类。登录失败通常是 IP/端口/账号密码不匹配,或者设备不在线;回调不触发则是命令常量对不上,或者 SDK 版本接口不同导致注册的是错误回调函数。

解决:登录失败先用 NET_DVR_GetLastError 拿错误码,用海康官方的 SADP 工具(局域网设备搜索工具)确认相机 IP、端口、设备序列号,很多现场问题是相机 IP 变了但程序里还是旧 IP。回调不触发时,先确认你用的 SDK 版本回调函数是 V30 还是老版本,两种接口的函数签名不一样,注册错了回调函数名虽然能编译过,但 SDK 内部调用点对不上,回调自然不进来。我一般在 StartListen 里加日志打印注册返回值,再在 OnAlarm 入口加日志,用两步定位。

6. 二次开发:从单机识别到多设备异步处理的改造路径

拿到这份工程,最值得改的第一个点是把回调结果推给后端。停车场场景里,上位机识别出车牌后往往要同步到云端或本地数据库。改造思路是在 OnAlarm 里不直接写文件,而是把车牌号、颜色、抓拍时间、图片字节组装成一个对象,丢到后台线程去 HTTP POST。回调线程只负责解析和投递:

ThreadPool.QueueUserWorkItem(delegate { string json = JsonConvert.SerializeObject(new { plateNo, color, time = DateTime.Now, imageBase64 }); // 发送 HTTP 请求到业务后端 });

第二个改造方向是多相机接入。把登录、注册回调的逻辑从界面里抽出来,用字典按设备 ID 维护每个相机的登录句柄和回调引用,pUser 参数可以携带你的设备上下文。回调触发时先根据 pUser 取设备身份,再处理车牌,这样一套界面就能管十几台抓拍机。

第三个是换识别算法的思路。海康相机自带的识别在大多数场景够用,但如果你要对特定车型或特殊车牌做二次确认,可以把回调里的抓拍大图字节复制出来,传给本地 OCR 模型重新识别一次,两个结果互相校验。这个改造的核心还是回调线程只搬运数据这个原则,识别放线程池里排队执行,避免阻塞 SDK 报警分发。

曾经有个项目,我在回调里同步做图片保存又做网络上传,抓拍高峰时直接丢图丢报警。从那以后我每次接海康设备都强制走一遍「回调线程只收数据、业务处理丢线程池」的流程,这个习惯救了我多次。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询