当前位置: 首页 > article >正文

【实战】基于 ABP vNext 构建高可用 S7 协议采集平台(西门子 PLC 通信全流程)

🚀🔧【实战】基于 ABP vNext 构建高可用 S7 协议采集平台(西门子 PLC 通信全流程)📊


📑 目录

  • 🚀🔧【实战】基于 ABP vNext 构建高可用 S7 协议采集平台(西门子 PLC 通信全流程)📊
      • 一、背景与目标 🎯
      • 二、系统架构与技术栈 🏗️
        • 系统架构概览流程图 🏗️
      • 三、配置系统设计 ⚙️
        • 1. appsettings.json
        • 2. 配置类
        • 3. 热加载与 DI 注册(Program.cs)
      • 四、线程安全通信连接池设计 🔒
        • 连接池租借/归还流程图 🔒
      • 五、后台采集 Worker 实现 🤖
        • Worker 周期执行流程图 🤖
      • 六、重试与熔断策略注入 🔄
        • 重试与熔断策略流程图 🔄
      • 七、健康检查与指标上报 📈
      • 八、Docker 容器化部署建议 🐳
        • Docker 多阶段构建流程图 🐳
      • 九、总结与最佳实践清单 📝
      • 十、参考资料 📚


一、背景与目标 🎯

在工业自动化项目中,西门子 S7 系列 PLC 广泛用于设备控制与数据采集。传统 OPC 通信方式配置繁琐、延迟高,难以胜任现代 IoT 场景。

目标:

  • 构建跨平台、配置化的高可用通信平台;
  • 实现多台 PLC 并发采集、统一缓存与错误容忍;
  • 支持部署、监控、容器化与持续运行。

二、系统架构与技术栈 🏗️

系统架构概览流程图 🏗️
基础设施层
业务层
API 层
S7.NetPlus PLC 连接
Redis 缓存
Prometheus/ELK
PlcPollingWorker
PlcConnectionManager
IDistributedCache
HealthChecks
HTTP 接口
Worker 任务调度
模块技术选型
框架ABP vNext (.NET 8)
通信S7.NetPlus
配置IOptionsSnapshot + reloadOnChange + 环境变量
重试Polly (Retry + CircuitBreaker)
后台任务AbpBackgroundWorker
健康检查ASP.NET Core HealthChecks
日志Serilog + 结构化日志
容器部署Docker 多阶段构建

三、配置系统设计 ⚙️

1. appsettings.json
{"PlcOptions": {"IntervalSeconds": 5,"Devices": [{"DeviceId": "PLC-1","CpuType": "S7300","Ip": "192.168.1.100","Rack": 0,"Slot": 2,"Address": "DB1.DBW0"}]}
}
2. 配置类
public class PlcDeviceOptions
{public string DeviceId { get; set; } = null!;public string CpuType   { get; set; } = "S7300";public string Ip        { get; set; } = null!;public int    Rack      { get; set; }public int    Slot      { get; set; }public string Address   { get; set; } = null!;
}public class PlcOptions
{public int                  IntervalSeconds { get; set; } = 5;public List<PlcDeviceOptions> Devices       { get; set; } = new();
}
3. 热加载与 DI 注册(Program.cs)
var builder = WebApplication.CreateBuilder(args);// 配置源:JSON + 环境变量
builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true).AddEnvironmentVariables();// 注册配置
builder.Services.Configure<PlcOptions>(builder.Configuration.GetSection("PlcOptions"));// 注册核心服务
builder.Services.AddSingleton<PlcConnectionManager>();
builder.Services.AddPolicyRegistry().Add("PlcRetry",Policy.Handle<Exception>().RetryAsync(3, onRetry: (ex, cnt) =>builder.Logging.CreateLogger("Polly").LogWarning(ex, "第{Attempt}次重试失败", cnt)).CircuitBreakerAsync(handledEventsAllowedBeforeBreaking: 5,durationOfBreak: TimeSpan.FromSeconds(30),onBreak: (ex, ts) =>builder.Logging.CreateLogger("Polly").LogError(ex, "熔断开启,持续{Break}s", ts.TotalSeconds),onReset: () =>builder.Logging.CreateLogger("Polly").LogInformation("熔断恢复")));// 注册 ABP Worker 和 HealthChecks
builder.Services.AddBackgroundWorker<PlcPollingWorker>();
builder.Services.AddHealthChecks().AddCheck<PlcHealthCheck>("plc_check");var app = builder.Build();// 映射健康检查端点
app.MapHealthChecks("/health");// 优雅停机:释放连接池
app.Lifetime.ApplicationStopping.Register(async () =>
{await app.Services.GetRequiredService<PlcConnectionManager>().DisposeAsync();
});app.Run();

