用c# 自己封装的Modbus工具类库源码

  前言

        Modbus通讯协议在工控行业的应用是很多的,并且也是上位机开发的基本技能之一。相关的类库也很多也很好用。以前只负责用,对其并没有深入学习和了解。前段时间有点空就在这块挖了挖。想做到知其然还要知其所以然。所以就有了自己封装的Modbus工具类库的想法。一来是练练手,二来是自己封装的用的更顺手。

        Modbus通讯协议我在工作中目前只用到了两种一个是串口通讯ModbusRTU,还有一个是网络通讯ModbusTcp。所以本文只有这两种通讯的实现。

设计思想

        C#是高级语言有很多好用的东西,如面像对像,设计模式等。但我在工作中还是经常看到面像过程的编程。如有多个串口设备就有多个代码类似的工具类。代码重复非常严重。我认为这种事还是要发点时间总结和代码一下代码,把它封装工具类库。以便后继在其他上的使用。

        本次的封装用了一点面像对像的方法,设计了一个多个Modbus 基类将一些公共方法放在基类中,子类就可以继续使用。不同的子类有不同的功能。可以按需调用。使用简单方便。

调用示例

        

var _serialPort = new ModbusRTUCoil(portName, baudRate, parity, dataBits, stopBits);
var isOk = false;
var resultModel = _serialPort.ReadDataCoil(1, 1, ModbusFunctionCode.ReadInputCoil, (ushort)type.GetHashCode());
if (resultModel.ResultList != null && resultModel.ResultList.Count > 0)
{isOk = resultModel.ResultList.FirstOrDefault();
}

类库项目结构

        

代码

        Modbus结果实体

        

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CJH.ModbusTool
{/// <summary>/// Modbus结果实体/// </summary>/// <typeparam name="DateType"></typeparam>public class ModbusResultModel{public ModbusResultModel(){IsSucceed = false;Msg = "失败(默认)";}private bool _isSucceed = false;/// <summary>/// 是否成功/// </summary>public bool IsSucceed{get{return _isSucceed;}set{_isSucceed = value;if (IsSucceed){Msg = "成功";}}}/// <summary>/// 返回消息/// </summary>public string Msg { get; set; }/// <summary>/// 发送报文/// </summary>public string SendDataStr { get; set; }/// <summary>/// 原始数据/// </summary>public byte[] Datas { get; set; }}/// <summary>/// Modbus结果实体/// </summary>/// <typeparam name="DateType"></typeparam>public class ModbusResultModel<DateType> : ModbusResultModel{public ModbusResultModel() : base(){ResultList = new List<DateType>();}/// <summary>/// 解析后的数据/// </summary>public List<DateType> ResultList { get; set; }}
}

Modbus 基类

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CJH.ModbusTool
{/// <summary>/// Modbus 基类/// </summary>public abstract class ModbusBase{/// <summary>/// 生成读取报文的 公共方法/// </summary>/// <param name="devAddr">从站地址</param>/// <param name="length">寄存器数量</param>/// <param name="functionCode">功能码</param>/// <param name="startAddr">起始寄存器地址</param>/// <returns>返回报文(协议格式:站地址+功能码+起始寄存器地址+寄存器数量)</returns>protected byte[] GenerateReadCommandBytes(byte devAddr, ushort length, ModbusFunctionCode functionCode = ModbusFunctionCode.ReadRegister, ushort startAddr = 0){//1.拼接报文:var sendCommand = new List<byte>();//协议格式:站地址+功能码+起始寄存器地址+寄存器数量//站地址sendCommand.Add(devAddr);//功能码sendCommand.Add((byte)functionCode.GetHashCode());//起始寄存器地址sendCommand.Add((byte)(startAddr / 256));sendCommand.Add((byte)(startAddr % 256));//寄存器数量sendCommand.Add((byte)(length / 256));sendCommand.Add((byte)(length % 256));//CRC//byte[] crc = Crc16(sendCommand.ToArray(), sendCommand.Count);//sendCommand.AddRange(crc);return sendCommand.ToArray();}/// <summary>/// 生成读取报文的 公共方法/// </summary>/// <param name="devAddr">从站地址</param>/// <param name="data">定入数据</param>/// <param name="functionCode">功能码</param>/// <param name="startAddr">写入地址</param>/// <returns>返回报文(协议格式:站地址+功能码+起始寄存器地址+寄存器数量)</returns>protected byte[] GenerateWriteCommandBytes(byte devAddr, ushort data, ModbusFunctionCode functionCode = ModbusFunctionCode.ReadRegister, ushort startAddr = 0){//1.拼接报文:var sendCommand = new List<byte>();//协议格式:站地址+功能码+起始寄存器地址+寄存器数量//站地址sendCommand.Add(devAddr);//功能码sendCommand.Add((byte)functionCode.GetHashCode());//写入地址sendCommand.Add((byte)(startAddr / 256));sendCommand.Add((byte)(startAddr % 256));//写入数据var temp_bytes = BitConverter.GetBytes(data);if (BitConverter.IsLittleEndian){//temp_bytes.Reverse();Array.Reverse(temp_bytes);}sendCommand.AddRange(temp_bytes);//CRC//byte[] crc = Crc16(sendCommand.ToArray(), sendCommand.Count);//sendCommand.AddRange(crc);return sendCommand.ToArray();}/// <summary>/// 生成发送命令报文/// </summary>/// <param name="sendCommand"></param>/// <returns></returns>protected string generateSendCommandStr(byte[] sendCommand){var sendCommandStr = string.Empty;foreach (var item in sendCommand){sendCommandStr += Convert.ToString(item, 16) + " ";}return sendCommandStr;}/// <summary>/// 验证CRC/// </summary>/// <param name="value">要验证的数据</param>/// <returns></returns>protected bool CheckCRC(byte[] value){var isOk = false;if (value != null && value.Length >= 2){int length = value.Length;byte[] buf = new byte[length - 2];Array.Copy(value, 0, buf, 0, buf.Length);//自己验证的结果byte[] CRCbuf = Crc16(buf, buf.Length);//把上面验证的结果和串口返回的校验码(最后两个)进行比较if (CRCbuf[0] == value[length - 2] && CRCbuf[1] == value[length - 1]){isOk = true;}}return isOk;}protected byte[] Crc16(byte[] pucFrame, int usLen){int i = 0;byte[] res = new byte[2] { 0xFF, 0xFF };ushort iIndex;while (usLen-- > 0){iIndex = (ushort)(res[0] ^ pucFrame[i++]);res[0] = (byte)(res[1] ^ aucCRCHi[iIndex]);res[1] = aucCRCLo[iIndex];}return res;}protected readonly byte[] aucCRCHi = {0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81, 0x40};protected readonly byte[] aucCRCLo = {0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7,0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E,0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9,0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC,0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32,0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D,0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38,0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF,0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1,0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4,0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB,0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA,0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0,0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97,0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E,0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89,0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83,0x41, 0x81, 0x80, 0x40};/// <summary>/// CRC校验/// </summary>/// <param name="pucFrame">字节数组</param>/// <param name="usLen">验证长度</param>/// <returns>2个字节</returns>protected byte[] CalculateCRC(byte[] pucFrame, int usLen){int i = 0;byte[] res = new byte[2] { 0xFF, 0xFF };ushort iIndex;while (usLen-- > 0){iIndex = (ushort)(res[0] ^ pucFrame[i++]);res[0] = (byte)(res[1] ^ aucCRCHi[iIndex]);res[1] = aucCRCLo[iIndex];}return res;}}/// <summary>/// Modbus 功能码/// </summary>public enum ModbusFunctionCode{/// <summary>/// 读取输出线圈/// </summary>[Description("读取输出线圈")]ReadOutCoil = 1,/// <summary>/// 读取输入线圈/// </summary>[Description("读取输入线圈")]ReadInputCoil = 2,/// <summary>/// 读取保持寄存器/// </summary>[Description("读取保持寄存器")]ReadRegister = 3,/// <summary>/// 读取输入寄存器/// </summary>[Description("读取输入寄存器")]ReadInputRegister = 4,/// <summary>/// (写入)预置单线圈/// </summary>[Description("(写入)预置单线圈")]WriteCoil = 5,/// <summary>/// (写入)预置单个寄存器/// </summary>[Description("(写入)预置单个寄存器")]WriteRegister = 6,/// <summary>/// (写入)预置多寄存器/// </summary>[Description("(写入)预置多寄存器")]WriteRegisterMultiple = 16,}
}

