从零搭建工业控制系统(二十五):Web API集成与数据上报

从零搭建工业控制系统(二十五):Web API集成与数据上报 Web API集成与数据上报这是「从零搭建工业控制系统」系列第25篇。前面讲了本地数据存储这篇讲远程上报——怎么把采集的数据推送到Web服务器怎么处理网络断开。为什么需要数据上报工业设备产生的数据不只是本地用。远程监控、数据分析、报表生成都需要数据集中到服务器。项目里的气体流量数据需要上报到Web API远程监控页面实时显示流量数据分析平台统计历史趋势管理层报表自动生成HttpClient使用.NET的HttpClient用于HTTP请求。关键原则HttpClient必须是单例或静态不能每次new。// 错误每次new会导致端口耗尽publicasyncTaskPostDataAsync(objectdata){usingvarclientnewHttpClient();// ❌awaitclient.PostAsync(url,content);}// 正确单例复用privatestaticreadonlyHttpClient_httpClientnewHttpClient(){TimeoutTimeSpan.FromSeconds(10)};publicasyncTaskPostDataAsync(objectdata){varjsonJsonSerializer.Serialize(data);varcontentnewStringContent(json,Encoding.UTF8,application/json);await_httpClient.PostAsync(url,content);}每次new HttpClient会创建新的TCP连接频繁创建会导致端口耗尽TIME_WAIT状态堆积。单例复用连接池。数据推送格式publicclassGasFlowReport{publicstringDeviceId{get;set;}publicDateTimeTimestamp{get;set;}publicListChannelDataChannels{get;set;}}publicclassChannelData{publicstringChannelName{get;set;}publicdoubleFlowValue{get;set;}publicstringUnit{get;set;}}序列化成JSON推送varreportnewGasFlowReport{DeviceIdDevice-A,TimestampDateTime.Now,Channelschannels.Select(cnewChannelData{ChannelNamec.Name,FlowValuec.CurrentValue,Unitsccm}).ToList()};varjsonJsonSerializer.Serialize(report);await_httpClient.PostAsync(apiUrl,newStringContent(json,Encoding.UTF8,application/json));重试机制网络不稳定推送可能失败。用指数退避重试publicasyncTaskboolPostWithRetryAsync(stringurl,stringjson,intmaxRetries3){for(inti0;imaxRetries;i){try{varresponseawait_httpClient.PostAsync(url,newStringContent(json,Encoding.UTF8,application/json));if(response.IsSuccessStatusCode)returntrue;}catch(HttpRequestException){// 网络异常等一会再试}awaitTask.Delay(TimeSpan.FromSeconds(Math.Pow(2,i)));// 1s, 2s, 4s}returnfalse;}3次重试间隔1秒→2秒→4秒。3次都失败就放弃这批数据下次采集时再推。网络断开时数据不丢如果网络断开推送失败的数据不能丢。存到本地队列网络恢复后补发privatereadonlyQueueGasFlowReport_pendingQueuenew();publicasyncTaskReportAsync(GasFlowReportdata){boolsuccessawaitPostWithRetryAsync(url,JsonSerializer.Serialize(data));if(!success){_pendingQueue.Enqueue(data);// 失败的存队列_logger.Warn($数据上报失败已加入待发队列队列长度{_pendingQueue.Count});}}// 定时检查队列privateasyncTaskFlushPendingQueueAsync(){while(_pendingQueue.Count0awaitIsApiAvailableAsync()){vardata_pendingQueue.Peek();if(awaitPostWithRetryAsync(url,JsonSerializer.Serialize(data)))_pendingQueue.Dequeue();// 成功了才出队elsebreak;// 还是失败下次再试}}API可用性检测定期探活确认API服务在线privateasyncTaskboolIsApiAvailableAsync(){try{varresponseawait_httpClient.GetAsync(${apiBaseUrl}/health);returnresponse.IsSuccessStatusCode;}catch{returnfalse;}}每60秒探一次。在线才补发队列数据离线就不浪费请求。批量上报 vs 实时上报模式优点缺点实时上报数据及时请求频繁网络压力大批量上报网络压力小数据有延迟混合模式平衡实现复杂实际用混合模式正常情况下每30秒批量推一次报警时立即推。踩坑记录坑1HttpClient频繁new导致端口耗尽上线后发现程序运行几小时后无法发HTTP请求。排查发现是HttpClient用完就DisposeTCP连接进入TIME_WAIT状态端口耗尽。改成单例后解决。坑2序列化大对象慢一次推5000条数据JSON序列化花了2秒阻塞了采集线程。改成在后台线程序列化不阻塞采集。坑3网络断开时内存涨网络断开后数据全存内存队列断了一上午内存涨了200MB。加了队列上限——超过1000条就丢弃最老的保最新数据优先。本篇小结概念关键做法HttpClient单例复用不每次new数据格式JSON序列化重试指数退避最多3次断网不丢失败存队列恢复后补发探活60秒一次health检查上报策略正常30秒批量报警立即推数据上报的核心网络不稳定是常态。重试本地队列探活保证数据不丢、不阻塞采集。下期预告第26篇调试标记系统数据上报讲完了下篇讲调试技巧——怎么用.tag文件切换开发模式和生产行为。