四、线程安全通信连接池设计 🔒

public class PlcConnectionManager : IAsyncDisposable
{// key → (信号量, 已打开的 Plc 实例)private readonly ConcurrentDictionary<string, (SemaphoreSlim Lock, Plc Plc)> _connections = new();/// <summary>/// 租借一个 PLC 实例(线程安全)/// </summary>public async Task<Plc> RentAsync(PlcDeviceOptions opt){var key = opt.DeviceId;var entry = _connections.GetOrAdd(key, _ =>{var plc = new Plc(Enum.Parse<CpuType>(opt.CpuType, ignoreCase: true),opt.Ip, opt.Rack, opt.Slot);try{plc.Open();}catch{// 打开失败,移除池中该项_connections.TryRemove(key, out _);throw;}return (new SemaphoreSlim(1, 1), plc);});// 等待获取信号量await entry.Lock.WaitAsync();return entry.Plc;}/// <summary>/// 归还租借的实例/// </summary>public void Return(string deviceId){if (_connections.TryGetValue(deviceId, out var entry)){entry.Lock.Release();}}/// <summary>/// 优雅释放所有资源/// </summary>public async ValueTask DisposeAsync(){foreach (var kv in _connections.Values){var sema = kv.Lock;await sema.WaitAsync();       // 确保没有并发占用kv.Plc.Close();sema.Release();sema.Dispose();}}/// <summary>/// 获取所有断线设备列表/// </summary>public IEnumerable<string> GetDisconnectedDevices() =>_connections.Where(kv => !kv.Value.Plc.IsConnected).Select(kv => kv.Key);
}
连接池租借/归还流程图 🔒
开始 RentAsync(Device)
池中存在?
获取 (Semaphore, Plc)
创建新 Plc 实例并 Open()
存入 ConcurrentDictionary
Await Semaphore.WaitAsync()
返回 Plc 给调用方
调用完成后执行 Return(DeviceId)
Semaphore.Release()
流程结束

五、后台采集 Worker 实现 🤖