RTU

串口基类 SerialPortBase

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CJH.ModbusTool.RTU
{//Modbus 规定4个存储区// 区号     名称     读写        范围// 0区     输出线圈  可读可写    00001-09999// 1区     输入线圈  只读        10001-19999// 2区     输入寄存器  只读      30001-39999// 4区     保存寄存器  可读可写  40001-19999//功能码//01H     读取输出线圈//02H     读取输入线圈//03H     读取保持寄存器//04H     读取输入寄存器//05H     (写入)预置单线圈//06H     (写入)预置寄存器//0FH     (写入)预置多线圈//10H     (写入)预置多寄存器/// <summary>/// 串口基类/// </summary>public abstract class SerialPortBase : ModbusBase{protected SerialPort SerialPortObj;/// <summary>/// 初始化/// </summary>/// <param name="portName">COM口名称</param>/// <param name="baudRate">波特率</param>/// <param name="parity">检验位</param>/// <param name="dataBits">数据位</param>/// <param name="stopBits">停止位</param>protected void Init(string portName, int baudRate = 9600, Parity parity = Parity.None, int dataBits = 8, StopBits stopBits = StopBits.One){SerialPortObj = new SerialPort(portName, baudRate, parity, dataBits, stopBits);if (SerialPortObj.IsOpen){SerialPortObj.Close();}SerialPortObj.Open();}        /// <summary>/// 关闭/// </summary>public void Close(){if (SerialPortObj.IsOpen){SerialPortObj.Close();SerialPortObj.Dispose();SerialPortObj = null;}}}//功能码//01H     读取输出线圈//02H     读取输入线圈//03H     读取保持寄存器//04H     读取输入寄存器//05H     (写入)预置单线圈//06H     (写入)预置寄存器//0FH     (写入)预置多线圈//10H     (写入)预置多寄存器}

Modbus 串口通讯

(串口操作的所有功能这个类都能做)

using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CJH.ModbusTool.RTU
{/// <summary>/// Modbus 串口通讯/// </summary>public class ModbusRTU : SerialPortBase{private string _className = "ModbusRTU";/// <summary>/// Modbus 串口通讯/// </summary>/// <param name="portName">COM口名称</param>/// <param name="baudRate">波特率</param>/// <param name="parity">检验位</param>/// <param name="dataBits">数据位</param>/// <param name="stopBits">停止位</param>public ModbusRTU(string portName, int baudRate = 9600, Parity parity = Parity.None, int dataBits = 8, StopBits stopBits = StopBits.One){Init(portName, baudRate, parity, dataBits, stopBits);//SerialPortObj.DataReceived += new SerialDataReceivedEventHandler(ComDataReceived);}/// <summary>/// 读取线圈数据 ok/// </summary>/// <param name="devAddr">从站地址</param>/// <param name="length">寄存器数量</param>/// <param name="functionCode">功能码</param>/// <param name="startAddr">起始寄存器地址</param>/// <returns>线圈数据</returns>public ModbusResultModel ReadData(byte devAddr, ushort length, ModbusFunctionCode functionCode = ModbusFunctionCode.ReadRegister, ushort startAddr = 0){return ReadData(devAddr, length, (byte)functionCode, startAddr);}/// <summary>/// 读取数据 ok/// </summary>/// <param name="devAddr">从站地址</param>/// <param name="length">寄存器数量</param>/// <param name="functionCode">功能码</param>/// <param name="startAddr">起始寄存器地址</param>/// <returns>线圈数据</returns>public ModbusResultModel ReadData(byte devAddr, ushort length, byte functionCode = 2, ushort startAddr = 0){var resultModel = new ModbusResultModel();//byte[] datas = null;if (functionCode >= 1 && functionCode <= 4){try{//1.拼接报文:var sendCommand = new List<byte>();//协议格式:站地址+功能码+起始寄存器地址+寄存器数量+CRC//站地址sendCommand.Add(devAddr);//功能码sendCommand.Add(functionCode);//起始寄存器地址sendCommand.Add((byte)(startAddr / 256));sendCommand.Add((byte)(startAddr % 256));//寄存器数量sendCommand.Add((byte)(length / 256));sendCommand.Add((byte)(length % 256));//CRCbyte[] crc = Crc16(sendCommand.ToArray(), sendCommand.Count);sendCommand.AddRange(crc);resultModel.SendDataStr = generateSendCommandStr(sendCommand.ToArray());//2.发送报文SerialPortObj.Write(sendCommand.ToArray(), 0, sendCommand.Count);//3.接收报文Thread.Sleep(50);//要延时一下,才能读到数据//读取响应报文byte[] respBytes = new byte[SerialPortObj.BytesToRead];SerialPortObj.Read(respBytes, 0, respBytes.Length);// respBytes -> 01 01 02 00 00 B9 FCresultModel.Datas = respBytes;// 检查一个校验位//if (CheckCRC(respBytes) && (respBytes.Length == 5 + length * 2) //    && respBytes[0] == devAdd && respBytes[1] == functionCode && respBytes[1] == length * 2)if (CheckCRC(respBytes) && respBytes[0] == devAddr && respBytes[1] == functionCode){//datas = respBytes;resultModel.IsSucceed = true;}else{resultModel.Msg = "响应报文校验失败";}}catch (Exception ex){resultModel.Msg = "异常:" + ex.Message;}}else{//throw new Exception("功能码不正确[1-4]");resultModel.Msg = "功能码不正确[1-4]";}//SerialPortObj.Close();return resultModel;}/// <summary>/// 写入单个寄存器 ok/// 数据示例: 200/// 功能码 6/// </summary>/// <param name="devAddr">从站地址</param>/// <param name="value">写入的数据</param>/// <param name="startAddr">写入地址</param>/// <returns>是否成功</returns>public ModbusResultModel WriteDataShort(int devAddr, short value, short startAddr = 0){var resultModel = new ModbusResultModel();try{//bool isOk = false;//1.拼接报文://var sendCommand = GetSingleDataWriteMessage(devAdd, startAddr, value); //okvar sendCommand = GetSingleDataWriteMessageList(devAddr, startAddr, value); //ok//var sendCommandStr = string.Join(' ', sendCommand.ToArray());resultModel.SendDataStr = generateSendCommandStr(sendCommand);//2.发送报文SerialPortObj.Write(sendCommand.ToArray(), 0, sendCommand.Length);//3.接收报文Thread.Sleep(50);//要延时一下,才能读到数据//读取响应报文byte[] respBytes = new byte[SerialPortObj.BytesToRead];SerialPortObj.Read(respBytes, 0, respBytes.Length);// respBytes -> 01 01 02 00 00 B9 FC// 检查一个校验位if (CheckCRC(respBytes) && respBytes[0] == devAddr && respBytes[1] == 0x06){//isOk = true;resultModel.IsSucceed = true;}else{resultModel.Msg = "响应报文校验失败";}}catch (Exception ex){resultModel.Msg = "异常:" + ex.Message;}//SerialPortObj.Close();return resultModel;}/// <summary>/// 写入单个寄存器 ok/// 数据示例: 200/// 功能码 6/// </summary>/// <param name="devAddr">站地址</param>/// <param name="dataList">写入的数据集合</param>/// <param name="startAddr">写入地址</param>/// <returns>是否成功</returns>public ModbusResultModel WriteDataShort(int devAddr, List<short> dataList, short startAddr = 0){var resultModel = new ModbusResultModel();if (dataList != null && dataList.Count > 0){foreach (var item in dataList){resultModel = WriteDataShort(devAddr, item, startAddr);startAddr++;}}return resultModel;}/// <summary>/// 获取写入单个寄存器的报文/// </summary>/// <param name="slaveStation">从站地址</param>/// <param name="startAddr">寄存器地址</param>/// <param name="value">写入值</param>/// <returns>写入单个寄存器的报文</returns>private byte[] GetSingleDataWriteMessage(int slaveStation, short startAddr, short value){//从站地址byte station = (byte)slaveStation;//功能码byte type = 0x06;//06H     (写入)预置寄存器//寄存器地址byte[] start = BitConverter.GetBytes(startAddr);//值byte[] valueBytes = BitConverter.GetBytes(value);//根据计算机大小端存储方式进行高低字节转换if (BitConverter.IsLittleEndian){Array.Reverse(start);Array.Reverse(valueBytes);}//拼接报文byte[] result = new byte[] { station, type };result = result.Concat(start.Concat(valueBytes).ToArray()).ToArray();//计算校验码并拼接,返回最后的报文结果return result.Concat(Crc16(result, result.Length)).ToArray();}/// <summary>/// 获取写入单个寄存器的报文/// </summary>/// <param name="slaveStation">从站地址</param>/// <param name="startAddr">寄存器地址</param>/// <param name="data">写入值</param>/// <returns>写入单个寄存器的报文</returns>private byte[] GetSingleDataWriteMessageList(int slaveStation, short startAddr, short data){//1.拼接报文:var sendCommand = new List<byte>();//从站地址byte station = (byte)slaveStation;//功能码byte type = 0x06;//06H     (写入)预置寄存器            //寄存器地址byte[] start = BitConverter.GetBytes(startAddr);//值byte[] valueBytes = BitConverter.GetBytes(data);//根据计算机大小端存储方式进行高低字节转换if (BitConverter.IsLittleEndian){Array.Reverse(start);Array.Reverse(valueBytes);}sendCommand.Add((byte)slaveStation);sendCommand.Add(type);sendCommand.AddRange(start);sendCommand.AddRange(valueBytes);byte[] crc = Crc16(sendCommand.ToArray(), sendCommand.Count);sendCommand.AddRange(crc);return sendCommand.ToArray();}/// <summary>/// 写入多个寄存器 ok/// 数据示例: 123.45f, 14.3f/// 功能码 10 /// </summary>/// <param name="devAddr">从站地址</param>/// <param name="data">写入的数据</param>/// <param name="startAddr">写入地址</param>/// <returns>是否成功</returns>public ModbusResultModel WriteDataFloat(byte devAddr, float data, ushort startAddr = 0){return WriteDataFloat(devAddr, new List<float>() { data }, startAddr);}/// <summary>/// 写入多个寄存器 ok/// 数据示例: 123.45f, 14.3f/// 功能码 10 /// </summary>/// <param name="devAdd">从站地址</param>/// <param name="dataList">写入的数据</param>/// <param name="startAddr">写入地址</param>/// <returns>是否成功</returns>public ModbusResultModel WriteDataFloat(byte devAddr, List<float> dataList, ushort startAddr = 0){var resultModel = new ModbusResultModel();if (dataList != null && dataList.Count > 0){try{byte functionCode = (byte)ModbusFunctionCode.WriteRegisterMultiple.GetHashCode();int length = dataList.Count * 2;//1.拼接报文:var sendCommand = new List<byte>();//协议格式:站地址+功能码+起始寄存器地址+寄存器数量+CRC//站地址sendCommand.Add(devAddr);//功能码sendCommand.Add(functionCode);//写入地址sendCommand.Add((byte)(startAddr / 256));sendCommand.Add((byte)(startAddr % 256));//寄存器数量sendCommand.Add((byte)(length / 256));sendCommand.Add((byte)(length % 256));// 获取数值的byte[]List<byte> valueBytes = new List<byte>();foreach (var data in dataList){List<byte> temp = new List<byte>(BitConverter.GetBytes(data));temp.Reverse();// 调整字节序valueBytes.AddRange(temp);}// 字节数sendCommand.Add((byte)valueBytes.Count);sendCommand.AddRange(valueBytes);//CRCbyte[] crc = Crc16(sendCommand.ToArray(), sendCommand.Count);sendCommand.AddRange(crc);//000004 - Rx:01 10 00 02 00 04 08 42 F6 E6 66 41 64 CC CD 83 23//000005 - Tx:01 10 00 02 00 04 60 0A//000006 - Rx:01 0A 00 02 00 04 08 42 F6 E6 66 41 64 CC CD 98 F9//000007 - Tx:01 8A 01 86 A0 //报错了//2.发送报文SerialPortObj.Write(sendCommand.ToArray(), 0, sendCommand.Count);//3.接收报文Thread.Sleep(50);//要延时一下,才能读到数据//读取响应报文byte[] respBytes = new byte[SerialPortObj.BytesToRead];SerialPortObj.Read(respBytes, 0, respBytes.Length);// respBytes -> 01 01 02 00 00 B9 FC// 检查一个校验位if (CheckCRC(respBytes) && respBytes[0] == devAddr && respBytes[1] == functionCode){resultModel.IsSucceed = true;}else{resultModel.Msg = "响应报文校验失败";}}catch (Exception ex){resultModel.Msg = "异常:" + ex.Message;}//SerialPortObj.Close();}else{resultModel.Msg = "dataLis参数不能为NULL 且 Count 要大于0";}return resultModel;}/// <summary>/// 写单个线圈输出 ok/// </summary>/// <param name="on">开关</param>/// <param name="devAddr">从站地址</param>/// <param name="startAddr">写入地址</param>/// <returns></returns>public ModbusResultModel WriteSingleOutOnOff(bool on, byte devAddr = 1, ushort startAddr = 0){var resultModel = new ModbusResultModel();try{//var isOk = false;//1.拼接报文:var sendCommand = new List<byte>();//协议格式:站地址+功能码+起始寄存器地址+寄存器数量+CRC//站地址sendCommand.Add(devAddr);//功能码byte functionCode = 0x05;sendCommand.Add(functionCode);//写入地址sendCommand.Add((byte)(startAddr / 256));sendCommand.Add((byte)(startAddr % 256));//写入数据sendCommand.Add((byte)(on ? 0xFF : 0x00));//true : 0xFF 开,false : 0x00 关sendCommand.Add(0x00);//CRCbyte[] crc = Crc16(sendCommand.ToArray(), sendCommand.Count);sendCommand.AddRange(crc);//2.发送报文SerialPortObj.Write(sendCommand.ToArray(), 0, sendCommand.Count);//isOk = true;resultModel.IsSucceed = true;}catch (Exception ex){resultModel.Msg = "异常:" + ex.Message;}return resultModel;}}
}

Modbus 串口通讯 读线圈状态

(这个类是针对线圈的 突出读取数据)

using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace CJH.ModbusTool.RTU
{/// <summary>/// Modbus 串口通讯 读线圈状态/// </summary>public class ModbusRTUCoil : ModbusRTU{//ModbusRTU rtu = new ModbusRTU(portName);//var resultModel = rtu.ReadData(1, readLen, ModbusFunctionCode.ReadOutCoil);/// <summary>/// Modbus 串口通讯/// </summary>/// <param name="portName">COM口名称</param>/// <param name="baudRate">波特率</param>/// <param name="parity">检验位</param>/// <param name="dataBits">数据位</param>/// <param name="stopBits">停止位</param>public ModbusRTUCoil(string portName, int baudRate = 9600, Parity parity = Parity.None, int dataBits = 8, StopBits stopBits = StopBits.One): base(portName, baudRate, parity, dataBits, stopBits){//Init(portName, baudRate, parity, dataBits, stopBits);//SerialPortObj.DataReceived += new SerialDataReceivedEventHandler(ComDataReceived);}/// <summary>/// 读取线圈数据 ok/// </summary>/// <param name="devAdd">从站地址</param>/// <param name="length">寄存器数量</param>/// <param name="functionCode">功能码</param>/// <param name="startAddr">起始寄存器地址</param>/// <returns>线圈数据</returns>public ModbusResultModel<bool> ReadDataCoil(byte devAdd, ushort length, ModbusFunctionCode functionCode = ModbusFunctionCode.ReadRegister, ushort startAddr = 0){var resultModel = new ModbusResultModel<bool>();var model = ReadData(devAdd, length, (byte)functionCode, startAddr);if (model != null && model.Datas != null && model.Datas.Length > 5){resultModel.IsSucceed = model.IsSucceed;//报文解析// 检查一个校验位List<byte> respList = new List<byte>(model.Datas);respList.RemoveRange(0, 3);respList.RemoveRange(respList.Count - 2, 2);// 00 00//集合反转respList.Reverse();//转换成2进制var respStrList = respList.Select(r => Convert.ToString(r, 2)).ToList();var values = string.Join("", respStrList).ToList();values.Reverse();//values.ForEach(c => Console.WriteLine(Convert.ToBoolean(int.Parse(c.ToString()))));foreach (var v in values){resultModel.ResultList.Add(v.ToString() == "1");}}return resultModel;}}
}

TCP

ModbusTCP 基类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;namespace CJH.ModbusTool.TCP
{/// <summary>/// ModbusTCP 基类/// </summary>public abstract class ModbusTCPBase : ModbusBase{private Socket _socket = null;ushort _tid = 0;//TransactionId 最大 65535/// <summary>/// 异常码 字典/// </summary>protected Dictionary<int, string> Errors = new Dictionary<int, string>() {{ 0x01 , "非法功能码"},{ 0x02 , "非法数据地址"},{ 0x03 , "非法数据值"},{ 0x04 , "从站设备故障"},{ 0x05 , "确认,从站需要一个耗时操作"},{ 0x06 , "从站忙"},{ 0x08 , "存储奇偶性差错"},{ 0x0A , "不可用网关路径"},{ 0x0B , "网关目标设备响应失败"},};/// <summary>/// Modbus TCP 通讯 初始化/// </summary>/// <param name="host">主机地址</param>/// <param name="port">端口</param>protected void Init(string host, int port){_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);_socket.Connect(host, port);}/// <summary>/// 读取报文的 公共方法/// </summary>/// <param name="devAddr">从站地址</param>/// <param name="length">寄存器数量</param>/// <param name="functionCode">功能码</param>/// <param name="startAddr">起始寄存器地址</param>/// <returns>返回报文(协议格式:TransactionId+协议标识+后续字节数+站地址+功能码+起始寄存器地址+寄存器数量)</returns>protected byte[] GenerateTcpCommandReadBytes(byte devAddr, ushort length, ModbusFunctionCode functionCode = ModbusFunctionCode.ReadRegister, ushort startAddr = 0){var baseCommand = GenerateReadCommandBytes(devAddr, length, functionCode, startAddr);var sendCommand = new List<byte>();//TransactionIdsendCommand.Add((byte)(_tid / 256));sendCommand.Add((byte)(_tid % 256));//Modbus 协议标识sendCommand.Add(0x00);sendCommand.Add(0x00);//后续字节数sendCommand.Add((byte)(baseCommand.Length / 256));sendCommand.Add((byte)(baseCommand.Length % 256));_tid++;_tid %= 65535;sendCommand.AddRange(baseCommand);return sendCommand.ToArray();}/// <summary>/// 读取报文的 公共方法/// </summary>/// <param name="devAddr">从站地址</param>/// <param name="data">输入数据</param>/// <param name="functionCode">功能码</param>/// <param name="startAddr">起始寄存器地址</param>/// <returns>返回报文(协议格式:TransactionId+协议标识+后续字节数+站地址+功能码+起始寄存器地址+寄存器数量)</returns>protected byte[] GenerateTcpCommandWriteBytes(byte devAddr, ushort data, ModbusFunctionCode functionCode = ModbusFunctionCode.WriteRegister, ushort startAddr = 0){var baseCommand = GenerateWriteCommandBytes(devAddr, data, functionCode, startAddr);var sendCommand = new List<byte>();//TransactionIdsendCommand.Add((byte)(_tid / 256));sendCommand.Add((byte)(_tid % 256));//Modbus 协议标识sendCommand.Add(0x00);sendCommand.Add(0x00);//后续字节数sendCommand.Add((byte)(baseCommand.Length / 256));sendCommand.Add((byte)(baseCommand.Length % 256));_tid++;_tid %= 65535;sendCommand.AddRange(baseCommand);return sendCommand.ToArray();}protected ModbusResultModel SendCommand(byte[] sendCommand){var resultModel = new ModbusResultModel();try{//报文//TransactionId Modbus 协议标识 后续字节数  从站地址  功能码         起始寄存器地址  寄存器数量  CRC//0x00 0x01     0x00   0x00     0x00  0x06  devAddr   functionCode   0x00  0x0A      resultModel.SendDataStr = generateSendCommandStr(sendCommand.ToArray());_socket.Send(sendCommand.ToArray());//000002-Rx:00 00 00 00 00 06 01 03 00 01 00 05//000003-Tx:00 00 00 00 00 0D 01 03 0A 00 00 00 00 00 00 00 00 00 00// 00 00 00 00 00 0D 前6位// 01 03 0A (0,1,2)// 0A 数据长度 (0A=10)//先取前6位,固定返回var resp_bytes = new byte[6];// 00 00 00 00 00 0D 前6位_socket.Receive(resp_bytes, 0, resp_bytes.Length, SocketFlags.None);//取出下标为 :4和5的数据[00 0D]var len_bytes = resp_bytes.ToList().GetRange(4, 2);//起始寄存器地址 的反向操作//将下标为4和5 两个字节转成10进制数int len = len_bytes[0] * 256 + len_bytes[1];//获取数据的长度//01 03 0A 00 00 00 00 00 00 00 00 00 00 [正常]//01 83 02 [异常,83, 异常代码 :02]resp_bytes = new byte[len];_socket.Receive(resp_bytes, 0, len, SocketFlags.None);//检查响应报文是否正常//0x83 1000 0011//01 83 02 [异常,83, 异常代码 :02]if (resp_bytes[1] > 0x08)//判断是否异常{//resp_bytes[2] = 异常代码 :02//说明响应是异常报文//返回异常信息,根据resp_bytes字节进行异常关联if (Errors.ContainsKey(resp_bytes[2])){resultModel.Msg = Errors[resp_bytes[2]];//获取异常码对应的异常说明}}else{//resp_bytes[2] = 0A 数据长度 (0A=10)//正常resultModel.Datas = resp_bytes.ToList().GetRange(3, resp_bytes[2]).ToArray();resultModel.IsSucceed = true;}}catch (Exception ex){resultModel.Msg = ex.Message;}return resultModel;}/// <summary>/// 解析数据/// </summary>/// <typeparam name="T"></typeparam>/// <param name="datas"></param>/// <returns></returns>public List<T> AnalysisDatas<T>(byte[] datas){//data_bytes 每两个字节转成一个数字, float 4个字节转成一个数字,double 8个字节转成一个数字//2  ushort short int16 uint32 float//4  int uint int32 uint32 float//8  double//16 decimalvar resultValue = new List<T>();try{var type_len = Marshal.SizeOf(typeof(T));for (int i = 0; i < datas.Length; i += type_len){var temp_bytes = datas.ToList().GetRange(i, type_len);if (BitConverter.IsLittleEndian){temp_bytes.Reverse();}//反射 方法Type bitConverter_type = typeof(BitConverter);var typeMethodList = bitConverter_type.GetMethods().ToList();//找到返回类型和传入的类型一至,且方法的参数是2个的方法var method = typeMethodList.FirstOrDefault(mi => mi.ReturnType == typeof(T)&& mi.GetParameters().Length == 2);if (method == null){throw new Exception("数据转换类型出错!");}else{//由 bitConverter_type 执行找到的 method方法,注意参数数量,上面找是的两个参数的方法var value = method.Invoke(bitConverter_type, new object[] { temp_bytes.ToArray(), 0 });resultValue.Add((T)value);}}}catch (Exception ex){}return resultValue;}}
}

Modbus TCP 通讯

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;namespace CJH.ModbusTool.TCP
{/// <summary>/// Modbus TCP 通讯/// </summary>public class ModbusTCP : ModbusTCPBase{/// <summary>/// Modbus TCP 通讯/// </summary>/// <param name="host">主机地址</param>/// <param name="port">端口</param>public ModbusTCP(string host, int port){//_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//_socket.Connect(host, port);Init(host, port);}/// <summary>/// 读取保持型寄存器 03/// </summary>/// <param name="devAddr">从站地址</param>/// <param name="count">数量</param>/// <param name="functionCode">功能码</param>/// <param name="startAddr">起始地址</param>//public ModbusResultModel ReadHoldingRegister(byte devAddr, ushort length, byte functionCode = 3, ushort startAddr = 0)//{//    var resultModel = new ModbusResultModel();//    //报文//    //TransactionId Modbus 协议标识 后续字节数  从站地址  功能码         起始寄存器地址  寄存器数量  CRC//    //0x00 0x01     0x00   0x00     0x00  0x06  devAddr   functionCode   0x00  0x0A      //    try//    {//        ushort tid = 0;//TransactionId 最大 65535//        var sendCommand = new List<byte>();//        //TransactionId//        sendCommand.Add((byte)(tid / 256));//        sendCommand.Add((byte)(tid % 256));//        //Modbus 协议标识//        sendCommand.Add(0x00);//        sendCommand.Add(0x00);//        //后续字节数//        sendCommand.Add(0x00);//        sendCommand.Add(0x06);//        //从站地址//        sendCommand.Add(devAddr);//        //功能码//        sendCommand.Add(functionCode);//        //起始寄存器地址//        sendCommand.Add((byte)(startAddr / 256));//        sendCommand.Add((byte)(startAddr % 256));//        //寄存器数量//        sendCommand.Add((byte)(length / 256));//        sendCommand.Add((byte)(length % 256));//        //CRC//        //byte[] crc = Crc16(sendCommand.ToArray(), sendCommand.Count);//        //sendCommand.AddRange(crc);//        tid++;//        tid %= 65535;//        resultModel.SendDataStr = generateSendCommandStr(sendCommand.ToArray());//        _socket.Send(sendCommand.ToArray());//        //000002-Rx:00 00 00 00 00 06 01 03 00 01 00 05//        //000003-Tx:00 00 00 00 00 0D 01 03 0A 00 00 00 00 00 00 00 00 00 00//        // 00 00 00 00 00 0D 前6位//        // 01 03 0A (0,1,2)//        // 0A 数据长度 (0A=10)//        //先取前6位,固定返回//        var resp_bytes = new byte[6];// 00 00 00 00 00 0D 前6位//        _socket.Receive(resp_bytes, 0, resp_bytes.Length, SocketFlags.None);//        //取出下标为 :4和5的数据[00 0D]//        var len_bytes = resp_bytes.ToList().GetRange(4, 2);//        //起始寄存器地址 的反向操作//        //将下标为4和5 两个字节转成10进制数//        int len = resp_bytes[4] * 256 + resp_bytes[5];//        //获取数据的长度//        //01 03 0A 00 00 00 00 00 00 00 00 00 00 [正常]//        //01 83 02 [异常,83, 异常代码 :02]//        resp_bytes = new byte[len];//        _socket.Receive(resp_bytes, 0, len, SocketFlags.None);//        //检查响应报文是否正常//        //0x83 1000 0011//        //01 83 02 [异常,83, 异常代码 :02]//        if (resp_bytes[1] > 0x08)//判断是否异常//        {//            //resp_bytes[2] = 异常代码 :02//            //说明响应是异常报文//            //返回异常信息,根据resp_bytes字节进行异常关联//            if (Errors.ContainsKey(resp_bytes[2]))//            {//                resultModel.Msg = Errors[resp_bytes[2]];//获取异常码对应的异常说明//            }//        }//        else//        {//            //resp_bytes[2] = 0A 数据长度 (0A=10)//            //正常//            resultModel.Datas = resp_bytes.ToList().GetRange(3, resp_bytes[2]).ToArray();//            resultModel.IsSucceed = true;//        }//    }//    catch (Exception ex)//    {//        resultModel.Msg = ex.Message;//    }//    return resultModel;//}/// <summary>/// 读取保持型寄存器 03/// </summary>/// <param name="devAddr">从站地址</param>/// <param name="length">数量</param>/// <param name="functionCode">功能码</param>/// <param name="startAddr">起始地址</param>/// <returns>返回对象</returns>public ModbusResultModel<T> ReadHoldingRegister<T>(byte devAddr, ushort length, ModbusFunctionCode functionCode = ModbusFunctionCode.ReadRegister, ushort startAddr = 0){var resultModel = new ModbusResultModel<T>();try{var command = GenerateTcpCommandReadBytes(devAddr, length, functionCode, startAddr);resultModel.SendDataStr = generateSendCommandStr(command.ToArray());var receptionModel = SendCommand(command.ToArray());if (receptionModel.IsSucceed&& receptionModel.Datas != null && receptionModel.Datas.Length > 0){resultModel.Datas = receptionModel.Datas;resultModel.ResultList = AnalysisDatas<T>(receptionModel.Datas);resultModel.IsSucceed = true;}}catch (Exception ex){resultModel.Msg = ex.Message;}return resultModel;}/// <summary>/// 写入保持型寄存器 03/// </summary>/// <param name="devAddr">从站地址</param>/// <param name="datas">写入数据</param>/// <param name="functionCode">功能码</param>/// <param name="startAddr">起始地址</param>/// <returns>返回对象</returns>public ModbusResultModel WriteHoldingRegister(byte devAddr, ushort data, ModbusFunctionCode functionCode = ModbusFunctionCode.WriteRegister, ushort startAddr = 0){var resultModel = new ModbusResultModel();try{var command = GenerateTcpCommandWriteBytes(devAddr, data, functionCode, startAddr);resultModel.SendDataStr = generateSendCommandStr(command.ToArray());var receptionModel = SendCommand(command.ToArray());if (receptionModel.IsSucceed){resultModel.IsSucceed = true;}}catch (Exception ex){resultModel.Msg = ex.Message;}return resultModel;}}
}

这就是全部的代码。如有使用的问题可以给我发评论。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/264427.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Leetcode3014. 输入单词需要的最少按键次数 I

题目&#xff1a; 代码(首刷看解析 2024年2月21日&#xff09;&#xff1a; class Solution { public:int minimumPushes(string word) {int n word.size();if (n < 8) return n;int mo n % 8;int x n / 8;int res 0;for (int i 1; i < x 1; i) {res i * 8;}res …

leet hot 100-6 三数之和

三数之和 原题链接思路代码 原题链接 leet hot 100-5 15. 三数之和 思路 从前往后定义第一个数字 first 开始遍历整个数组 然后要求 frist和上一个数字不重复否则就是重复组合 从frist往后遍历第二个数字 同样要求第二个数字不能重复 再定义第三个数字从后往前面数 三个数字…

iOS面试:4.多线程GCD

一、多线程基础知识 1.1 什么是进程&#xff1f; 进程是指在系统中正在运行的一个应用程序。对于电脑而已&#xff0c;你打开一个软件&#xff0c;就相当于开启了一个进程。对于手机而已&#xff0c;你打开了一个APP&#xff0c;就相当于开启了一个进程。 1.2 什么是线程&am…

http协议基础与Apache的简单介绍

一、相关介绍&#xff1a; 互联网&#xff1a;是网络的网络&#xff0c;是所有类型网络的母集因特网&#xff1a;世界上最大的互联网网络。即因特网概念从属于互联网概念。习惯上&#xff0c;大家把连接在因特网上的计算机都成为主机。万维网&#xff1a;WWW&#xff08;world…

二叉树和堆

二叉树和堆 一、树的概念和结构二、二叉树的概念三、堆四、堆的创建one、堆的插入(需要与向上或者向下调整算法配合(取决于你建大堆还是小堆)two、剔除堆中的根节点 五、堆的简单排序 一、树的概念和结构 树是一种非线性的的数据结构&#xff0c;逻辑结构就是一颗倒挂的树&…

Linux使用Docker部署Nacos容器并结合内网穿透实现公网访问本地服务

文章目录 推荐1. Docker 运行Nacos2. 本地访问Nacos3. Linux安装Cpolar4. 配置Nacos UI界面公网地址5. 远程访问 Nacos UI界面6. 固定Nacos UI界面公网地址7. 固定地址访问Plik 推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff…

精美的WordPress外贸独立站模板

WordPress外贸独立站主题 简洁实用的WordPress外贸独立站主题&#xff0c;适合时尚服装行业搭建wordpress企业官网使用。 https://www.jianzhanpress.com/?p4999 简洁wordpress独立站模板 绿色精美、简洁大气的wordpress外贸独立网站模板 https://www.jianzhanpress.com/?…

本地配置多个git账户及ll设置

本地配置多个git账户 清除全局配置将命令行&#xff0c;切换到ssh目录生成GitLab和Gitee的公钥、私钥去对应的代码仓库添加 SSH Keys添加私钥ll设置 管理密钥验证仓库配置关于gitgitee.com: Permission denied (publickey) 清除全局配置 此步骤可以不做&#xff0c;经测试不影…

微信小程序本地开发

微信小程序本地开发时不需要在小程序后台配置服务器域名直接在小程序项目中填写后端在本机的IP地址和端口号 如图&#xff08;第一步&#xff09; 填写地址后发现报错&#xff0c;url不是合法域名&#xff0c;则在详情设置不校验合法域名 如图&#xff08;第二歩&#xff09;…

AI:134-基于深度学习的社交媒体图像内容分析

🚀点击这里跳转到本专栏,可查阅专栏顶置最新的指南宝典~ 🎉🎊🎉 你的技术旅程将在这里启航! 从基础到实践,深入学习。无论你是初学者还是经验丰富的老手,对于本专栏案例和项目实践都有参考学习意义。 ✨✨✨ 每一个案例都附带有在本地跑过的关键代码,详细讲解供…

猫头虎分享已解决Bug || AttributeError: ‘Sequential‘ object has no attribute ‘session‘

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

Remainder Problem(根号分治)

Educational Codeforces Round 71 (Rated for Div. 2) F. Remainder Problem 题目链接 F. Remainder Problem 题意&#xff1a; 给你一个由 500000 500000 500000 个整数&#xff08;编号从 1 1 1 到 500000 500000 500000 &#xff09;组成的数组 a a a 。最初 a a a…

SpringBoot -【SmartInitializingSingleton】基础使用及应用场景

SmartInitializingSingleton 在继续深入探讨 SmartInitializingSingleton接口之前&#xff0c;让我们先了解一下 Spring Framework 的基本概念和背景。Spring Framework 是一个开源的 JavaEE&#xff08;Java Enterprise Edition&#xff09;全栈&#xff08;full-stack&#x…

PureFlash v1.9.1特性介绍

PureFlashv1.9.1版本特性主要有3个&#xff1a; 1. 支持RDMA网络 使用RDMA协议可以大大减少对CPU的消耗&#xff0c;性能提升30%以上。 PureFlash的网络配置分为存储节点间网络&#xff08;存储后端网&#xff09;和客户端网络&#xff08;前端网&#xff09;。都支持使用RD…

Java的编程之旅19——使用idea对面相对象编程项目的创建

在介绍面向对象编程之前先说一下我们在idea中如何创建项目文件 使用快捷键CtrlshiftaltS新建一个模块&#xff0c;点击“”&#xff0c;再点New Module 点击Next 我这里给Module起名叫OOP,就是面向对象编程的英文缩写&#xff0c;再点击下面的Finish 点Apply或OK均可 右键src…

网络设备和网络软件

文章目录 网络设备和网络软件网卡交换机交换机的三个主要功能交换机的工作原理第二层交换和第三层交换交换机的堆叠和级联 路由器路由器工作原理 网关网关的分类 无线接入点(AP)调制解调器网络软件 网络设备和网络软件 网卡 网络接口卡又称网络适配器&#xff0c;简称网卡。网…

MySQL数据库基础(十五):PyMySQL使用介绍

文章目录 PyMySQL使用介绍 一、为什么要学习PyMySQL 二、安装PyMySQL模块 三、PyMySQL的使用 1、导入 pymysql 包 2、创建连接对象 3、获取游标对象 4、pymysql完成数据的查询操作 5、pymysql完成对数据的增删改 PyMySQL使用介绍 提前安装MySQL数据库&#xff08;可以…

服务器防漏扫

什么是漏扫&#xff1f; 漏扫是漏洞扫描的简称。漏洞扫描是一种安全测试方法&#xff0c;用于发现计算机系统、网络或应用程序中的潜在漏洞和安全弱点。通过使用自动化工具或软件&#xff0c;漏洞扫描可以检测系统中存在的已知漏洞&#xff0c;并提供相关的报告和建议&#xf…

记阿里云mysql丢表丢数据的实践记录

第一时间挂工单&#xff0c;联系工程师指引&#xff0c;现在回过来想&#xff0c;第一时间要确认发生时间。 1.通过性能视图&#xff08;马后炮的总结&#xff0c;实际凭记忆恢复了三四次才找到数据&#xff09; 2.先恢复数据 通过Navicat工具&#xff0c;结构同步&#xff0…

【Docker】03 容器操作

文章目录 一、流转图二、基本操作2.1 查看本地容器进程2.2 启动容器2.2.1 交互式启动容器2.2.2 后台启动容器 2.3 进入容器2.4 停止启动重启容器2.5 退出容器2.6 删除容器2.7 提交容器&#xff08;打包成镜像&#xff09;2.8 拷贝文件2.8.1 拷贝容器内文件到宿主机2.8.2 拷贝宿…