基于C#的ModbusTcp客户端Demo

这篇具有很好参考价值的文章主要介绍了基于C#的ModbusTcp客户端Demo。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

        今天跟大家分享一个基于C#的ModbusTcp客户端的创建,本人小白一枚,第一次发表博客,有诸多不足之处,还请谅解,也希望大佬可以指点,如何可以做得更好。

先展示一下成品效果吧。

c# modbus tcp客户端,ModbusTcp客户端创建,c#,.net,单例模式

        Demo看起来就跟上图一样,这里ui使用了sunnyui的一些控件,以及运用了单例模式,扁平风风格,自动读取数据等功能。


        上代码之前先简单介绍一下ModbusTcp,ModbusTcp是在ModbusRTU的基础上做了相对应的变更,去掉了ModbusRTU的从站地址以及CRC校验码,在数据头增加了MBAP报文头,整体ModbusTCP数据格式由 MBAP+PDU组成,如下图所示:

c# modbus tcp客户端,ModbusTcp客户端创建,c#,.net,单例模式

 关于MBAP,具体如下:

  1. 事务处理标识符:可以自由定义,服务器端将返回相同内容,默认为 00 01
  2. 协议标识符: modbus协议规定,必须为00 00
  3. 长度: 数据内容由单元标识符+PDU,两者共同长度之和,除了功能码15、16之外都为00 06,只有功能码15、16时这个数据为不定。
  4. 单元标识符:可以理解为替代了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功能码已经讲完了,接下来开始上代码啦~~~~~


由于代码内容较多,这里只展示五个部分的内容:

  1. 窗体淡出效果
  2. 建立连接&断开连接&防断线
  3. 客户端发送报文
  4. 读取部分
  5. 发送部分

第一部分:窗体淡出效果

        这里代码产生的效果是达到扁平风风格,当我们关闭客户端时,客户端应用程序将会渐渐淡化直到退出,具体代码如下:

        #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

到了这里,关于基于C#的ModbusTcp客户端Demo的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C++ ModBUS TCP客户端工具 qModMaster 介绍及使用

    QModMaster是一个基于Qt的Modbus主站(Master)模拟器,用于模拟和测试Modbus TCP和RTU通信。它提供了一个直观的图形界面,使用户能够轻松设置和发送Modbus请求,并查看和分析响应数据。 以下是QModMaster工具的一些主要特点和功能:  支持Modbus TCP和RTU:QModMaster可以用作Modbus TCP和

    2024年02月13日
    浏览(43)
  • 一种基于QT5.12+VS2015的ModbusTcp客户端轮询方法

    一种基于QT5.12+VS2015的ModbusTcp客户端轮询方法 服务端:西门子PLC-1200 客户端:Win10+QT5.12.1+VS2015 (1)客户端00000(线圈:可读可写) - 服务端10000(离散输入) (2)客户端10000(离散输入:只读) - 服务端00000(线圈) (3)客户端30000(输入寄存器:只读) - 服务端40000(保持

    2024年02月08日
    浏览(40)
  • S7-200SMART 实现MODBUS TCP通信的具体方法示例(客户端读写+服务器响应)

    前面和大家介绍了MODBUS TCP的基本使用方法,具体可参考以下链接中的内容: S7-200SMART实现MODBUS TCP通信(客户端+服务器)的具体方法和步骤示例 本次继续和大家分享S7-200SMART 中实现MODBUS TCP通信的具体方法 , 任务要求:

    2024年02月16日
    浏览(46)
  • 三菱FX5U modbus tcp协议 plc做服务器和客户端案例程序

    三菱FX5U  modbus tcp协议  plc做服务器和客户端案例程序,提供调试工具,程序注解,通讯协议功能的配置。 标题:三菱FX5U PLC在Modbus TCP协议中充当服务器和客户端的案例程序及通信配置详解 摘要:本文主要介绍了如何在三菱FX5U PLC上实现Modbus TCP协议的服务器和客户端功能,并

    2024年04月17日
    浏览(46)
  • C# socket——简单的TCP 客户端 连接通信

    TCP编程的客户端一般步骤是: 1、创建一个socket,用函数socket()。 2、设置socket属性。 3、设置要连接的对方的IP地址和端口等属性。 4、连接服务器,用函数connect()。 5、收发数据,用函数send()和recv(),或者read()和write()。 6、关闭网络连接。 using System; using System.Net; using System.

    2024年02月11日
    浏览(48)
  • 如何链接多个modbus_tcp设备,并将设备数据写入同一个modbusSlave,以便外部客户端获取所有链接设备的数据。

    在modbus通信中,一个modbus服务器一次只能链接一个客户机,那么,外部客户端要获取多个设备的modbus数据,就需要使用链接一个专用的mosbus服务器,一下就是详细解决方法。 第一步:创建modbus客户端,链接一个modbus设备,然后再链接一个共有的modbus服务器,modbus客户端可以连

    2024年04月25日
    浏览(55)
  • C# 简易TCP网口调试助手(一) 客户端Client

      最近的上位机开发工作中开始频繁涉及到网口、串口的通讯,网上找了各种资料和帖子都没怎么找到好用的开源代码或者工具。目前找到几个好一点的方式来实现的网口和串口通讯工具包,先写个好用的TCP的negut包记录下来,将使用的步骤写下来做个记录。   本博客主要用

    2024年04月13日
    浏览(45)
  • C#实现简单TCP服务器和客户端网络编程

    在C#中进行网络编程涉及许多类和命名空间,用于创建和管理网络连接、传输数据等。下面是一些主要涉及的类和命名空间: System.Net 命名空间: 这个命名空间提供了大部分网络编程所需的类,包括: IPAddress :用于表示IP地址。 IPEndPoint :表示IP地址和端口号的组合。 Socke

    2024年02月11日
    浏览(60)
  • C# 解决TCP Server 关不掉客户端连接的问题

    拷贝了一段 TCP Server的应用代码,第一次运行正常,但是关闭软件或者实现disconnect+close后都无法关闭端口连接。 关闭之后,另外一个客户端还在正常与PC连接。 TCP Server 重新运行,无法接收到客户端的连接。 1、 C#  .net界面程序 2、 .net 4.8.1 3、 System.Net.Sockets 4、 TcpListener 接

    2024年02月15日
    浏览(44)
  • QT5.14 实现ModbusTCP客户端 Demo

    本文在QT5.14平台,基于QModbusClientTcp类,实现了客户端对单个寄存器的读写,用ModbusSlave做服务器做测试。 1.界面 (1)更改读按钮的名称为bt_Read (2)更改写按钮的名称为bt_Write 2.修改pro文件的第三行 greaterThan(QT_MAJOR_VERSION, 4): QT += widgets  serialbus   3.修改mainWindow.h #ifndef MAINWINDOW_H

    2024年01月22日
    浏览(58)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包