public class PlcPollingWorker : AsyncPeriodicBackgroundWorkerBase
{private readonly IOptionsSnapshot<PlcOptions> _options;private readonly PlcConnectionManager          _connMgr;private readonly ILogger<PlcPollingWorker>     _logger;private readonly IDistributedCache<string>     _cache;private readonly AsyncPolicy                   _retry;public PlcPollingWorker(AbpTimer                       timer,IOptionsSnapshot<PlcOptions>   options,PlcConnectionManager           connMgr,IDistributedCache<string>      cache,IPolicyRegistry<string>        policyRegistry,ILogger<PlcPollingWorker>      logger) : base(timer){_options = options;_connMgr  = connMgr;_cache    = cache;_retry    = policyRegistry.Get<AsyncPolicy>("PlcRetry");_logger   = logger;Timer.PeriodTimeSpan = TimeSpan.FromSeconds(_options.Value.IntervalSeconds);}protected override async Task DoWorkAsync(PeriodicBackgroundWorkerContext ctx){foreach (var dev in _options.Value.Devices){using var scope = _logger.BeginScope("Device:{DeviceId}", dev.DeviceId);Plc plc = null!;try{plc = await _connMgr.RentAsync(dev);// 异步执行阻塞调用var val = await _retry.ExecuteAsync(() =>Task.Run(() => (short)plc.Read(dev.Address)));await _cache.SetAsync($"Plc:{dev.DeviceId}:Value",val.ToString(),new DistributedCacheEntryOptions{SlidingExpiration = TimeSpan.FromSeconds(30)});_logger.LogInformation("{Device} OK - {Val}", dev.DeviceId, val);}catch (Exception ex){_logger.LogWarning(ex, "Device {DeviceId} 读取失败", dev.DeviceId);}finally{if (plc is not null){_connMgr.Return(dev.DeviceId);}}}}
}
Worker 周期执行流程图 🤖
Timer PlcPollingWorker PlcConnectionManager PollyRetryPolicy IDistributedCache ILogger 定时触发 DoWorkAsync() RentAsync(dev) 返回 plc 实例 ExecuteAsync(Read(dev.Address)) 返回 value SetAsync(key, value) LogInformation(...) Return(dev) loop [遍历每台设备] LogWarning(...) alt [出现异常] Timer PlcPollingWorker PlcConnectionManager PollyRetryPolicy IDistributedCache ILogger

六、重试与熔断策略注入 🔄

// 在 Program.cs 中已注册:
builder.Services.AddPolicyRegistry().Add("PlcRetry",Policy.Handle<Exception>().RetryAsync(3, onRetry: (ex, cnt) =>logger.LogWarning(ex, "第{Attempt}次重试失败", cnt)).CircuitBreakerAsync(handledEventsAllowedBeforeBreaking: 5,durationOfBreak: TimeSpan.FromSeconds(30),onBreak: (ex, ts) =>logger.LogError(ex, "熔断开启,持续{Break}s", ts.TotalSeconds),onReset: () =>logger.LogInformation("熔断已恢复")));
重试与熔断策略流程图 🔄
成功
失败
失败
失败
重试下一次
RetryAsync 第1次
返回结果
RetryAsync 第2次
RetryAsync 第3次
CircuitBreaker 计数 +1
失败次数 ≥5?
熔断开启: 30s
拒绝所有后续调用
onReset 恢复后重置计数

七、健康检查与指标上报 📈

public class PlcHealthCheck : IHealthCheck
{private readonly PlcConnectionManager _manager;public PlcHealthCheck(PlcConnectionManager manager) =>_manager = manager;public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext ctx,CancellationToken    ct = default){var down = _manager.GetDisconnectedDevices().ToList();return Task.FromResult(down.Any()? HealthCheckResult.Unhealthy($"失联设备: {string.Join(", ", down)}"): HealthCheckResult.Healthy("所有 PLC 均连接正常"));}
}
  • 已通过 app.MapHealthChecks("/health") 暴露
  • 可集成 Prometheus/OpenTelemetry 暴露 /metrics

八、Docker 容器化部署建议 🐳

# 构建阶段
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY . .
RUN dotnet publish -c Release -o /app# 运行阶段
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY --from=build /app ./# 安装健康探针依赖并清理
RUN apt-get update \&& apt-get install -y --no-install-recommends curl \&& rm -rf /var/lib/apt/lists/*HEALTHCHECK --interval=10s \CMD curl --fail http://localhost:5000/health || exit 1ENTRYPOINT ["dotnet", "S7Reader.HttpApi.Host.dll"]
Docker 多阶段构建流程图 🐳
Runtime 阶段
COPY /app
FROM aspnet:8.0
apt-get install curl
HEALTHCHECK
ENTRYPOINT dotnet S7Reader.HttpApi.Host.dll
Build 阶段
dotnet restore
checkout 源码
dotnet build
dotnet publish -o /app

九、总结与最佳实践清单 📝

分类实践建议
配置管理IOptionsSnapshot + reloadOnChange + 环境变量,实现热更新
DI 注册AddSingleton()
AddBackgroundWorker()
AddHealthChecks()/MapHealthChecks
连接池设计租借/归还模式 + 异常清理
IAsyncDisposable 优雅关闭
异步与容错Task.Run 封装阻塞调用
Polly Retry + CircuitBreaker
日志 onBreak/onReset
后台调度ABP Worker 框架周期执行
外层全捕异常,保持服务持续运行
健康监控精准检测每台设备状态
HealthChecks + Prometheus/OpenTelemetry
容器部署Docker 多阶段构建
无冗余依赖镜像
标准 HEALTHCHECK
优雅停机ApplicationStopping.ReleaseAsync → DisposeAsync
日志可视化Serilog 结构化 + BeginScope “设备ID”上下文

十、参考资料 📚

  • S7.NetPlus GitHub
  • ABP vNext 官方文档
  • Polly 异常处理库
  • ASP.NET Core HealthChecks
  • Serilog 结构化日志

相关文章:

【实战】基于 ABP vNext 构建高可用 S7 协议采集平台(西门子 PLC 通信全流程)

&#x1f680;&#x1f527;【实战】基于 ABP vNext 构建高可用 S7 协议采集平台&#xff08;西门子 PLC 通信全流程&#xff09;&#x1f4ca; &#x1f4d1; 目录 &#x1f680;&#x1f527;【实战】基于 ABP vNext 构建高可用 S7 协议采集平台&#xff08;西门子 PLC 通信全…...

数据结构:树(Tree)

目录 为什么需要树&#xff1f; &#x1f331; 基本的树结构定义 什么是树&#xff1f; 树的术语 &#x1f33f; 常见基本树的变体 &#x1f333; 二叉搜索树&#xff08;BST&#xff09; &#x1f332; 自平衡二叉搜索树 1. AVL树&#xff08;Adelson-Velsky and La…...

自动化测试与功能测试详解

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 什么是自动化测试? 自动化测试是指利用软件测试工具自动实现全部或部分测试&#xff0c;它是软件测试的一个重要组成 部分&#xff0c;能完成许多手工测试无…...

java中的Optional

在 Java 8 中&#xff0c;Optional 是一个用于处理可能为 null 的值的容器类&#xff0c;旨在减少空指针异常&#xff08;NullPointerException&#xff09;并提升代码的可读性。以下是 Optional 的核心用法和最佳实践&#xff1a; 1. 创建 Optional 对象 1.1 常规创建方式 Op…...

Qt事件循环机制

受事件循环机制影响&#xff0c;按钮的样式表改变了可能不会立即刷新。 需要使用 update() 或 repaint() 或者调用 QApplication::processEvents() 强制处理所有待处理的事件&#xff0c;从而确保界面更新。 在 Qt 中&#xff0c;事件循环&#xff08;Event Loop&#xff09;是…...

深入理解 OAuth 2.0:技术核心与实战场景

在互联网应用日益复杂的今天&#xff0c;如何安全、高效地实现第三方应用授权访问资源&#xff0c;成为开发者面临的重要问题。OAuth 2.0 凭借其灵活、安全的授权机制&#xff0c;成为解决这一问题的主流方案。本文将深入剖析 OAuth 2.0 的技术重点&#xff0c;并结合具体使用场…...

Rust 环境变量管理秘籍:从菜鸟到老鸟都爱的 dotenv 教程

前言 写代码的你,是否遭遇过这些灵魂拷问: “我现在在哪个环境?开发?测试?还是直接在生产线上裸奔?”“少写一个 .env,测试脚本在数据库里上演清空大法,客户当场破防。”“每次手动设置 RUST_ENV,命令敲到一半就开始怀疑人生,还怕输错一个字符引发灭世级事故。”别慌…...

CSS经典布局之圣杯布局和双飞翼布局

目标&#xff1a; 中间自适应&#xff0c;两边定宽&#xff0c;并且三栏布局在一行展示。 圣杯布局 实现方法&#xff1a; 通过float搭建布局margin使三列布局到一行上relative相对定位调整位置&#xff1b; 给外部容器添加padding&#xff0c;通过相对定位调整左右两列的…...

OpenCV 的 CUDA 模块中用于将多个单通道的 GpuMat 图像合并成一个多通道的图像 函数cv::cuda::merge

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 在 OpenCV 的 CUDA 模块中&#xff0c;cv::cuda::merge 函数用于将多个单通道的 GpuMat 图像合并成一个多通道的图像。该函数是 cv::merge 的 GP…...

计网实验笔记(一)CS144 Lab

Lab0 ByteStream : 实现一个在内存中的 有序可靠字节流Lab1 StreamReassembler&#xff1a;实现一个流重组器&#xff0c;一个将字节流的字串或者小段按照正确顺序来拼接回连续字节流的模块Lab2 TCPReceiver&#xff1a;实现入站字节流的TCP部分。Lab3 TCPSender&#xff1a;实…...

Blog Contents

目录 Python Financing Medical Logistics Tool(IT & AI) 持续更新~ Python # Name URL 1 Python | Dashboard制作 Python | Dashboard制作-CSDN博客 2 Python | AKShare获取A股数据 Python | AKShare获取A股数据-CSDN博客 3 Python | A股指标对比 Python | A股…...

什么是ERP?ERP有哪些功能?小微企业ERP系统源码,SpringBoot+Vue+ElementUI+UniAPP

什么是ERP&#xff1f; ERP翻译过来叫企业资源计划&#xff0c;通俗的讲&#xff0c;应该叫企业的全面预算控制&#xff0c;其通常包括三个部分&#xff1a;工程预算、投资预算和经营预算&#xff08;即产销存预算&#xff09;。之所以做预算控制&#xff0c;是因为企业运作的…...

dockerfile: PaddleOCR hubserving api 服务

前言 目前 OCR 有比较成熟的方案&#xff0c;想着直接通过 docker 部署一个提供 api 接口服务&#xff0c;查看了一些开源方案&#xff0c;最终发现还是 PaddleOCR 比较好用。 本篇不介绍 PaddleOCR 的详细使用方式&#xff0c;只介绍一下构建镜像的 dockerfile 需要注意的事…...

【速写】TRL:Trainer的细节与思考(PPO/DPO+LoRA可行性)

序言 问题缘起来自发现PPOTrainer里并没有跟SFTTrainer类似的peft_config参数&#xff0c;而SFTTrainer在带和不带peft_config参数的情况下分别对应高效微调和全量微调。自然就会想到是否可以把PPO和PEFT结合&#xff0c;但是目前peft包和trl包上似乎还是存在这种兼容性的问题…...

Vue3+uniapp 封装axios

1.第一步在项目根目录新建utils文件夹&#xff0c;里边新建两个文件request.js和uni-api-promisify.js 2.request.js 代码 要安装axios import axios from axios import { showToast } from /utils/uni-api-promisify// 创建axios实例 const service axios.create({baseURL:…...

QEMU模拟32位ARM实现自定义系统调用

实现自定义系统调用 如何使用 QEMU 模拟32位 ARM 环境参考&#xff1a;使用Qemu模拟32位ARM系统 修改linux内核源码 使用 linux-4.4.240 源码&#xff0c;下载链接&#xff1a;下载链接 在 arch\arm\include\uapi\asm\unistd.h 文件下新增系统调用 sys_test&#xff1a; /…...

MySQL——数据类型表的约束

目录 数据类型 数值类型 tinyint类型 bit类型 float类型 decimal类型 字符类型 char类型 varchar类型 日期和时间类型 选择类型 表的约束 null default comment zerofill primary key auto_increment unique key foreign key 数据类型 在MySQL中的数据类…...

# YOLOv2:目标检测的升级之作

YOLOv2&#xff1a;目标检测的升级之作 在目标检测领域&#xff0c;YOLO&#xff08;You Only Look Once&#xff09;系列算法以其高效的速度和创新的检测方式受到了广泛关注。今天&#xff0c;我们就来深入探讨一下 YOLOv2&#xff0c;看看它是如何在继承 YOLOv1 的基础上进行…...

【爬虫】DrissionPage-1

官网地址&#xff1a;DrissionPage官网 小需求采集&#xff0c;我喜欢&#xff0c;我要学。 1 介绍 这是用python编写的爬虫自动化工具&#xff0c;将Selenium 和 Requests 的功能巧妙地整合在一起&#xff0c;提供了统一又简单的操作接口。开发者可以在浏览器模式&#xff0…...

Oracle OCP认证考试考点详解083系列15

题记&#xff1a; 本系列主要讲解Oracle OCP认证考试考点&#xff08;题目&#xff09;&#xff0c;适用于19C/21C,跟着学OCP考试必过。 71. 第71题&#xff1a; 题目 解析及答案&#xff1a; 关于在 Oracle 18c 及更高版本中基于 Oracle 黄金镜像的安装&#xff0c;以下哪…...

java刷题基础知识

List<int[]> merged new ArrayList<int[]>(); return merged.toArray(new int[merged.size()][]); 表示一个存储 int[] 类型元素的列表&#xff0c;list灵活支持扩展&#xff0c;因为不知道最后有几个区间&#xff0c;所以用list&#xff0c;最后toArray返回成数组…...

部署大模型:解决ollama.service: Failed with result ‘exit-code‘的问题

起因是这样: Loaded: loaded (/etc/systemd/system/ollama.service; disabled; preset: enabled) Active: activating (auto-restart) (Result: exit-code) since Tue 2025-05-13 19:31:19 CST; > Process: 12272 ExecStart/usr/bin/ollama serve (codeexited, status1/FAI…...

阿克曼-幻宇机器人系列教程2- 机器人交互实践(Topic)

在上一篇文章中&#xff0c;我们介绍了两种登录机器人的方式&#xff0c;接下来我们介绍登录机器人之后&#xff0c;我们如何通过topic操作命令实现与机器人的交互。 1. 启动 & 获取topic 在一个终端登录树莓派后&#xff0c;执行下列命令运行机器人 roslaunch huanyu_r…...

Spring AI 开发本地deepseek对话快速上手笔记

Spring AI Spring AI是一个旨在推进生成式人工智能应用程序发展的项目&#xff0c;Spring AI的核心目标是提供高度抽象化的组件&#xff0c;作为开发AI应用程序的基础&#xff0c;使得开发者能够以最少的代码改动便捷地交换和优化功能模块‌ 在开发之前先得引入大模型&#xf…...

SpringBoot中的拦截器

SpringBoot中的拦截器 Filter 典型场景 全局鉴权/接口耗时统计 WebFilter("/*") public class CostFilter implements Filter {Overridepublic void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {long start System.currentTimeMill…...

Spark,IDEA编写Maven项目

以下是在IDEA中使用Maven构建Spark项目的步骤&#xff1a; 一、环境准备 1. 安装JDK - 确保IDEA配置了JDK 8&#xff08;推荐11&#xff09;。 2. 安装Maven - 配置Maven环境变量&#xff0c;IDEA中设置Maven路径&#xff08; File > Settings > Build > Maven &#…...

半小时快速入门Spring AI:使用腾讯云编程助手CodeBuddy 开发简易聊天程序

引言 随着人工智能&#xff08;AI&#xff09;技术的飞速发展&#xff0c;越来越多的开发者开始探索如何将AI集成到自己的应用中。人工智能正在迅速改变各行各业的工作方式&#xff0c;从自动化客服到智能推荐系统&#xff0c;AI的应用几乎无处不在。Spring AI作为一种开源框架…...

【PostgreSQL数据分析实战:从数据清洗到可视化全流程】金融风控分析案例-10.3 风险指标可视化监控

&#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 文章大纲 PostgreSQL金融风控分析之风险指标可视化监控实战一、引言二、案例背景三、数据准备&#xff08;一&#xff09;数据来源与字段说明&#xff08;二&#xff09;数据清洗 四、…...

数学复习笔记 6

前言 复习一下行列式的一些基本的题。感觉网课有点没跟上了。今天花点时间跟上网课的进度。要紧跟进度&#xff0c;然后剩下的时间再去复习前面的内容。多复习&#xff0c;提升自己的解题能力。 行列式和矩阵 三年级&#xff0c;我现在是三年级下册。。。马上就要结束大学的…...

微服务的“导航系统”:使用Spring Cloud Eureka实现服务注册与发现

在上一篇中&#xff0c;我们理解了微服务架构的核心理念以及Spring Cloud为我们提供的强大工具集。我们提到&#xff0c;微服务架构的一个核心挑战在于&#xff0c;服务实例的网络位置是动态的&#xff0c;服务之间需要一种机制来互相定位。 想象一下&#xff0c;你开了一家新…...