基于C#的ModbusTcp客户端Demo

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

先展示一下成品效果吧。

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


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

 关于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主:工控白小白

希望遇到更多志同道合的朋友,一起前进~~~~~

猜你喜欢

转载自blog.csdn.net/m0_45366556/article/details/130709013
今日推荐