本文主要讲解关于.NET 8实现modbus通讯工具类封装的操作方法相关内容,由优网导航(www.uonce.com)提供,欢迎关注收藏本站!
引言
Modbus 协议是工业自动化领域应用最广泛的通信协议之一,广泛应用于 PLC、传感器、仪表等设备之间的数据交换。在 .NET 8 中实现 Modbus 通讯工具类可以大大简化工业控制系统的开发工作。本文将详细介绍如何封装一个功能完整的 Modbus 工具类,支持 RTU 和 TCP 两种传输模式。

程序员导航
优网导航旗下整合全网优质开发资源,一站式IT编程学习与工具大全网站
1. Modbus 协议基础
Modbus 协议主要有两种传输模式:
- Modbus RTU:基于串行通信(RS232/RS485),使用二进制数据格式
- Modbus TCP:基于以太网通信,使用 TCP/IP 协议栈
1.1 Modbus 功能码
常用功能码:
- 0x01:读线圈状态
- 0x02:读输入状态
- 0x03:读保持寄存器
- 0x04:读输入寄存器
- 0x05:写单个线圈
- 0x06:写单个寄存器
- 0x0F:写多个线圈
- 0x10:写多个寄存器
2. Modbus 工具类设计
2.1 基础接口和枚举
public enum ModbusType
{
RTU,
TCP
}
public enum ModbusFunctionCode : byte
{
ReadCoils = 0x01,
ReadDiscreteInputs = 0x02,
ReadHoldingRegisters = 0x03,
ReadInputRegisters = 0x04,
WriteSingleCoil = 0x05,
WriteSingleRegister = 0x06,
WriteMultipleCoils = 0x0F,
WriteMultipleRegisters = 0x10
}
public interface IModbusClient : IDisposable
{
Task<bool> ConnectAsync();
void Disconnect();
bool IsConnected { get; }
// 读取操作
Task<bool[]> ReadCoilsAsync(byte slaveId, ushort startAddress, ushort numberOfCoils);
Task<bool[]> ReadDiscreteInputsAsync(byte slaveId, ushort startAddress, ushort numberOfInputs);
Task<ushort[]> ReadHoldingRegistersAsync(byte slaveId, ushort startAddress, ushort numberOfRegisters);
Task<ushort[]> ReadInputRegistersAsync(byte slaveId, ushort startAddress, ushort numberOfRegisters);
// 写入操作
Task<bool> WriteSingleCoilAsync(byte slaveId, ushort coilAddress, bool value);
Task<bool> WriteSingleRegisterAsync(byte slaveId, ushort registerAddress, ushort value);
Task<bool> WriteMultipleCoilsAsync(byte slaveId, ushort startAddress, bool[] values);
Task<bool> WriteMultipleRegistersAsync(byte slaveId, ushort startAddress, ushort[] values);
}

