简介:这是基于VS2015开发的C#版ONVIF协议客户端工具源代码,覆盖设备发现、设备鉴权、设备参数获取与设置、用户信息管理、固件升级以及视频流参数配置与RTSP显示等完整功能,适合安防监控研发或需要二次开发ONVIF设备的工程技术人员。资源包共2000个文件,压缩后约31.69MB,主要包含C#源文件、C/C++源码(用于RTSP流解析与视频解码)、XAML界面和动态库等,结构完整。已有1409人学习下载,是一份可直接用于工程参考的ONVIF客户端实现。通过源码可系统了解ONVIF设备发现与鉴权流程、参数交互方式及RTSP视频流的获取与显示,掌握在VS2015中构建C#客户端工具的具体做法,也能为使用其他语言实现ONVIF协议提供接口设计参考。
1. 在 VS 2015 里徒手写一个 ONVIF 客户端:先搞清楚这工具到底解决什么问题
做安防或者上位机的人,迟早会遇到这么一幕:现场有台网络摄像机,型号挺老,厂商 SDK 光盘早就不知道扔哪了,但你又必须从它身上把 RTSP 取流地址、设备序列号、PTZ 控制权拿下来。市面上虽有 ONVIF Device Manager 这类现成工具,可它毕竟是黑匣子,没法嵌进你自己的 C# 上位机里。这时候,一份能编译、能二次开发的 C# 版 ONVIF 协议客户端工具源代码就显得格外值钱——它不依赖厂商 SDK,走的是标准协议,只要设备支持 ONVIF 就能互通。VS 2015 虽然老,但 WinForm 和 WCF 的开发体验在这套代码里刚刚好。这篇文章想讲清楚的,就是从零拉通一个基于 SOAP/WCF 的 C# ONVIF 客户端,把设备发现、能力协商、取流、PTZ 这条路彻底跑通,顺便把密钥交换和 RTSP URL 拼接这些容易翻车的细节都摊开说。
2. ONVIF 协议的核心套路与 VS 2015 工程准备:从 WS-Discovery 到 SOAP 都是标准动作,别自己发明轮子
2.1 ONVIF 协议族到底包含哪些服务,以及它们各自扮演什么角色
刚接触 ONVIF 时很容易被一堆文档吓到,实际上它就是一个基于 Web Service 的接口规范,底层通信全靠 SOAP over HTTP,消息格式是 XML。对于 C# 开发者来说,最直接的感受就是:这玩意儿跟我用 WCF 调服务没什么两样。但 ONVIF 不是单一服务,它按功能划分成设备管理服务、媒体服务、PTZ 服务、事件服务等好几类。日常开发中最常用的是前三种:设备管理服务负责取设备信息、网络配置、系统重启这类基础操作;媒体服务负责拿能力集、取视频源、拿 RTSP 字符串;PTZ 服务用来做云台控制。
这套分层在设计上是有讲究的。比如你要拿 RTSP 取流地址,不会直接去问设备“把流地址给我”,而是先查媒体服务的能力集,再通过 GetProfiles 拿到媒体配置文件,最后通过配置文件里的视频源和编码器信息拼出真正的 RTSP URL。这套流程一开始觉得绕,但好处是它跟设备品牌无关,不管大华、海康还是天地伟业,只要支持 ONVIF,返回的 XML 结构基本一致。这就是协议的价值:你写一次客户端,理论上能对接所有合规设备。当然,现实里各家在服务地址、鉴权细节上各有怪癖,这就得靠调试慢慢磨了。
2.2 用 WCF 还是手写 SOAP?VS 2015 下的选型理由与最小工程结构
做 C# ONVIF 客户端,业界最普遍的做法是用 WCF 的 ChannelFactory 动态生成服务代理,而不是靠“添加服务引用”。原因其实很现实:ONVIF 的服务地址是动态的,设备发现完成后才能拿到具体的 URI,而且同一个设备上有多个服务(Device、Media、PTZ),它们的地址往往各不相同。你要是用 Visual Studio 的“添加服务引用”,就得在编译期把服务地址写死,这在 ONVIF 场景下根本不现实。ChannelFactory 可以在运行时指定 endpoint 地址,刚好满足这个需求。
另外还有个关键点是鉴权。ONVIF 强制走 WS-Security,头部要带 UsernameToken,里面的密码不是明文,而是 PasswordDigest 算法算出来的摘要值。这个算法说白了就是 BASE64(SHA1(Nonce + Created + Password)),Nonce 和 Created 都要放到 Token 里发过去。.NET 的 WCF 框架对 WS-Security 有原生支持,自己实现起来比手动解析 SOAP XML 要省心得多。因此,VS 2015 + WCF 这套组合,算是在性能和开发效率之间找到了个平衡点。
工程结构上,我习惯按这样组织:
OnvifClient.sln ├─ OnvifClient.Core # 核心库:Discovery、服务代理、DTO ├─ OnvifClient.App # WinForm 测试工具(可选的) └─ OnvifClient.Tests # 单元测试(可选)在 VS 2015 里新建一个类库项目,目标框架选 .NET Framework 4.5。这个版本对 WCF 的支持最成熟,跑在 Win7/10 上都没问题。如果你用的是 .NET 4.0 以下的框架,WCF 的某些扩展可能受限,倒是也能用,但没必要给自己挖坑。
2.3 引用与 NuGet 包:不用第三方库也能跑,但有了它们能省半天
严格来说,ONVIF 客户端不依赖任何第三方 NuGet 包,因为 WCF 本身就在 .NET Framework 里。但实际做的时候有几个痛点:一是 SOAP 报文要抓包调试,Visual Studio 自带的 WCF 日志配置起来略繁琐;二是 WS-Discovery 的 UDP 组播报文要用 WCF 的 Discovery 协议栈,VS 2015 对此支持的还算顺。为了少写点底层代码,我用了一个叫 Onvif.Core 的开源库作为参考(网上搜就能搜到,它封装了设备发现和代理生成),但最终生产代码还是自己写,因为第三方库往往会绑定特定版本协议,比如 Profile S 的支持程度参差不齐。
对于只是想在 VS 2015 里快速跑通业务的人来说,我的建议是:不引第三方库,完全用 .NET 自带的 System.ServiceModel。代码量多一点,但优势是你对整条链路的掌控度高了,设备返回什么怪异的报文你能一眼定位到问题出在哪个环节。NuGet 方面,其实只需要确保 System.ServiceModel 和 System.ServiceModel.Discovery 这两个程序集在项目里可用。如果工程是新建的,在解决方案管理器里右键引用,把这两个勾上就完事。另外,ONVIF 的 SOAP 报文里会用到一个命名空间,这个命名空间是固定的,代码里直接硬编码字符串做常量即可,不需要引入额外的 XML 序列化库。
3. 跑通第一个 ONVIF 客户端:从 WS-Discovery 搜设备到拉出 RTSP 取流地址
3.1 设备发现的两种方式:UDP 组播和定向探测,哪种更靠谱
ONVIF 的设备发现基于 WS-Discovery,本质上就是往网络里发一个 UDP 组播报文,设备收到后单播回复。协议给的标准端口是 3702,报文是 SOAP over UDP。初次做这块时,最容易踩的坑是抓包时看不到任何回复,因为组播报文被交换机挡了,或者网卡的防火墙把入站 UDP 过滤掉了。
代码层面,WCF 提供了现成的 DiscoveryClient,用起来相当简洁:
using System.ServiceModel.Discovery; var discoveryClient = new DiscoveryClient(new UdpDiscoveryEndpoint()); var findCriteria = new FindCriteria(ContractTypes.ContractType); findCriteria.Duration = TimeSpan.FromSeconds(5); // 等待 5 秒收集回复 findCriteria.MaxResults = 32; // 最多收集 32 台设备 var findResponse = discoveryClient.Find(findCriteria); foreach (var result in findResponse) { // result.Address 是设备 XAddr,这通常指向 Device 服务 Console.WriteLine($"发现设备: {result.Address}"); } discoveryClient.Close();这段代码的逻辑是先构造 UDP 发现终结点,然后设定查找条件——按 ONVIF 规范,查找条件是协议的 ContractType,这是个固定 GUID。Duration 设 5 秒是比较稳妥的做法,太短设备可能来不及回,太长界面上显得卡顿。MaxResults 设为 32 是因为现场网吧可能有几十台设备,超过 32 台后的设备在界面上已经刷不过来了。
不过这种方式有个问题:组播在跨网段时基本失效。所以实际工程里,我一般还会留一个“手动输入 IP 地址”的入口,用定向探测的方式去试。做法就是直接构造一个指向该 IP 的 HTTP 地址,去请求设备服务,碰一下就知道它是不是 ONVIF 设备。这在排查问题时相当好用。
3.2 动态生成设备服务代理:ChannelFactory 与 WS-Security 的正确用法
找到设备之后,下一步是跟设备对话。ONVIF 的服务地址可以在发现结果里拿到,但那只是设备服务地址。更通用的做法是先去请求设备服务,从返回的设备信息里把所有服务地址(MediaUrl、PtzUrl 等)全拿回来。这一步叫做 GetCapabilities,也是用 ChannelFactory 动态代理来发。
在代码里,基础步骤是这样的:
// 1. 定义服务契约 [ServiceContract(Namespace = "http://www.onvif.org/ver10/device/wsdl")] public interface IDevice { [OperationContract(Action = "http://www.onvif.org/ver10/device/wsdl/GetDeviceInformation")] Task<GetDeviceInformationResponse> GetDeviceInformationAsync(GetDeviceInformationRequest request); } // 2. 动态生成代理 var binding = new HttpTransportBindingElement(); var messageElement = new TextMessageEncodingBindingElement(MessageVersion.Soap12WSAddressing10); var customBinding = new CustomBinding(messageElement, binding); var endpointAddress = new EndpointAddress(new Uri($"http://{ip}:{port}/onvif/device_service")); var factory = new ChannelFactory<IDevice>(customBinding, endpointAddress); // 3. 注入 WS-Security 凭据 factory.Endpoint.EndpointBehaviors.Add(new ClientCredentialsBehavior("admin", "password")); var client = factory.CreateChannel(); var info = client.GetDeviceInformation(new GetDeviceInformationRequest());代码核心逻辑分三段:第一段定义服务契约,Namespace 和设备官方 WSDL 里的命名空间必须严格一致,这里是 ver10;第二段用 CustomBinding 组合出 SOAP 1.2 加 WS-Addressing 10 的传输栈;第三段是给终结点加一个 ClientCredentials 行为,因为 ONVIF 的 UsernameToken 必须每一条消息都带,不能只在建立会话时验一次。
关于 ClientCredentials 行为,WCF 自带的 ClientCredentials 类默认生成的是 UsernameToken 的明文版本,不满足 ONVIF 的摘要要求。需要自己写一个 endpoint behavior 来替换默认的,代码结构就是继承 IEndpointBehavior,在 ApplyClientBehavior 里给 ClientRuntime 的 MessageInspectors 集合里塞一个自定义 inspector。Inspector 的职责很单一:在消息发出前,按照 PasswordDigest 算法把安全头填好。
public class OnvifAuthInspector : IClientMessageInspector { public object BeforeSendRequest(ref Message request, IClientChannel channel) { // 从请求消息中取到当前时间,按 ONVIF 要求格式化 var created = DateTime.Now.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"); // Nonce 必须是随机数,且每次请求都要变 var nonceBytes = new byte[16]; new Random().NextBytes(nonceBytes); var nonce = Convert.ToBase64String(nonceBytes); // PasswordDigest = BASE64( SHA1( Nonce + Created + Password ) ) var raw = nonceBytes.Concat(Encoding.UTF8.GetBytes(created)) .Concat(Encoding.UTF8.GetBytes(password)).ToArray(); var digest = Convert.ToBase64String(new SHA1Managed().ComputeHash(raw)); // 手动构造 WS-Security 头并附加到请求上 // ... return null; } }这段是整套代码里最容易翻车的地方。先看密码摘要的计算:ONVIF 规范要求的是 SHA1 哈希,不是 MD5,而且 Nonce 必须以二进制字节拼接,不是以 Base64 字符串参与哈希计算。网上很多示例代码在这里搞混,导致设备一直报 401。再看 Nonce 的长度,标准里没强制要求,但设备端大多默认接受 16 字节。第三,Created 必须是 UTC 时间,设备会把本地时间转成 UTC 来比对,如果你发的是本地时间,很可能因为时区差被拒。最后,Nonce 每次都要重新生成,如果连续两条消息 Nonce 一样,部分设备会直接当成重放攻击拒绝掉。
3.3 拿 RTSP 取流地址:GetProfiles 与 GetStreamUri 的调用顺序
设备服务通了之后,接下来就是拿 RTSP 地址。这步做起来要按固定顺序来,顺序错了拿不到东西。常见做法是:先调 GetProfiles 拿到所有媒体配置文件,然后根据你想要的流类型(主码流/子码流),用对应的 ProfileToken 调 GetStreamUri。
// 媒体服务地址和命名空间 // 注意:不同设备返回的 MediaUrl 可能不一样,要以能力集返回值为准 var mediaBinding = new CustomBinding(...); var mediaClient = new ChannelFactory<IMedia>(mediaBinding, new EndpointAddress(mediaUrl)).CreateChannel(); // 第一步:拿所有 Profile var profiles = mediaClient.GetProfiles(new GetProfilesRequest()); foreach (var profile in profiles.Profiles) { Console.WriteLine($"ProfileToken: {profile.token}"); Console.WriteLine($"视频编码: {profile.VideoEncoderConfiguration?.Encoding}"); } // 第二步:指定 ProfileToken 拿 RTSP 地址 var streamUri = mediaClient.GetStreamUri(new GetStreamUriRequest() { ProfileToken = profile.token, StreamSetup = new StreamSetup { Stream = StreamType.RTPUnicast, Transport = new Transport { Protocol = TransportProtocol.RTSP } } }); Console.WriteLine($"RTSP URL: {streamUri.MediaUri.Uri}");这里面关键的参数是两个:ProfileToken 必须在 GetProfiles 返回的列表里选,不能随便填;StreamSetup 里的 StreamType 选 RTPUnicast 就行,组播暂时用不上。而 TransportProtocol 选 RTSP,这是 ONVIF 标准里最通用的传输方式,虽然有些设备也支持 RTMTP,但兼容性远不如 RTSP 稳妥。
得到的 RTSP URL,有的设备直接返回完整的 rtsp://ip:554/...,有的只返回路径,比如 /live/ch1,需要你自己拼前缀。如果返回的是路径,拼的时候要注意用户名密码,形如 rtsp://admin:password@ip:554/path。至于拉流播放,那一步交给 VLC 的 LibVLC 或 FFmpeg 去处理即可,ONVIF 客户端到这一步的任务就算完成。
4. 把配置变成可改的:App.Config 封装、VS 2015 调试技巧和 WCF 日志设置
4.1 用 App.Config 管好 IP、端口、账号密码,别把敏感信息写死在代码里
工作中常有同事图省事,把设备 IP 和密码写在代码里,等现场换设备时就得重新编译一遍,非常被动。ONVIF 客户端工具作为常驻的上位机组件,配置项应该放在 App.Config 里,这是个基础但很重要的工程习惯。
<?xml version="1.0" encoding="utf-8"?> <configuration> <appSettings> <!-- 设备默认端口,ONVIF 标准是 80,但也有用 8000、8899 的 --> <add key="OnvifPort" value="80" /> <!-- 发现超时时间(秒),现场设备响应慢时可以调大 --> <add key="DiscoveryTimeout" value="5" /> <!-- 默认只做主码流,传 0 代表主码流,1 代表子码流 --> <add key="StreamType" value="0" /> </appSettings> </configuration>读取配置只需一行 ConfigurationManager.AppSettings["OnvifPort"],代码逻辑里再做个端口自动探测:先按配置项试试 80 端口,不通再试 8000、8899 等常见端口。这样到了现场,即使设备端口跟配置不符,只要改配置就能适配,不用重新发布。账号密码这块,常规做法是认证信息在程序启动时弹窗输入,或者从加密的配置文件读,App.Config 里尽量别放明文密码。
4.2 用 WCF 自带日志抓 SOAP 报文:把这个黑匣子打开
全网都在说 ONVIF 调试难,难就难在你看不见设备到底返回了什么。有时候你发了一个 GetProfiles,设备回了个空数组,你根本不知道是权限不够、版本不匹配,还是命名空间错了。这时候,WCF 自带的日志就是最好的窥探镜。
打开方式在 VS 2015 里很简单,在 App.Config 里加一段配置:
<system.diagnostics> <sources> <source name="System.ServiceModel.MessageLogging" switchValue="Verbose"> <listeners> <add name="Messages" type="System.Diagnostics.XmlWriterTraceListener" initializeData="onvif_trace.svclog" /> </listeners> </source> </sources> <trace autoflush="true" /> </system.diagnostics>配置完后重新跑程序,就会在运行目录下生成一个 onvif_trace.svclog 文件。这个文件要用 Windows SDK 自带的 SvcTraceViewer 打开,VS 2015 的安装目录里就带这个工具,打开后左侧是消息列表,右侧能看到完整的 XML 报文。通过这个工具能直观看到请求头和响应头里的具体内容,比如设备到底回的是哪个命名空间、错误码具体是什么。实际排错时,90% 的问题在报文里扫一眼就能发现。比如 PasswordDigest 算错了,设备回的 SOAP Fault 里会明确提示“Invalid security token”。
4.3 .NET 4.5 环境下的 SSL/TLS 兼容问题:老代码跑新设备,握手先失败
另外一个跟 VS 2015 直接相关的问题是这个:默认建的 .NET Framework 4.5 项目,TLS 默认只开到 1.0 和 1.1,可现在的摄像机固件普遍要求 TLS 1.2,尤其是海外品牌。于是就会出现一种很诡异的情况:同一个 ONVIF 地址在浏览器里能访问,代码里一调就报“基础连接已关闭”。解决办法是在程序启动时显式指定安全协议:
System.Net.ServicePointManager.SecurityProtocol |= System.Net.SecurityProtocolType.Tls12;这一行代码放在 Main 函数或窗体构造函数里,兼容老设备,又不影响新设备。还有一个常见的坑是 ServicePointManager 的默认连接数的限制,并发去探测多台设备时,连接数可能排不过来。所以批量扫描设备时,记得把这个值设大一点:
System.Net.ServicePointManager.DefaultConnectionLimit = 100;这两行配置在开发环境里看不出什么差别,到现场部署时才会显出价值。设备多了、网络乱的时候,连接池不够用会表现为:能搜到设备但拿信息超时,或者偶尔成功偶尔失败,极其玄学。
5. 避坑与排查:设备厂商不按套路出牌时的血泪经验
5.1 现象:Media 服务地址 404 —— 原因:拿 XAddr 时别漏掉能力集的二次解析
有段时间我被同一个问题反复折腾:设备能发现,设备服务也能通,但一旦把媒体服务的地址填进 ChannelFactory,请求就 404。后来抓包发现,设备在 GetCapabilities 返回的 Media.XAddr 不是一个完整的 HTTP URL,而是一个相对路径,比如 /onvif/Media。这时候如果直接拿这个相对路径跟 ChannelFactory 拼接,它会把 “http://ip:port/onvif/device_service” 当宿主,结果路径变成了 /onvif/device_service/onvif/Media,自然 404。
解决方式不复杂,拼接 XAddr 前判断一下开头是 http 还是相对路径:
string GetFullServiceUrl(string baseAddress, string xaddr) { if (xaddr.StartsWith("http://") || xaddr.StartsWith("https://")) return xaddr; var uri = new Uri(baseAddress); return $"{uri.Scheme}://{uri.Authority}{xaddr}"; }这段代码的逻辑是:如果设备返回绝对地址就直接用;如果是相对路径,就拿设备服务地址的 Scheme 和 Authority,手动拼出完整 URL。别小看这个细节,市面上一半的兼容性问题都出在这里。
5.2 现象:设备时间不对导致一直 401 —— 原因:PasswordDigest 里藏了一个时区盒
有一次在现场调试了一个多小时,设备始终回 401 Unauthorized,账号密码确认没问题,抓包看报文结构也完全正常。最后灵机一动去设备 Web 页面看了一眼系统时间,发现设备的系统时间比北京时间快了两个小时。问题就出在 Created 这个字段上:ONVIF 的鉴权机制会拿“当前时间”和报文里的 Created 进行比对,差值超过一定阈值就认为是非法请求。因为设备系统时间不准,我发的 UTC 时间转换完以后跟设备自己的时间偏差过大,就被拒了。
解决方法是两个方面一起做:一是代码里不要依赖本机时间,直接用 DateTime.Now.ToUniversalTime() 转 UTC;二是调试时碰到 401,先去看设备 Web 管理页面的时钟,如果歪了就先校时。设备校时可以通过 NTP 把时间同步上,实在不行就在 ONVIF 客户端里加一个 SetSystemDateAndTime 方法,代码里调用一次即可,但注意有的老设备不支持这个指令。
5.3 现象:RTSP 拉流 401 —— 原因:ONVIF 鉴权通过了,不代表 RTSP 鉴权也通过
在 ONVIF 层把设备信息拉得顺风顺水,但到了用 VLC 拉 RTSP 流的时候突然就 401。一开始我以为是自己代码的问题,但后来发现这是两种完全独立的鉴权体系。ONVIF 走的是 WS-Security UsernameToken,RTSP 走的是 RTSP 标准里的 Basic 或 Digest 摘要鉴权。两者账号密码可能相同,但鉴权流程和报文格式完全不一样,设备往往对这两套分别配置。
遇到 RTSP 401,主要是这几种情况:一是密码里有特殊字符,在 URL 里没做 URL 编码,比如 @、#、空格,直接拼上去会被解析器吃过;二是设备的 RTSP 端口不是默认的 554,GetStreamUri 返回的地址里虽然带了端口,但用自定义播放器时端口被覆盖成默认值了。常规处理方式是在 GetStreamUri 拿到的地址基础上,把密码部分用 Uri.EscapeDataString 转义一遍,同时不要信任默认端口,以返回的 URL 为准。
5.4 现象:设备能发现但 GetDeviceInformation 超时 —— 原因:设备只支持 SOAP 1.1,你却发了 1.2
有个少见但很恶心的兼容性陷阱:部分老设备,尤其是 2012 年前后的硬件,固件对 SOAP 版本的支持很挑剔。ONVIF 标准里 SOAP 1.2 是推荐版本,但一些老实现只完整支持 SOAP 1.1,对 1.2 的报文要么解析错误,要么直接不回。现象就是很干脆的超时。
排查方法也很直接:抓包看设备有没有回 TCP ACK,如果回包了但没有 SOAP Fault,那大概率是解析不了你发的 SOAP Action。这时候把 TextMessageEncodingBindingElement 的 MessageVersion 改成 Soap11WSAddressing10,多数情况就能解开这个结。WCF 这个切换成本很低,就在绑定的构造参数里改一下就行,只是需要注意 SOAP 1.1 时有些设备要求 WS-Addressing 头里带 To 和 Action,这个 WCF 会自动生成。在实际项目里,我做了一个双模式切换:先按 SOAP 1.2 试,超时就自动切 1.1 重试,两条腿走路,兼容性大幅提升。
6. 进阶技巧:从“能跑”到“好用”——WS-Discovery 批量扫描、能力协商与设备保活
项目走完前面的步骤,其实已经算一个合格的 ONVIF 客户端工具了。但要让它从“能跑”变成“好用”,还有三个细节值得收敛。第一是批量发现时的去重与过滤。WS-Discovery 组播有个毛病:同一台设备可能因为多网卡或者交换机行为,回复多条几乎相同的结果。在我的代码里,发现完成后会把 XAddr 放进 HashSet 去重,再按 IP 段做排序,让实际界面展示出来不会一屏重复的设备名。另外,如果你只需要特定品牌的设备,比如只需要天地伟业或海康的机器,可以直接拿设备的管理服务地址先做 WebRequest 探活,不行就直接跳过,能大幅减少无效的 SOAP 请求。
第二是能力协商要做得细致一些。ONVIF 的 Profile S 定了最低要求,但不同设备实际支持的功能集不一样。有的设备支持 PTZ,拿到的能力集里就没有对应的 PtzUrl;有的设备 GetStreamUri 返回的地址里根本不带鉴权参数。常规做法是:设备信息拉回来之后,逐个判断 Capabilities 里的 Media.XAddr、PTZ.XAddr、Events.XAddr 是否为空,为空就在界面上把对应的按钮禁用,防止用户点了报硬错误。
第三是设备的会话保活。部分设备会在长时间无操作后断开 TCP 连接,尤其是那些固件实现比较激进的型号。为了保住连接,我习惯在程序里起一个定时器,每隔 60 秒调一次 GetSystemDateAndTime。这个操作的负载极低,但能保证连接活着,还能顺便校准设备时间偏移。如果发现连续三次都失败,就说明设备网络断了或重启了,这时自动触发重新发现逻辑,把连接恢复回来。
最后分享我自己的一个教训:不要过度依赖发现协议。组播发现这个功能在跨 VLAN 的场景下极其不可靠,所以我在工程里几乎都会留一个手动添加设备的入口——用户直接输入 IP、端口、账号、密码,程序跳过发现,直接尝试跟设备服务握手。这个入口虽然不起眼,但在实际项目里反而是最稳的一条路。真正经历了现场才知道,组播报文飘不过三层交换机,而“手动输入”是永远有效的后悔药。
做 ONVIF 客户端这件事,本质上就是把一串串 SOAP 报文和一个个服务地址串起来。刚开始觉得繁琐,但一旦把底层逻辑理顺,再遇到什么新设备都不慌:先看报错,再看报文,最终都能找到解决方案。希望这篇笔记能帮你在 VS 2015 里省下几个不眠之夜,顺利把手里的 C# ONVIF 工具跑起来。
本文还有配套的精品资源,点击获取