今天跟大家分享一个基于C#的ModbusTcp客户端的创建,本人小白一枚,第一次发表博客,有诸多不足之处,还请谅解,也希望大佬可以指点,如何可以做得更好。
先展示一下成品效果吧。
Demo看起来就跟上图一样,这里ui使用了sunnyui的一些控件,以及运用了单例模式,扁平风风格,自动读取数据等功能。
上代码之前先简单介绍一下ModbusTcp,ModbusTcp是在ModbusRTU的基础上做了相对应的变更,去掉了ModbusRTU的从站地址以及CRC校验码,在数据头增加了MBAP报文头,整体ModbusTCP数据格式由 MBAP+PDU组成,如下图所示:
关于MBAP,具体如下:
- 事务处理标识符:可以自由定义,服务器端将返回相同内容,默认为 00 01
- 协议标识符: modbus协议规定,必须为00 00
- 长度: 数据内容由单元标识符+PDU,两者共同长度之和,除了功能码15、16之外都为00 06,只有功能码15、16时这个数据为不定。
- 单元标识符:可以理解为替代了ModbusRTU中从站地址,默认为01
Modbus功能码分为:01、02、03、04、05、06、15、16,其中01、02、03、04为读取功能码;05、06、15、16为写入功能码。
01功能码:读取单个输出线圈。
02功能码:读取单个输入Bool。
03功能码:读取保持性寄存器。
04功能码:读取输入寄存器。
05功能码:写入单个线圈。
06功能码:写入单个保持性寄存器。
15功能码:写入多个线圈。
16功能码:写入多个保持性寄存器。
Modbus功能码已经讲完了,接下来开始上代码啦~~~~~
由于代码内容较多,这里只展示五个部分的内容:
- 窗体淡出效果
- 建立连接&断开连接&防断线
- 客户端发送报文
- 读取部分
- 发送部分
第一部分:窗体淡出效果
这里代码产生的效果是达到扁平风风格,当我们关闭客户端时,客户端应用程序将会渐渐淡化直到退出,具体代码如下:
#region 窗体淡出效果
/// <summary>
/// 定时运行
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnClose_Click(object sender, EventArgs e)
{
CloseTimer.Start();
}
private void CloseTimer_Tick(object sender, EventArgs e)
{
//改变当前窗体透明度
if (this.Opacity > 0.025)
{
this.Opacity -= 0.025;
}
else
{
CloseTimer.Stop();
this.Close();
}
}
#endregion
第二部分:建立连接&断开连接&防断线
这部分内容是与ModbusTcp服务器端建立连接,以及断开连接,并且在建立了连接之后保持连接,避免了连接超时自动断开,这里采用的保持连接是发送无效报文,保持活跃,具体代码如下:
#region 建立连接&断开连接&防断线
/// <summary>
/// 建立连接
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnConnect_Click(object sender, EventArgs e)
{
try
{
//new socket采用IPv4,tcp流模式
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//这里的IP地址以及端口由UI前端输入
socket.Connect(System.Net.IPAddress.Parse(TbxIpAddress.Text), Convert.ToInt32(TbxPort.Text));
//判断是否已连接
if (socket.Connected)
{
MessageBox.Show("已连接服务端");
timer.Start();
connected = true;
}
BtnConnect.Enabled = false;
BtnDisconnect.Enabled = true;
}
catch
{
MessageBox.Show("连接失败,请检查ip地址以及端口号是否填写正确\r\n \t或者服务端是否正常开启");
}
}
/// <summary>
/// 断开连接
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnDisconnect_Click(object sender, EventArgs e)
{
//禁用收发,确保close前,已全部接收或发送
timer.Stop();
socket.Shutdown(SocketShutdown.Both);
socket.Disconnect(true);
socket.Close();
if (!socket.Connected)
{
MessageBox.Show("已断开服务端");
connected = false;
}
BtnConnect.Enabled = true;
BtnDisconnect.Enabled = false;
}
/// <summary>
/// 定时发送给服务器端,避免断线
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void timer_Tick(object sender, EventArgs e)
{
if (connected)
{
byte[] buffer;
//发送无效报文,保持连接活跃,避免断线
buffer = SendMessage(8, 0, 1);
socket.Send(buffer);
byte[] receiveBuffer = new byte[1024];
socket.Receive(receiveBuffer);
}
}
#endregion
第三部分:客户端发送报文
无论读取数据还是写入数据,都由客户端主动发送报文,为了避免代码冗余,这里将客户端发送报文封装成了一个方法,并且采用了方法重载,使SendMessage()方法具备两种状态,来可以应对上面所有的功能码,代码如下:
#region 客户端发送报文
/// <summary>
/// 客户端发送报文
/// </summary>
/// <param name="function"></param>
/// <param name="startAddress"></param>
/// <param name="count"></param>
/// <returns></returns>
byte[] SendMessage(byte function, ushort startAddress, ushort count)
{
/* modbus tcp报文 --- 报文高位在前 --- C#为小端字节序
* MBAP:
* 1.事务元标识符: 占两个字节 客户端发送 这里设置为 0x00 0x01
* 2.协议标识符: 占两个字节 modbus 规定为 0x00 0x00
* 3.长度: 从单元标识符开始往后计数 占两个字节 客户端发送 规定为 0x00 0x06
* 4.单元标识符: 替代RTU的从站地址, 占一个字节 默认为0x01
*
* 其余报文:
* 1.功能码: 一个字节
* 01:读线圈 读线圈状态 位操作
02:读离散量输入状态 位操作
03:读保持寄存器(每个寄存器含有两个字节) 字操作
04:读输入寄存器 字操作
05:写单个线圈
06:写单个寄存器
15:用于写多个线圈
16:写多个寄存器
* 2.操作数据起始地址: 两个字节
* 3.操作数据数量: 两个字节
*/
byte[] buffer = new byte[12];
buffer[0] = 0x00;
buffer[1] = 0x01;
buffer[2] = 0x00;
buffer[3] = 0x00;
buffer[4] = 0x00;
buffer[5] = 0x06;
buffer[6] = 0x01;
buffer[7] = function;
//起始地址
buffer[8] = BitConverter.GetBytes(startAddress)[1];
buffer[9] = BitConverter.GetBytes(startAddress)[0];
//操作个数
buffer[10] = BitConverter.GetBytes(count)[1];
buffer[11] = BitConverter.GetBytes(count)[0];
return buffer;
}
/// <summary>
/// 写入多个coils or register时使用的方法重载
/// </summary>
/// <param name="function"></param>
/// <param name="startAddress"></param>
/// <param name="count"></param>
/// <param name="byteCount"></param>
/// <param name="values"></param>
/// <returns></returns>
byte[] SendMessage(byte function, ushort startAddress, ushort count, byte byteCount, byte[] values)
{
/* modbus tcp报文 --- 报文高位在前 --- C#为小端字节序
* MBAP:
* 1.事务元标识符: 占两个字节 客户端发送 这里设置为 0x00 0x01
* 2.协议标识符: 占两个字节 modbus 规定为 0x00 0x00
* 3.长度: 从单元标识符开始往后计数 占两个字节 客户端发送 规定为 0x00 0x06
* 4.单元标识符: 替代RTU的从站地址, 占一个字节 默认为0x01
*
* 其余报文:
* 1.功能码: 一个字节
* 01:读线圈 读线圈状态 位操作
02:读离散量输入状态 位操作
03:读保持寄存器(每个寄存器含有两个字节) 字操作
04:读输入寄存器 字操作
05:写单个线圈
06:写单个寄存器
15:用于写多个线圈
16:写多个寄存器
* 2.操作数据起始地址: 两个字节
* 3.操作数据数量: 两个字节
*/
byte[] buffer = new byte[13 + values.Length];
buffer[0] = 0x00;
buffer[1] = 0x01;
buffer[2] = 0x00;
buffer[3] = 0x00;
buffer[4] = 0x00;
buffer[5] = Convert.ToByte(7 + values.Length);
buffer[6] = 0x01;
buffer[7] = function;
//起始地址
buffer[8] = BitConverter.GetBytes(startAddress)[1];
buffer[9] = BitConverter.GetBytes(startAddress)[0];
//操作个数
buffer[10] = BitConverter.GetBytes(count)[1];
buffer[11] = BitConverter.GetBytes(count)[0];
//字节数量
buffer[12] = byteCount;
//字节数值
for (int i = 0; i < values.Length; i++)
{
buffer[13 + i] = values[i];
}
return buffer;
}
#endregion
第四部分:读取部分
读取部分根据功能码,分成了四个部分,分别对应功能码01、功能码02、功能码03、功能码04。先附上调用代码,具体代码如下:
/// <summary>
/// 读取server数据
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnReadData_Click(object sender, EventArgs e)
{
//因为采用了自动读取,所以这里发布了一个任务执行下面代码
Task.Run(new Action(() =>
{
try
{
continueRead = !continueRead;
if (continueRead)
{
while (continueRead)
{
//因为对前端ui画面做了更改,所以这里使用this.invoke回归主线程更改Ui控件画面
this.Invoke(new Action(() =>
{
TbxMessage.Clear();
BtnReadData.Style = Sunny.UI.UIStyle.LayuiRed;
BtnReadData.Text = "StopReadData";
switch (Convert.ToInt16(TbxFunction.Text))
{
case 1:
ReadCoil(); //读取单个输出线圈
break;
case 2:
ReadInputBit(); //读取输入Bool
break;
case 3:
ReadRegister(); //读取保持性寄存器
break;
case 4:
ReadInputRegister(); //读取输入寄存器
break;
default:
MessageBox.Show("请检查功能码输入是否正确");
break;
}
}));
System.Threading.Thread.Sleep(1000); //1s读取一次
}
}
else
{
//回归主线程对UI控件进行变更
this.Invoke(new Action(() =>
{
BtnReadData.Text = "StartReadData";
BtnReadData.Style = Sunny.UI.UIStyle.Blue;
}));
}
}
catch
{
if (socket.Connected)
{
MessageBox.Show("请正确输入功能码以及相关参数");
}
else
{
MessageBox.Show("请先建立连接");
}
}
}));
}
看完了调用代码部分,下面为具体读取功能码代码~~~
01功能码代码如下:
/// <summary>
/// 读输出线圈 功能码01
/// </summary>
void ReadCoil()
{
//声明buffer数值,用于存储发送字节
byte[] buffer;
//调用SendMessage方法,返回发送字节
buffer = SendMessage(Convert.ToByte(TbxFunction.Text), Convert.ToUInt16(TbxStartAddress.Text), Convert.ToUInt16(TbxCount.Text));
socket.Send(buffer);
byte[] receiveBuffer = new byte[1024];
socket.Receive(receiveBuffer);
if (!(receiveBuffer[7] == buffer[7]))
{
MessageBox.Show("客户端功能码与服务端功能码不同,请进行检查");
return;
}
int receiveDataLength = (receiveBuffer[4] * 256) + receiveBuffer[5] - 3; //获取返回的有效数据字节数
byte[] receiveDataBuffer = new byte[receiveDataLength]; //存放接收的有效数据字节
Array.Copy(receiveBuffer, 9, receiveDataBuffer, 0, receiveDataLength); //receiveBuffer中,前面九个字节为MBAP+功能码+返回的字节数量,所以要从第十个字节开始拿去数据
///因为读取coil为单个位,不可以用字节来表示了,所以注释掉
//for (int i = 0; i < receiveDataLength; i++)
//{
// for (int j = 0; j < 7; j++)
// TbxMessage.AppendText($"第{i}字节位数值为:" + BitConverter.ToBoolean(receiveDataBuffer, j) + "\r\n");
//}
BitArray boolArray = new BitArray(receiveDataBuffer);
for (int i = 0; i < boolArray.Length; i++)
{
if (i % 2 == 0)
{
TbxMessage.AppendText($"读取第{i + Convert.ToInt32(TbxStartAddress.Text)}位数值为:" + boolArray[i] + "\t | \t");
}
else
{
TbxMessage.AppendText($"读取第{i + Convert.ToInt32(TbxStartAddress.Text)}位数值为:" + boolArray[i] + "\r\n");
}
}
}
02功能码代码如下:
/// <summary>
/// 读取输入位 功能码02
/// </summary>
void ReadInputBit()
{
byte[] buffer;
buffer = SendMessage(Convert.ToByte(TbxFunction.Text), Convert.ToUInt16(TbxStartAddress.Text), Convert.ToUInt16(TbxCount.Text));
socket.Send(buffer);
byte[] receiveBuffer = new byte[1024];
socket.Receive(receiveBuffer);
if (!(receiveBuffer[7] == buffer[7]))
{
MessageBox.Show("客户端功能码与服务端功能码不同,请进行检查");
return;
}
int receiveDataLength = (receiveBuffer[4] * 256) + receiveBuffer[5] - 3; //获取返回的有效数据字节数
byte[] receiveDataBuffer = new byte[receiveDataLength]; //存放接收的有效数据字节
Array.Copy(receiveBuffer, 9, receiveDataBuffer, 0, receiveDataLength); //receiveBuffer中,前面九个字节为MBAP+功能码+返回的字节数量,所以要从第十个字节开始拿去数据
BitArray boolArray = new BitArray(receiveDataBuffer);
for (int i = 0; i < boolArray.Length; i++)
{
if (i % 2 == 0)
{
TbxMessage.AppendText($"读取第{i + Convert.ToInt32(TbxStartAddress.Text)}位数值为:" + boolArray[i] + "\t | \t");
}
else
{
TbxMessage.AppendText($"读取第{i + Convert.ToInt32(TbxStartAddress.Text)}位数值为:" + boolArray[i] + "\r\n");
}
}
}
03功能码代码如下:
/// <summary>
/// 读取寄存器 功能码03
/// </summary>
void ReadRegister()
{
byte[] buffer;
buffer = SendMessage(Convert.ToByte(TbxFunction.Text), Convert.ToUInt16(TbxStartAddress.Text), Convert.ToUInt16(TbxCount.Text));
socket.Send(buffer);
byte[] receiveBuffer = new byte[1024];
socket.Receive(receiveBuffer);
if (!(receiveBuffer[7] == buffer[7]))
{
MessageBox.Show("客户端功能码与服务端功能码不同,请进行检查");
return;
}
int receiveDataLength = (receiveBuffer[4] * 256) + receiveBuffer[5] - 3; //获取返回的有效数据字节数
byte[] receiveDataBuffer = new byte[receiveDataLength]; //存放接收的有效数据字节
Array.Copy(receiveBuffer, 9, receiveDataBuffer, 0, receiveDataLength); //receiveBuffer中,前面九个字节为MBAP+功能码+返回的字节数量,所以要从第十个字节开始拿去数据
//byte[] reverseDataBuffer = receiveDataBuffer.Reverse().ToArray(); //此处没必要反转,因为for里面,展示当前第几位不好与实际数值匹配,for内部直接采取IPAddress.NetWorlToHostOrder进行大小端反转
for (int i = 0; i < receiveDataLength; i += 2)
{
if ((i / 2) % 2 == 0)
{
//因为数据是两个字节为单位,所以i/2 ,接收到的报文是大端在前,而C#是小端在前模式,直接使用IPAddress.NetWorkToHostOrder进行数值大小端转换
TbxMessage.AppendText($"读取第{i / 2 + Convert.ToInt32(TbxStartAddress.Text)}位数值为:" + IPAddress.NetworkToHostOrder(BitConverter.ToInt16(receiveDataBuffer, i)).ToString() + "\t | \t");
}
else
{
//因为数据是两个字节为单位,所以i/2 ,接收到的报文是大端在前,而C#是小端在前模式,直接使用IPAddress.NetWorkToHostOrder进行数值大小端转换
TbxMessage.AppendText($"读取第{i / 2 + Convert.ToInt32(TbxStartAddress.Text)}位数值为:" + IPAddress.NetworkToHostOrder(BitConverter.ToInt16(receiveDataBuffer, i)).ToString() + "\r\n");
}
}
}
04功能码代码如下:
/// <summary>
/// 读取输入寄存器 功能码04
/// </summary>
void ReadInputRegister()
{
byte[] buffer;
buffer = SendMessage(Convert.ToByte(TbxFunction.Text), Convert.ToUInt16(TbxStartAddress.Text), Convert.ToUInt16(TbxCount.Text));
socket.Send(buffer);
byte[] receiveBuffer = new byte[1024];
socket.Receive(receiveBuffer);
if (!(receiveBuffer[7] == buffer[7]))
{
MessageBox.Show("客户端功能码与服务端功能码不同,请进行检查");
return;
}
int receiveDataLength = (receiveBuffer[4] * 256) + receiveBuffer[5] - 3; //获取返回的有效数据字节数
byte[] receiveDataBuffer = new byte[receiveDataLength]; //存放接收的有效数据字节
Array.Copy(receiveBuffer, 9, receiveDataBuffer, 0, receiveDataLength); //receiveBuffer中,前面九个字节为MBAP+功能码+返回的字节数量,所以要从第十个字节开始拿去数据
//byte[] reverseDataBuffer = receiveDataBuffer.Reverse().ToArray(); //此处没必要反转,因为for里面,展示当前第几位不好与实际数值匹配,for内部直接采取IPAddress.NetWorlToHostOrder进行大小端反转
for (int i = 0; i < receiveDataLength; i += 2)
{
if ((i / 2) % 2 == 0)
{
//因为数据是两个字节为单位,所以i/2 ,接收到的报文是大端在前,而C#是小端在前模式,直接使用IPAddress.NetWorkToHostOrder进行数值大小端转换
TbxMessage.AppendText($"读取第{i / 2 + Convert.ToInt32(TbxStartAddress.Text)}位数值为:" + IPAddress.NetworkToHostOrder(BitConverter.ToInt16(receiveDataBuffer, i)).ToString() + "\t | \t");
}
else
{
//因为数据是两个字节为单位,所以i/2 ,接收到的报文是大端在前,而C#是小端在前模式,直接使用IPAddress.NetWorkToHostOrder进行数值大小端转换
TbxMessage.AppendText($"读取第{i / 2 + Convert.ToInt32(TbxStartAddress.Text)}位数值为:" + IPAddress.NetworkToHostOrder(BitConverter.ToInt16(receiveDataBuffer, i)).ToString() + "\r\n");
}
}
}
第五部分:写入部分
读取部分代码已经展示完毕,接下来我们一起看一下写入部分代码,无论是读取还是写入,都是客户端主动发送报文,所以在写入部分,依然是调用了我们封装的SendMessage()方法,而且该方法第二个重载就是为了15、16功能码所写。
写入部分根据功能码,分成了四个部分,分别对应功能码05、功能码06、功能码15、功能码16。先附上调用代码,具体代码如下:
/// <summary>
/// 写入功能码
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnWriteData_Click(object sender, EventArgs e)
{
try
{
switch (Convert.ToInt16(TbxFunction.Text))
{
case 5:
WriteSingleCoil();
break;
case 6:
WriteRegister();
break;
case 15:
WriteCoils();
break;
case 16:
WriteRegisters();
break;
default:
MessageBox.Show("请检查功能码输入是否正确");
break;
}
}
catch
{
if (socket.Connected)
{
MessageBox.Show("请正确输入功能码以及相关参数");
}
else
{
MessageBox.Show("请先建立连接");
}
}
}
05功能码代码如下:
/// <summary>
/// 写入线圈 功能码05 报文注意: 0xff00 置1 0x0000置0 Message:MBAP+ 0x05+线圈地址H+线圈地址L+线圈值H+线圈值L
/// </summary>
void WriteSingleCoil()
{
//对于写入线圈coil, 报文 0xff00为置1 0x0000为置0
byte[] buffer;
byte[] coilValue = new byte[2];
if (Convert.ToUInt16(TbxCount.Text) == 1)
{
coilValue[1] = 0xff;
coilValue[0] = 0x00;
}
else
{
coilValue[1] = 0x00;
coilValue[0] = 0x00;
}
buffer = SendMessage(Convert.ToByte(TbxFunction.Text), Convert.ToUInt16(TbxStartAddress.Text), BitConverter.ToUInt16(coilValue, 0));
socket.Send(buffer);
byte[] receiveBuffer = new byte[1024];
socket.Receive(receiveBuffer);
if (!(receiveBuffer[7] == buffer[7]))
{
MessageBox.Show("客户端功能码与服务端功能码不同,请进行检查");
return;
}
MessageBox.Show("写入成功");
}
06功能码代码如下:
/// <summary>
/// WriteSingleRegister Function:06 Message: MBAP+ 0x06+寄存器地址H+寄存器地址L+寄存器值H+寄存器值L
/// </summary>
void WriteRegister()
{
byte[] buffer;
buffer = SendMessage(Convert.ToByte(TbxFunction.Text), Convert.ToUInt16(TbxStartAddress.Text), Convert.ToUInt16(TbxCount.Text));
socket.Send(buffer);
byte[] receiveBuffer = new byte[1024];
socket.Receive(receiveBuffer);
if (!(receiveBuffer[7] == buffer[7]))
{
MessageBox.Show("客户端功能码与服务端功能码不同,请进行检查");
return;
}
MessageBox.Show("写入成功");
}
15功能码代码如下:
/// <summary>
/// WriteCoils Function:15 Message: MBAP(the length will be change) + 0x15 +寄存器地址H+寄存器地址L+Bool输出数量H+Bool输出数量L + ByteCount(输出数量%8==0?输出数量:输出数量+1) + Values[输出数量%8==0?输出数量:输出数量+1]
/// </summary>
void WriteCoils()
{
byte[] buffer;
//get the byteCount
byte bytecount = Convert.ToByte(Convert.ToUInt16(TbxCount.Text) % 8 == 0 ? Convert.ToUInt16(TbxCount.Text) / 8 : (Convert.ToUInt16(TbxCount.Text) / 8) + 1);
string[] str = TbxMessage.Text.Split(',');
//get the valuesLength
byte[] values = new byte[Convert.ToUInt16(TbxCount.Text) % 8 == 0 ? Convert.ToUInt16(TbxCount.Text) / 8 : (Convert.ToUInt16(TbxCount.Text) / 8) + 1];
bool[] bools = new bool[values.Length * 8];
//这里使用BitArray,可以对位进行操作 ,先对位进行操作,然后再将该对象copy给byte[]即可
BitArray bitArray = new BitArray(values.Length * 8);
//ushort iii = 0;
for (int i = 0; i < str.Length; i++)
{
if (str[i] == "1")
{
bools[i] = true;
bitArray.Set(i, true);
}
else
{
bools[i] = false;
bitArray.Set(i, false);
}
}
//BitArray.CopyTo 方法,将目标数组的指定索引处开始将整个BitArray复制到兼容的一维数组
// 重点:这个复制,是将会按位去复制到目标数组!!!!!!!
bitArray.CopyTo(values, 0);
buffer = SendMessage(Convert.ToByte(TbxFunction.Text), Convert.ToUInt16(TbxStartAddress.Text), Convert.ToUInt16(TbxCount.Text), bytecount, values);
socket.Send(buffer);
//byte[] data = new byte[] { 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, 0x01, 0x0F, 0x00, 0x00, 0x00, 0x0A, 0x02, 0xCD, 0x01 };
//socket.Send(data);
//byte[] bbb = new byte[1024];
//int aaa = socket.Receive(bbb);
byte[] receiveBuffer = new byte[1024];
socket.Receive(receiveBuffer);
if (!(receiveBuffer[7] == buffer[7]))
{
MessageBox.Show("1.客户端功能码与服务端功能码不同,请进行检查\r\n2.服务器端数据地址未开放,请检查");
return;
}
MessageBox.Show("写入成功");
}
16功能码代码如下:
/// <summary>
/// WriteRegisters Function:16 Message:MBAP+16+ 起始地址H+起始地址L+寄存器个数H+寄存器个数L+ byteCount(寄存器个数*2 // so the registerCount max value is 127) + values[(寄存器个数*2)]
/// </summary>
void WriteRegisters()
{
byte[] buffer;
//get the byteCount
byte bytecount = Convert.ToByte(Convert.ToUInt16(TbxCount.Text) * 2);
//becuase the one str represent a word , so the valuesLength is double valueLength
string[] str = TbxMessage.Text.Split(',');
ushort[] value = new ushort[Convert.ToUInt16(TbxCount.Text)];
byte[] values = new byte[Convert.ToUInt16(TbxCount.Text) * 2];
//becuase the one str represent two byte ,so first change to ushort , than change to byte
//use IPAddress.HostToNetWorkOrder, to rollback values
for (int i = 0; i < str.Length; i++)
{
//声明一个临时byte数组,为了大小端对调 ----- 因为重载方法 SendMessage里面 发送寄存器数值字节数组 是顺序赋值进去 先进排前面 但是modbus协议,是大端在前, 所以这里需要反转,不然给1的时候相当于给了256这样
byte[] tempByte = new byte[2];
tempByte[0] = BitConverter.GetBytes(Convert.ToUInt16(str[i]))[1];
tempByte[1] = BitConverter.GetBytes(Convert.ToUInt16(str[i]))[0];
//将对调的大小端复制给value,用于下面复制给values
value[i] = BitConverter.ToUInt16(tempByte, 0);
for (int j = 0; j < 2; j++)
{
values[i * 2 + j] = BitConverter.GetBytes(value[i])[j];
}
}
buffer = SendMessage(Convert.ToByte(TbxFunction.Text), Convert.ToUInt16(TbxStartAddress.Text), Convert.ToUInt16(TbxCount.Text), bytecount, values);
socket.Send(buffer);
byte[] receiveBuffer = new byte[1024];
socket.Receive(receiveBuffer);
if (!(receiveBuffer[7] == buffer[7]))
{
MessageBox.Show("1.客户端功能码与服务端功能码不同,请进行检查\r\n2.服务器端数据地址未开放,请检查");
return;
}
MessageBox.Show("写入成功");
}
以上为modbusTcp客户端创建的所有代码内容,直接复制粘贴就可以使用了,第一次写博客,有诸多不足,还请各位指教,不断改进。
小破站同名UP主:工控白小白文章来源:https://www.toymoban.com/news/detail-680852.html
希望遇到更多志同道合的朋友,一起前进~~~~~文章来源地址https://www.toymoban.com/news/detail-680852.html
到了这里,关于基于C#的ModbusTcp客户端Demo的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!