AI 工具导航
优网导航旗下AI工具导航,精选全球千款优质 AI 工具集
2.2 Modbus 异常处理
public class ModbusException : Exception
{
public byte ExceptionCode { get; }
public ModbusException(byte exceptionCode, string message)
: base(message)
{
ExceptionCode = exceptionCode;
}
}
public static class ModbusErrorCodes
{
public static readonly Dictionary<byte, string> Errors = new Dictionary<byte, string>
{
{ 0x01, "非法功能码" },
{ 0x02, "非法数据地址" },
{ 0x03, "非法数据值" },
{ 0x04, "从站设备故障" },
{ 0x05, "确认" },
{ 0x06, "从属设备忙" },
{ 0x07, "存储奇偶性错误" },
{ 0x08, "不可用网关路径" },
{ 0x09, "网关目标设备响应失败" },
{ 0x0A, "网关目标设备响应失败" }
};
}
3. Modbus RTU 实现
3.1 ModbusRtuClient 类
using System.IO.Ports;
using System.Threading.Tasks;
public class ModbusRtuClient : IModbusClient
{
private readonly SerialPort _serialPort;
private readonly int _responseTimeout;
private readonly byte _defaultSlaveId;
public bool IsConnected => _serialPort?.IsOpen ?? false;
public ModbusRtuClient(string portName, int baudRate = 9600, Parity parity = Parity.None,
int dataBits = 8, StopBits stopBits = StopBits.One,
int responseTimeout = 1000, byte defaultSlaveId = 1)
{
_serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits)
{
ReadTimeout = responseTimeout,
WriteTimeout = responseTimeout
};
_responseTimeout = responseTimeout;
_defaultSlaveId = defaultSlaveId;
}
public async Task<bool> ConnectAsync()
{
if (IsConnected) return true;
try
{
_serialPort.Open();
return true;
}
catch
{
return false;
}
}
public void Disconnect()
{
if (IsConnected)
{
_serialPort.Close();
}
}
public void Dispose()
{
Disconnect();
_serialPort.Dispose();
}
// 核心通信方法
private async Task<byte[]> SendReceiveAsync(byte slaveId, byte[] request)
{
if (!IsConnected) throw new InvalidOperationException("串口未连接");
// 添加CRC校验
byte[] frame = new byte[request.Length + 2];
Array.Copy(request, frame, request.Length);
ushort crc = CalculateCrc(request);
frame[request.Length] = (byte)(crc & 0xFF);
frame[request.Length + 1] = (byte)((crc >> 8) & 0xFF);
// 发送请求
_serialPort.DiscardInBuffer();
_serialPort.Write(frame, 0, frame.Length);
// 接收响应
int expectedLength = GetExpectedResponseLength(request[1]);
byte[] response = new byte[expectedLength + 2]; // +2 for CRC
int bytesRead = 0;
int totalBytesToRead = response.Length;
DateTime startTime = DateTime.Now;
while (bytesRead < totalBytesToRead)
{
if ((DateTime.Now - startTime).TotalMilliseconds > _responseTimeout)
{
throw new TimeoutException("读取响应超时");
}
if (_serialPort.BytesToRead > 0)
{
bytesRead += _serialPort.Read(response, bytesRead, totalBytesToRead - bytesRead);
}
else
{
await Task.Delay(10);
}
}
// 验证CRC
ushort receivedCrc = (ushort)(response[response.Length - 2] | (response[response.Length - 1] << 8));
ushort calculatedCrc = CalculateCrc(response, 0, response.Length - 2);
if (receivedCrc != calculatedCrc)
{
throw new ModbusException(0, "CRC校验失败");
}
// 检查异常响应
if ((response[1] & 0x80) != 0)
{
byte exceptionCode = response[2];
string errorMessage = ModbusErrorCodes.Errors.TryGetValue(exceptionCode, out string msg)
? msg : $"未知异常代码: 0x{exceptionCode:X2}";
throw new ModbusException(exceptionCode, errorMessage);
}
return response;
}
private ushort CalculateCrc(byte[] data, int offset = 0, int length = -1)
{
if (length == -1) length = data.Length - offset;
ushort crc = 0xFFFF;
for (int i = offset; i < offset + length; i++)
{
crc ^= data[i];
for (int j = 0; j < 8; j++)
{
if ((crc & 0x0001) != 0)
{
crc >>= 1;
crc ^= 0xA001;
}
else
{
crc >>= 1;
}
}
}
return crc;
}
private int GetExpectedResponseLength(byte functionCode)
{
switch (functionCode)
{
case (byte)ModbusFunctionCode.ReadCoils:
case (byte)ModbusFunctionCode.ReadDiscreteInputs:
return 3; // SlaveId + FC + ByteCount + (Data bytes) + CRC
case (byte)ModbusFunctionCode.ReadHoldingRegisters:
case (byte)ModbusFunctionCode.ReadInputRegisters:
return 3; // SlaveId + FC + ByteCount + (Data bytes) + CRC
case (byte)ModbusFunctionCode.WriteSingleCoil:
case (byte)ModbusFunctionCode.WriteSingleRegister:
return 6; // SlaveId + FC + Address + Value + CRC
case (byte)ModbusFunctionCode.WriteMultipleCoils:
case (byte)ModbusFunctionCode.WriteMultipleRegisters:
return 6; // SlaveId + FC + Address + Quantity + CRC
default:
throw new ArgumentException($"未知功能码: 0x{functionCode:X2}");
}
}
// 具体功能实现将在下面展开...
}
3.2 Modbus RTU 功能实现
// 在 ModbusRtuClient 类中添加以下方法
public async Task<bool[]> ReadCoilsAsync(byte slaveId, ushort startAddress, ushort numberOfCoils)
{
if (numberOfCoils < 1 || numberOfCoils > 2000)
throw new ArgumentException("线圈数量必须在1-2000之间");
byte[] request = new byte[6];
request[0] = slaveId;
request[1] = (byte)ModbusFunctionCode.ReadCoils;
request[2] = (byte)(startAddress >> 8);
request[3] = (byte)(startAddress & 0xFF);
request[4] = (byte)(numberOfCoils >> 8);
request[5] = (byte)(numberOfCoils & 0xFF);
byte[] response = await SendReceiveAsync(slaveId, request);
int byteCount = response[2];
bool[] coils = new bool[numberOfCoils];
for (int i = 0; i < numberOfCoils; i++)
{
int byteIndex = 3 + i / 8;
int bitIndex = i % 8;
coils[i] = (response[byteIndex] & (1 << bitIndex)) != 0;
}
return coils;
}
public async Task<ushort[]> ReadHoldingRegistersAsync(byte slaveId, ushort startAddress, ushort numberOfRegisters)
{
if (numberOfRegisters < 1 || numberOfRegisters > 125)
throw new ArgumentException("寄存器数量必须在1-125之间");
byte[] request = new byte[6];
request[0] = slaveId;
request[1] = (byte)ModbusFunctionCode.ReadHoldingRegisters;
request[2] = (byte)(startAddress >> 8);
request[3] = (byte)(startAddress & 0xFF);
request[4] = (byte)(numberOfRegisters >> 8);
request[5] = (byte)(numberOfRegisters & 0xFF);
byte[] response = await SendReceiveAsync(slaveId, request);
int byteCount = response[2];
ushort[] registers = new ushort[numberOfRegisters];
for (int i = 0; i < numberOfRegisters; i++)
{
int offset = 3 + i * 2;
registers[i] = (ushort)((response[offset] << 8) | response[offset + 1]);
}
return registers;
}
public async Task<bool> WriteSingleCoilAsync(byte slaveId, ushort coilAddress, bool value)
{
byte[] request = new byte[6];
request[0] = slaveId;
request[1] = (byte)ModbusFunctionCode.WriteSingleCoil;
request[2] = (byte)(coilAddress >> 8);
request[3] = (byte)(coilAddress & 0xFF);
request[4] = value ? (byte)0xFF : (byte)0x00;
request[5] = 0x00;
byte[] response = await SendReceiveAsync(slaveId, request);
// 验证响应
if (response.Length != 6) return false;
if (response[2] != request[2] || response[3] != request[3]) return false;
if (response[4] != request[4] || response[5] != request[5]) return false;
return true;
}
public async Task<bool> WriteMultipleRegistersAsync(byte slaveId, ushort startAddress, ushort[] values)
{
if (values == null || values.Length == 0 || values.Length > 123)
throw new ArgumentException("寄存器数量必须在1-123之间");
byte[] request = new byte[7 + values.Length * 2];
request[0] = slaveId;
request[1] = (byte)ModbusFunctionCode.WriteMultipleRegisters;
request[2] = (byte)(startAddress >> 8);
request[3] = (byte)(startAddress & 0xFF);
request[4] = (byte)(values.Length >> 8);
request[5] = (byte)(values.Length & 0xFF);
request[6] = (byte)(values.Length * 2);
for (int i = 0; i < values.Length; i++)
{
request[7 + i * 2] = (byte)(values[i] >> 8);
request[8 + i * 2] = (byte)(values[i] & 0xFF);
}
byte[] response = await SendReceiveAsync(slaveId, request);
// 验证响应
if (response.Length != 6) return false;
if (response[2] != request[2] || response[3] != request[3]) return false;
if (response[4] != request[4] || response[5] != request[5]) return false;
return true;
}
// 其他功能实现类似...
4. Modbus TCP 实现
4.1 ModbusTcpClient 类
using System.Net.Sockets;
using System.Threading.Tasks;
public class ModbusTcpClient : IModbusClient
{
private TcpClient _tcpClient;
private NetworkStream _networkStream;
private readonly string _host;
private readonly int _port;
private readonly int _responseTimeout;
private readonly byte _defaultSlaveId;
private ushort _transactionId = 0;
public bool IsConnected => _tcpClient?.Connected ?? false;
public ModbusTcpClient(string host, int port = 502, int responseTimeout = 1000, byte defaultSlaveId = 1)
{
_host = host;
_port = port;
_responseTimeout = responseTimeout;
_defaultSlaveId = defaultSlaveId;
}
public async Task<bool> ConnectAsync()
{
if (IsConnected) return true;
try
{
_tcpClient = new TcpClient();
await _tcpClient.ConnectAsync(_host, _port);
_networkStream = _tcpClient.GetStream();
_networkStream.ReadTimeout = _responseTimeout;
return true;
}
catch
{
return false;
}
}
public void Disconnect()
{
if (IsConnected)
{
_networkStream?.Close();
_tcpClient?.Close();
_tcpClient = null;
_networkStream = null;
}
}
public void Dispose()
{
Disconnect();
_tcpClient?.Dispose();
_networkStream?.Dispose();
}
// 核心通信方法
private async Task<byte[]> SendReceiveAsync(byte slaveId, byte[] pdu)
{
if (!IsConnected) throw new InvalidOperationException("TCP连接未建立");
// 构建MBAP头
byte[] mbapHeader = new byte[7];
ushort transactionId = _transactionId++;
mbapHeader[0] = (byte)(transactionId >> 8);
mbapHeader[1] = (byte)(transactionId & 0xFF);
mbapHeader[2] = 0x00; // 协议标识符高位
mbapHeader[3] = 0x00; // 协议标识符低位
mbapHeader[4] = (byte)((pdu.Length + 1) >> 8); // 长度高位
mbapHeader[5] = (byte)((pdu.Length + 1) & 0xFF); // 长度低位
mbapHeader[6] = slaveId; // 单元标识符
// 构建完整请求
byte[] request = new byte[mbapHeader.Length + pdu.Length];
Array.Copy(mbapHeader, request, mbapHeader.Length);
Array.Copy(pdu, 0, request, mbapHeader.Length, pdu.Length);
// 发送请求
await _networkStream.WriteAsync(request, 0, request.Length);
// 接收MBAP头
byte[] mbapResponse = new byte[7];
int bytesRead = await _networkStream.ReadAsync(mbapResponse, 0, mbapResponse.Length);
if (bytesRead != mbapResponse.Length)
{
throw new IOException("读取MBAP头不完整");
}
// 验证事务ID
ushort responseTransactionId = (ushort)((mbapResponse[0] << 8) | mbapResponse[1]);
if (responseTransactionId != transactionId)
{
throw new ModbusException(0, "事务ID不匹配");
}
// 获取PDU长度
int pduLength = (mbapResponse[4] << 8) | mbapResponse[5];
// 接收PDU
byte[] pduResponse = new byte[pduLength - 1]; // 减去单元标识符
bytesRead = await _networkStream.ReadAsync(pduResponse, 0, pduResponse.Length);
if (bytesRead != pduResponse.Length)
{
throw new IOException("读取PDU不完整");
}
// 检查异常响应
if ((pduResponse[0] & 0x80) != 0)
{
byte exceptionCode = pduResponse[1];
string errorMessage = ModbusErrorCodes.Errors.TryGetValue(exceptionCode, out string msg)
? msg : $"未知异常代码: 0x{exceptionCode:X2}";
throw new ModbusException(exceptionCode, errorMessage);
}
return pduResponse;
}
// 具体功能实现与RTU类似,只是使用PDU而不是完整帧
public async Task<ushort[]> ReadHoldingRegistersAsync(byte slaveId, ushort startAddress, ushort numberOfRegisters)
{
if (numberOfRegisters < 1 || numberOfRegisters > 125)
throw new ArgumentException("寄存器数量必须在1-125之间");
byte[] pdu = new byte[5];
pdu[0] = (byte)ModbusFunctionCode.ReadHoldingRegisters;
pdu[1] = (byte)(startAddress >> 8);
pdu[2] = (byte)(startAddress & 0xFF);
pdu[3] = (byte)(numberOfRegisters >> 8);
pdu[4] = (byte)(numberOfRegisters & 0xFF);
byte[] response = await SendReceiveAsync(slaveId, pdu);
int byteCount = response[1];
ushort[] registers = new ushort[numberOfRegisters];
for (int i = 0; i < numberOfRegisters; i++)
{
int offset = 2 + i * 2;
registers[i] = (ushort)((response[offset] << 8) | response[offset + 1]);
}
return registers;
}
// 其他功能实现类似...
}

