实例介绍
【实例简介】用于modbusTcp测试或dll,调用
【实例截图】
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Timers;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;//序列化和反序列化
namespace Modbus
{
/// <summary>
/// ModbusTcp主站
/// </summary>
// modbustcp与串口类似,tcp端口是502,没有CRC校验,增加一个报文头固定码00 00 00 00,数据长度:高字节 低字节
//(数据位:1-2) 定义我是谁(Transaction Identifier),协议给了我2byte空间定义我是谁,我代号就是00 00
//(数据位:3-4)定义协议号,协议给了我2byte空间定义协议号(协议号就是00 00,表示这是MODBUS 协议)
[Serializable]//对象可序列化标识
public class ModbusTcpMaster
{
/// <summary>
/// Modbus服务器IP地址
/// </summary>
public string IP;
/// <summary>
///Modbus服务器端口号502
/// </summary>
public int Port;
/// <summary>
/// Modbus从站地址
/// </summary>
public byte Addr;
/// <summary>
/// 4.X只读寄存器地址0-65535
/// </summary>
public Int16[] ModbusD = new Int16[65535];
private Int16[] ModbusDWriteR = new Int16[65535];//记忆寄存器,用于判断数据是否发生变化
/// <summary>
/// 采集频率
/// </summary>
public int ReadCycle;
/// <summary>
/// 只读开始地址
/// </summary>
public int ReadStartAddr;
/// <summary>
/// 只读寄存器数量
/// </summary>
public int ReadCount;
/// <summary>
/// 只写起始地址
/// </summary>
public int WriteStartAddr;
/// <summary>
/// 只写结束地址
/// </summary>
public int WriteEndAddr;
/// <summary>
/// 一次读写最大寄存器个数
/// </summary>
public int MaxReadWriteCount;
/// <summary>
/// 通讯错误
/// </summary>
public bool ComError;
/// <summary>
/// 是否记录通讯信息
/// </summary>
public bool IsComLog;
private FileOpration ObjFileOpration = new FileOpration();
private string ComLogFileName;
public int SendCount = 0;//发送计数
public int ReceiveCount = 0;//接收计数
public Socket SocketClient;//Socket客户端
public Thread ModbusThread;//定义一个线程
public Thread ModbusReceiveThread;//定义一个接收数据线程
public System.Timers.Timer SampleTimer;//定义一个采集定时器
//构造方法
public ModbusTcpMaster()
{
Port = 502;
if (ReadCycle < 100) ReadCycle = 100;//最小采集周期100ms
if (ReadStartAddr < 0) ReadStartAddr = 0;//起始地址从0开始
if (ReadCount == 0) ReadCount = 1;//最小读取一个数据
if (MaxReadWriteCount < 10) MaxReadWriteCount = 10;
if (MaxReadWriteCount > 100) MaxReadWriteCount = 100;
ComLogFileName = "ComLog.txt";
}
private List<int> WriteRegisterList = new List<int>();
List<byte[]> SendDataList = new List<byte[]>();
int ErrorCount = 0;//通读出错计数
bool SendLable = false;//发送标志
int SendCountR = 0;
bool ReceiveLable = false;//接收标志
private void ModbusThreadMethod()//线程方法,处理发送接收逻辑
{
while (true)
{
Thread.Sleep(1);
}
}
private void GetSendDataList()
{
#region 生成只写寄存器集合
int writeRegisterCount = WriteEndAddr - WriteStartAddr;
if (writeRegisterCount > 0 && WriteStartAddr > (ReadStartAddr ReadCount))
{
for (int i = 0; i < writeRegisterCount; i )
{
if (ModbusD[WriteStartAddr i] != ModbusDWriteR[WriteStartAddr i])//只写数据发生了变化
{
if (WriteRegisterList.Contains(WriteStartAddr i) == false) WriteRegisterList.Add(WriteStartAddr i);//把地址添加到集合
ModbusDWriteR[WriteStartAddr i] = ModbusD[WriteStartAddr i];
}
}
}
#endregion
byte cmd;
#region 写指令
if (WriteRegisterList.Count >= 1 && ReadStartAddr >= 0 && WriteStartAddr > (ReadStartAddr ReadCount) && ReadCycle >= 100)
{
//通过需要写入的寄存器集合,得到指令所需的起始地址和数量
List<StartAddrCount> writeList = GetWriteStartAddrCount(WriteRegisterList);
for (int i = 0; i < writeList.Count; i )
{
byte[] sendData = WriteRigsterCmd16TCP(ModbusD, writeList[i].StartAddr, writeList[i].RegisterCount, out cmd);
//指令一致,起始地址一致时,说明指令已有
if (SendDataList.Count > 0)
{
if (SendDataList.Exists(t => (t[7] == 0x10) && ((t[8] * 255 t[9]) == writeList[i].StartAddr)) == false)
SendDataList.Add(sendData);
}
else
{
SendDataList.Add(sendData);
}
}
}
#endregion
#region 读指令
if (ReadStartAddr >= 0 && WriteStartAddr > (ReadStartAddr ReadCount) && ReadCycle >= 100)
{
//如果读取的数据个数大于50个则分多次读取
int a = ReadCount / MaxReadWriteCount;//整除
int b = ReadCount % MaxReadWriteCount;//余数
for (int i = 0; i < a; i )
{
byte[] sendData = ReadRegisterCmd03TCP(ReadStartAddr MaxReadWriteCount * i, MaxReadWriteCount, out cmd);
//指令一致,起始地址一致时,说明指令已有
if (SendDataList.Count > 0)
{
if (SendDataList.Exists(t => (t[7] == 0x03) && ((t[8] * 255 t[9]) == (ReadStartAddr MaxReadWriteCount * i))) == false)
SendDataList.Add(sendData);
}
else
{
SendDataList.Add(sendData);
}
}
if (b != 0)
{
byte[] sendData = ReadRegisterCmd03TCP(ReadStartAddr MaxReadWriteCount * a, b, out cmd);
//指令一致,起始地址一致时,说明指令已有
if (SendDataList.Count > 0)
{
if (SendDataList.Exists(t => (t[7] == 0x03) && ((t[8] * 255 t[9]) == (ReadStartAddr MaxReadWriteCount * a))) == false)
SendDataList.Add(sendData);
}
else
{
SendDataList.Add(sendData);
}
}
}
#endregion
}
private void SampleTimer_Elapsed(object sender, ElapsedEventArgs e)//定时器方法,只发送数据
{
#region 发送指令和接收数据处理
SocketClient = AutoConnectTCPServer(SocketClient, IP, Port);//自动连接socket
GetSendDataList();//生成发送指令队列
if (SocketClient == null || (SocketClient != null && SocketClient.Connected == false)) ComError = true;
if (SocketClient != null && SocketClient.Connected == true && SendDataList.Count>0)//服务器连接正常
{
try
{
if (SendLable == true && ReceiveLable == true)//发送完成,等待接收
{
//接收到数据后解析
if (SendCmdReturnValueTCP(receiveData, receiveRealLen, SendDataList[0]) == 1)
{
if (SendDataList.Count > 0)
{
SendDataList.Remove(SendDataList[0]);
}
SendCountR = SendCount;
ErrorCount = 0;
ComError = false;
}
SendLable = false;
ReceiveLable = false;
}
#region Socket发送接收
if (SendCount != SendCountR)
{
ErrorCount ;//如果通讯错误重发
SendCountR = SendCount;
}
if (ErrorCount >= 5)//超过5次错误判断为通讯中断
{
if(SendDataList.Count>0)
SendDataList.Remove(SendDataList[0]);
ComError = true;
ErrorCount = 0;
}
if (SendDataList.Count > 0)
{
SendCount ;//发送计数
if (IsComLog == true)
{
string LogStr = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") " " SocketClient.LocalEndPoint.ToString() " 发送"
" 次数:" SendCount.ToString() "通讯错误:" ErrorCount.ToString()
"队列总数:" SendDataList.Count.ToString() " " BitConverter.ToString(SendDataList[0]).Replace("-", " ");
ObjFileOpration.WriteLine(ComLogFileName, LogStr);//通讯记录
}
SocketClient.Send(SendDataList[0], SendDataList[0].Length, 0); //向服务器发送指令
SendLable = true;
}
#endregion
}
catch
{
}
}
#endregion
}
byte[] receiveData = new byte[1024];
int receiveRealLen = -1;
//独立线程只接收数据
private void ReceiveServer()
{
while (true)
{
if (SocketClient != null && SocketClient.Connected == true)
{
try
{
receiveRealLen = SocketClient.Receive(receiveData, receiveData.Length, 0); //同步从服务器接受消息
ReceiveCount ;
ReceiveLable = true;
if (IsComLog == true && receiveRealLen > 0)
{
string LogStr = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") " " SocketClient.RemoteEndPoint.ToString() " 接收"
" 次数:" ReceiveCount.ToString() " " BitConverter.ToString(receiveData, 0, receiveRealLen).Replace("-", " ");
ObjFileOpration.WriteLine(ComLogFileName, LogStr);//通讯记录
}
if (receiveRealLen == 0)//接收到空消息,对方退出了
{
ObjFileOpration.WriteLine(ComLogFileName, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") SocketClient.RemoteEndPoint.ToString() "退出");
SocketClient.Shutdown(SocketShutdown.Both);//关闭接收和发送
SocketClient.Close();//关闭Socket
return;//退出循环
}
}
catch
{
}
}
Thread.Sleep(1);
}
}
/// <summary>
/// 读取寄存器指令03
/// </summary>
/// <param name="startRegister">起始地址</param>
/// <param name="count">寄存器数量,最多255</param>
/// <param name="cmd">输出指令</param>
/// <returns></returns>
public byte[] ReadRegisterCmd03TCP(int startRegister, int count, out byte cmd)
{
byte[] sendBuffer = new byte[12];
sendBuffer[0] = 0x00;//报文头固定码
sendBuffer[1] = 0x00;//报文头固定码
sendBuffer[2] = 0x00;//报文头固定码
sendBuffer[3] = 0x00;//报文头固定码
sendBuffer[4] = 0x00;//数据长度高字节
sendBuffer[5] = 0x06;//数据长度低字节
sendBuffer[6] = Addr;//地址
sendBuffer[7] = 0x03;//指令
sendBuffer[8] = (byte)(startRegister >> 8);//右移8位后强制转换为byte类型得到高8位
sendBuffer[9] = (byte)startRegister;//直接转换为byte型去掉高8位得到低8位
sendBuffer[10] = (byte)(count >> 8);//右移8位后强制转换为byte类型得到高8位
sendBuffer[11] = (byte)count;//直接转换为byte型去掉高8位得到低8位
cmd = sendBuffer[7];
return sendBuffer;
}
/// <summary>
/// 写单个寄存器指令06
/// </summary>
/// <param name="registerAddr">寄存器地址</param>
/// <param name="cmd"></param>
/// <returns></returns>
public byte[] WriteRegisterCmd06TCP(Int16[] registerD,int registerAddr, out byte cmd)
{
byte[] sendBuffer = new byte[12];
sendBuffer[0] = 0x00;//报文头固定码
sendBuffer[1] = 0x00;//报文头固定码
sendBuffer[2] = 0x00;//报文头固定码
sendBuffer[3] = 0x00;//报文头固定码
sendBuffer[4] = 0x00;//数据长度高字节
sendBuffer[5] = 0x06;//数据长度低字节
sendBuffer[6] = Addr;//地址
sendBuffer[7] = 0x06;//指令
sendBuffer[8] = (byte)(registerAddr >> 8);//右移8位后强制转换为byte类型得到寄存器地址高8位
sendBuffer[9] = (byte)registerAddr;//直接转换为byte型去掉高8位得到寄存器地址低8位
sendBuffer[10] = (byte)(registerD[registerAddr] >> 8);//右移8位后强制转换为byte类型得到寄存器值高8位
sendBuffer[11] = (byte)registerD[registerAddr];//直接转换为byte型去掉高8位得到寄存器值低8位
cmd = sendBuffer[7];
return sendBuffer;
}
/// <summary>
/// 写多个寄存器指令10
/// </summary>
/// <param name="startRegister"></param>
/// <param name="count"></param>
/// <param name="cmd"></param>
/// <returns></returns>
public byte[] WriteRigsterCmd16TCP(Int16[] registerD, int startRegister, int count, out byte cmd)
{
List<byte> sendBuffer = new List<byte>();
sendBuffer.Add(0x00);//0报文头固定码
sendBuffer.Add(0x00);//1报文头固定码
sendBuffer.Add(0x00);//2报文头固定码
sendBuffer.Add(0x00);//3报文头固定码
sendBuffer.Add((byte)((2 * count 7) >> 8));//4数据长度高字节
sendBuffer.Add((byte)(2 * count 7));//5数据长度低字节
sendBuffer.Add(Addr);//6地址
sendBuffer.Add(0x10);//7指令
sendBuffer.Add((byte)(startRegister >> 8));//8右移8位后强制转换为byte类型得到,寄存器起始地址高8位
sendBuffer.Add((byte)startRegister);//9直接转换为byte型去掉高8位得到,寄存器起始地址低8位
sendBuffer.Add((byte)(count >> 8));//10右移8位后强制转换为byte类型得到,寄存器数量高8位
sendBuffer.Add((byte)count);//11直接转换为byte型去掉高8位得到低8位
sendBuffer.Add((byte)(2 * count));//12直接转换为byte型去掉高8位得到,寄存器数量低8位
for (int i = 0; i < count; i )
{
sendBuffer.Add((byte)(registerD[startRegister i] >> 8));//右移8位后强制转换为byte类型得到高8位
sendBuffer.Add((byte)registerD[startRegister i]);//直接转换为byte型去掉高8位得到低8位
}
cmd = sendBuffer[7];
byte[] sendBF = sendBuffer.ToArray();//把集合复制到数组中
return sendBF;
}
/// <summary>
/// TCP发送指令后返回值处理程序
/// </summary>
/// <param name="receiveBuffer"></param>
/// <param name="dataLength"></param>
/// <param name="sendBuffer"></param>
/// <returns></returns>
public int SendCmdReturnValueTCP(byte[] receiveBuffer, int receiveDataLength, byte[] sendBuffer)
{
byte receiveCorrect = 0;
if (Addr >= 0 && receiveDataLength >= 11) //接收数据够长
{
int byteTatolCount = 9 2 * (sendBuffer[10] * 256 sendBuffer[11]);//总接收字节数
int startRegister = sendBuffer[8] * 256 sendBuffer[9];//起址寄存器,两个高低字节合并为一个字
int registerCount = sendBuffer[10] * 256 sendBuffer[11];//寄存器数量
#region 地址指令、接收字节正确,处理03读指令返回值
if (receiveBuffer[6] == Addr && receiveBuffer[7] == 0x03 && receiveDataLength == byteTatolCount)
{
if ((receiveBuffer[8] == registerCount * 2))//接收寄存器一致,分配数值
{
for (int i = 0; i < registerCount; i )
{
ModbusD[i startRegister] = (Int16)(receiveBuffer[2 * i 9] * 256 receiveBuffer[2 * i 10]);
}
receiveCorrect = 1;//接收正常
}
else receiveCorrect = 0;
}
#endregion
#region 地址正确,处理06写单一寄存器指令返回值
if (receiveBuffer[6] == Addr && receiveBuffer[7] == 0x06 && receiveDataLength == 12)
{
int j = 0;
for (int i = 0; i < 12; i )
{
if (receiveBuffer[i] == sendBuffer[i])
{
j ;
}
}
if (j == 12) receiveCorrect = 1;//判断接收数据和发送数据完全一致
else receiveCorrect = 0;
}
#endregion
#region 地址正确,16处理写多个寄存器指令返回值
if (receiveBuffer[6] == Addr && receiveBuffer[7] == 0x10 && receiveDataLength == 12)
{
int startRegisterR = receiveBuffer[8] * 256 receiveBuffer[9];//接收数据,起址寄存器,两个高低字节合并为一个字
int registerCountR = receiveBuffer[10] * 256 receiveBuffer[11];//接收数据,寄存器数量
if (startRegister == startRegisterR && registerCount == registerCountR)//接收寄存器,起始地址和寄存器数据
{
receiveCorrect = 1;//接收正常
}
else receiveCorrect = 0;
}
#endregion
}
return receiveCorrect;
}
/// <summary>
/// socket自动重连
/// </summary>
/// <param name="_SocketClient"></param>
/// <param name="strIP"></param>
/// <param name="intPort"></param>
/// <returns></returns>
private Socket AutoConnectTCPServer(Socket _SocketClient, string strIP, int intPort)
{
if (_SocketClient == null)
{
Thread.Sleep(100);
_SocketClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
int SocketTimeOut = 100;
_SocketClient.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, SocketTimeOut);//设置这个socket的超时时间
_SocketClient.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, SocketTimeOut);//设置这个socket的超时时间
try
{
_SocketClient.Connect(new IPEndPoint(IPAddress.Parse(strIP), intPort)); //配置服务器IP与端口
}
catch
{
}
return _SocketClient;
}
//断开自动重新创建连接
if (_SocketClient != null && !_SocketClient.Connected)
{
_SocketClient.Close();
//Thread.Sleep(1000);
_SocketClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
int SocketTimeOut = 100;
_SocketClient.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, SocketTimeOut);//设置这个socket的超时时间
_SocketClient.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, SocketTimeOut);//设置这个socket的超时时间
try
{
_SocketClient.Connect(new IPEndPoint(IPAddress.Parse(strIP), intPort)); //配置服务器IP与端口
}
catch
{
}
return _SocketClient;
}
return _SocketClient;
}
/// <summary>
/// 连接设备
/// </summary>
public void Connect()
{
SocketClient = AutoConnectTCPServer(SocketClient, IP, Port);//自动连接socket
//启动线程
ModbusThread = new Thread(ModbusThreadMethod);
ModbusThread.IsBackground = true;
//ModbusThread.Start();
//启动定时器
SampleTimer = new System.Timers.Timer(ReadCycle);
SampleTimer.Elapsed = SampleTimer_Elapsed;
SampleTimer.AutoReset = true;
SampleTimer.Start();
//启动数据接收线程
ModbusReceiveThread = new Thread(ReceiveServer);
ModbusReceiveThread.IsBackground = true;
ModbusReceiveThread.Start();
ComError = false;
//通讯记录
ObjFileOpration.WriteLine(ComLogFileName, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") " 开始通讯 " IP);
}
/// <summary>
/// 断开设备
/// </summary>
public void Disconnect()
{
if (ModbusThread != null) ModbusThread.Abort();//终止线程
if (ModbusReceiveThread != null) ModbusReceiveThread.Abort();
if (SampleTimer != null)
{
SampleTimer.Stop();
SampleTimer.Close();
}
ComError = true;
if (SocketClient != null)
{
SocketClient.Close();//关闭连接
}
//通讯记录
ObjFileOpration.WriteLine(ComLogFileName, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") " 断开通讯 " IP);
}
/// <summary>
/// 获取寄存器开始地址和数量集合
/// </summary>
/// <param name="writeRegisterList"></param>
/// <returns></returns>
public List<StartAddrCount> GetWriteStartAddrCount(List<int> writeRegisterList)
{
writeRegisterList.Sort((x, y) => x.CompareTo(y));//集合排序,升序
//writeRegisterList.Sort((x, y) => -x.CompareTo(y));//集合排序,降序
List<StartAddrCount> writeList = new List<StartAddrCount>();
int startAddr = writeRegisterList[0];
int registerCount = 1;
for (int i = 0; i < writeRegisterList.Count; i )
{
if (writeRegisterList.Count == 1)//只有一个寄存器时
{
StartAddrCount objSAC = new StartAddrCount();
objSAC.RegisterCount = registerCount;
objSAC.StartAddr = startAddr;
writeList.Add(objSAC);
break;//跳出当前循环
//continue;//跳出本次循环,进入下一循环
}
else//有多个寄存器时
{
if (i == 0)
{
registerCount = 1;
startAddr = writeRegisterList[i];
}
if (i > 0)
{
if (writeRegisterList[i] - writeRegisterList[i - 1] == 1)//判断寄存器是否连续
{
registerCount ;
}
else
{
//不连续增加新的集合
StartAddrCount objSAC = new StartAddrCount();
objSAC.StartAddr = startAddr;
objSAC.RegisterCount = registerCount;
writeList.Add(objSAC);
registerCount = 1;
startAddr = writeRegisterList[i];
}
}
if (i == (writeRegisterList.Count - 1))//到最后一个寄存器
{
StartAddrCount objSAC = new StartAddrCount();
objSAC.StartAddr = startAddr;
objSAC.RegisterCount = registerCount;
writeList.Add(objSAC);
}
}
}
return writeList;
}
public void SaveModbusD()
{
try
{
//1.创建文件流
FileStream fs = new FileStream("ModbusRegister.obj", FileMode.Create);
//2.创建二进制格式化器
BinaryFormatter formatter = new BinaryFormatter();
//3.调用序列化方法
formatter.Serialize(fs, ModbusD);
//4.关闭文件流
fs.Close();
}
catch
{
}
}
public void GetModbusD()
{
try
{
//1.创建文件流
FileStream fs = new FileStream("ModbusRegister.obj", FileMode.Open);
//2.创建二进制格式化器
BinaryFormatter formatter = new BinaryFormatter();
//3.调用反序列化方法
ModbusD = (Int16[])formatter.Deserialize(fs);
//4.关闭文件流
fs.Close();
}
catch
{
}
}
}
}
好例子网口号:伸出你的我的手 — 分享!
相关软件
小贴士
感谢您为本站写下的评论,您的评论对其它用户来说具有重要的参考价值,所以请认真填写。
- 类似“顶”、“沙发”之类没有营养的文字,对勤劳贡献的楼主来说是令人沮丧的反馈信息。
- 相信您也不想看到一排文字/表情墙,所以请不要反馈意义不大的重复字符,也请尽量不要纯表情的回复。
- 提问之前请再仔细看一遍楼主的说明,或许是您遗漏了。
- 请勿到处挖坑绊人、招贴广告。既占空间让人厌烦,又没人会搭理,于人于己都无利。
关于好例子网
本站旨在为广大IT学习爱好者提供一个非营利性互相学习交流分享平台。本站所有资源都可以被免费获取学习研究。本站资源来自网友分享,对搜索内容的合法性不具有预见性、识别性、控制性,仅供学习研究,请务必在下载后24小时内给予删除,不得用于其他任何用途,否则后果自负。基于互联网的特殊性,平台无法对用户传输的作品、信息、内容的权属或合法性、安全性、合规性、真实性、科学性、完整权、有效性等进行实质审查;无论平台是否已进行审查,用户均应自行承担因其传输的作品、信息、内容而可能或已经产生的侵权或权属纠纷等法律责任。本站所有资源不代表本站的观点或立场,基于网友分享,根据中国法律《信息网络传播权保护条例》第二十二与二十三条之规定,若资源存在侵权或相关问题请联系本站客服人员,点此联系我们。关于更多版权及免责申明参见 版权及免责申明
网友评论
我要评论