基于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日
    浏览(50)
  • 一种基于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日
    浏览(42)
  • S7-200SMART 实现MODBUS TCP通信的具体方法示例(客户端读写+服务器响应)

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

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

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

    2024年04月17日
    浏览(47)
  • 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日
    浏览(52)
  • 如何链接多个modbus_tcp设备,并将设备数据写入同一个modbusSlave,以便外部客户端获取所有链接设备的数据。

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

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

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

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

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

    2024年02月11日
    浏览(62)
  • 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日
    浏览(46)
  • 【工控通信】ModbusTCP通讯之ModbusPoll客户端工具配置

    1.安装Modbus Poll客户端工具 2.Modbus Poll客户端工具安装好以后的界面 Modbus Poll uses a multiple windows user interface. That means you can open several windows showing different data areas or data from different slave ID’s at the same time. You can write any text in the Alias cells. In any dialog box you can press the F1 key for more he

    2024年02月13日
    浏览(39)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包