免费在线工具导航
优网导航旗下整合全网优质免费、免注册的在线工具导航大全
5. 工厂模式创建 Modbus 客户端
public static class ModbusClientFactory
{
public static IModbusClient CreateClient(ModbusType type, string connectionString, byte defaultSlaveId = 1)
{
switch (type)
{
case ModbusType.RTU:
// 连接字符串格式: COM1,9600,None,8,One
string[] rtuParams = connectionString.Split(',');
if (rtuParams.Length < 1) throw new ArgumentException("无效的RTU连接字符串");
string portName = rtuParams[0];
int baudRate = rtuParams.Length > 1 ? int.Parse(rtuParams[1]) : 9600;
Parity parity = rtuParams.Length > 2 ? (Parity)Enum.Parse(typeof(Parity), rtuParams[2]) : Parity.None;
int dataBits = rtuParams.Length > 3 ? int.Parse(rtuParams[3]) : 8;
StopBits stopBits = rtuParams.Length > 4 ? (StopBits)Enum.Parse(typeof(StopBits), rtuParams[4]) : StopBits.One;
return new ModbusRtuClient(portName, baudRate, parity, dataBits, stopBits, defaultSlaveId: defaultSlaveId);
case ModbusType.TCP:
// 连接字符串格式: 192.168.1.100:502
string[] tcpParams = connectionString.Split(':');
if (tcpParams.Length < 1) throw new ArgumentException("无效的TCP连接字符串");
string host = tcpParams[0];
int port = tcpParams.Length > 1 ? int.Parse(tcpParams[1]) : 502;
return new ModbusTcpClient(host, port, defaultSlaveId: defaultSlaveId);
default:
throw new ArgumentException("不支持的Modbus类型");
}
}
}
6. 高级功能扩展
6.1 批量读取优化
public static class ModbusExtensions
{
public static async Task<Dictionary<ushort, ushort>> ReadHoldingRegistersRangeAsync(
this IModbusClient client, byte slaveId,
ushort startAddress, ushort endAddress, int maxPerRequest = 100)
{
var results = new Dictionary<ushort, ushort>();
ushort current = startAddress;
while (current <= endAddress)
{
ushort count = (ushort)Math.Min(maxPerRequest, endAddress - current + 1);
ushort[] values = await client.ReadHoldingRegistersAsync(slaveId, current, count);
for (int i = 0; i < values.Length; i++)
{
results[(ushort)(current + i)] = values[i];
}
current += count;
}
return results;
}
}
6.2 自动重连机制
public class AutoReconnectModbusClient : IModbusClient
{
private readonly IModbusClient _innerClient;
private readonly int _maxRetries;
private readonly int _retryDelay;
public bool IsConnected => _innerClient.IsConnected;
public AutoReconnectModbusClient(IModbusClient innerClient, int maxRetries = 3, int retryDelay = 1000)
{
_innerClient = innerClient;
_maxRetries = maxRetries;
_retryDelay = retryDelay;
}
public async Task<bool> ConnectAsync()
{
for (int i = 0; i < _maxRetries; i++)
{
if (await _innerClient.ConnectAsync())
return true;
await Task.Delay(_retryDelay);
}
return false;
}
public void Disconnect() => _innerClient.Disconnect();
public void Dispose() => _innerClient.Dispose();
private async Task<T> ExecuteWithRetry<T>(Func<Task<T>> operation)
{
for (int i = 0; i < _maxRetries; i++)
{
try
{
if (!_innerClient.IsConnected)
{
await ConnectAsync();
}
return await operation();
}
catch (IOException) when (i < _maxRetries - 1)
{
// 连接错误,尝试重连
Disconnect();
await Task.Delay(_retryDelay);
}
catch (TimeoutException) when (i < _maxRetries - 1)
{
// 超时错误,尝试重试
await Task.Delay(_retryDelay);
}
}
// 最后一次尝试
return await operation();
}
public async Task<ushort[]> ReadHoldingRegistersAsync(byte slaveId, ushort startAddress, ushort numberOfRegisters)
{
return await ExecuteWithRetry(() =>
_innerClient.ReadHoldingRegistersAsync(slaveId, startAddress, numberOfRegisters));
}
// 其他方法实现类似...
}
7. 使用示例
class Program
{
static async Task Main(string[] args)
{
// 创建Modbus TCP客户端
var tcpClient = ModbusClientFactory.CreateClient(
ModbusType.TCP, "192.168.1.100:502", defaultSlaveId: 1);
// 创建带自动重连的客户端
var autoReconnectClient = new AutoReconnectModbusClient(tcpClient);
try
{
// 连接设备
if (await autoReconnectClient.ConnectAsync())
{
Console.WriteLine("Modbus连接成功");
// 读取保持寄存器
ushort[] registers = await autoReconnectClient.ReadHoldingRegistersAsync(1, 0, 10);
Console.WriteLine("寄存器值: " + string.Join(", ", registers));
// 写入单个寄存器
bool writeResult = await autoReconnectClient.WriteSingleRegisterAsync(1, 5, 1234);
Console.WriteLine($"写入寄存器结果: {writeResult}");
// 批量读取寄存器范围
var registerMap = await autoReconnectClient.ReadHoldingRegistersRangeAsync(1, 0, 100);
foreach (var kvp in registerMap)
{
Console.WriteLine($"地址 {kvp.Key}: {kvp.Value}");
}
}
else
{
Console.WriteLine("Modbus连接失败");
}
}
catch (ModbusException ex)
{
Console.WriteLine($"Modbus错误: {ex.Message} (代码: 0x{ex.ExceptionCode:X2})");
}
catch (Exception ex)
{
Console.WriteLine($"发生错误: {ex.Message}");
}
finally
{
autoReconnectClient.Disconnect();
}
// 使用Modbus RTU
var rtuClient = ModbusClientFactory.CreateClient(
ModbusType.RTU, "COM3,9600,None,8,One", defaultSlaveId: 2);
try
{
if (await rtuClient.ConnectAsync())
{
// 读取线圈状态
bool[] coils = await rtuClient.ReadCoilsAsync(2, 0, 8);
Console.WriteLine("线圈状态: " + string.Join(", ", coils.Select(c => c ? "1" : "0")));
}
}
finally
{
rtuClient.Disconnect();
}
}
}
8. 最佳实践
1.连接管理:
- 保持连接时间尽可能短
- 使用自动重连机制处理网络波动
- 合理设置超时时间
2.错误处理:
- 捕获并处理所有Modbus异常
- 实现重试机制处理临时错误
- 记录详细错误日志
3.性能优化:
- 批量读取数据减少请求次数
- 使用异步方法避免阻塞
- 合理设置请求大小(避免过大PDU)
4.线程安全:
- Modbus客户端不是线程安全的
- 在多线程环境中使用锁或创建多个实例
- 避免并发访问同一连接
5.配置管理:
- 将Modbus配置存储在配置文件或数据库中
- 支持动态更新配置
- 提供配置验证功能
9. 总结
本文介绍了如何使用 .NET 8 实现一个功能完整的 Modbus 通讯工具类,主要特点包括:
1.双协议支持:
- 完整实现 Modbus RTU 和 Modbus TCP 协议
- 支持所有常用功能码
2.优雅封装:
- 统一的接口设计
- 工厂模式创建客户端
- 扩展方法提供高级功能
3.健壮性设计:
- 完善的错误处理机制
- 自动重连功能
- CRC 和事务ID验证
4.易用性:
- 简洁的API设计
- 详细的异常信息
- 丰富的使用示例
通过这种封装,开发者可以快速集成 Modbus 通讯功能到各种工业控制系统中,大大提高了开发效率和系统可靠性。该工具类适用于 SCADA 系统、MES 系统、设备监控平台等各种工业自动化场景。
以上就是.NET 8实现modbus通讯工具类封装的操作方法的详细内容,更多关于.NET 8 modbus工具类封装的资料请关注优网导航其它相关文章!
您可能感兴趣的文章:
- 在.NET 8 中使用中介模式优雅处理多版本 API 请求的实现方案
- .NET 8 中的 Keyed Services解锁依赖注入的新方式(最新推荐)
- .net8创建tcp服务接收数据通过websocket广播的实现代码
- .NET8 gRPC实现高效100G大文件断点续传工具
- .NET8中使用JWT进行身份认证和授权的实现
相关推荐: Prometheus运维实战:如何接入AlertManager配置邮件告警
如果大家还没搞定AlertManager与Prometheus的对接,要么看看其他公开教程,要么参考我之前写的部署文档——先把基础环境搭好,后面的配置才好推进。另外,要是这篇文章帮到你了,麻烦点个赞、转个发,多谢老铁们支持! 1 配置Prometheus监控告…



