zoukankan      html  css  js  c++  java
  • HslCommunication库的二次协议扩展,适配第三方通讯协议开发,基础框架支持长短连接模式

    本文将使用一个gitHub开源的项目来扩展实现二次协议的开发,该项目已经搭建好了基础层架构,并实现了三菱,西门子,欧姆龙,MODBUS-TCP的通讯示例,也可以参照这些示例开发其他的通讯协议,并Pull request到这个项目中来实现这个项目的最终目标

    github地址:https://github.com/dathlin/HslCommunication 如果喜欢可以star或是fork,还可以打赏支持。

    联系作者及加群方式(激活码在群里发放):http://www.hslcommunication.cn/Cooperation

    在Visual Studio 中的NuGet管理器中可以下载安装,也可以直接在NuGet控制台输入下面的指令安装

    Install-Package HslCommunication
    

     如果需要教程:Nuget安装教程:http://www.cnblogs.com/dathlin/p/7705014.html

    组件的完整信息和其他API介绍参照:http://www.cnblogs.com/dathlin/p/7703805.html   组件的授权协议,更新日志,都在该页面里面。

    本文将展示如果进行二次扩展通讯协议,来进行远程交互,可以是PLC协议,自定义协议等等。以一个示例为切入点,根据这个示例来深入讲解

    此处使用到了2个命名空间:

    using HslCommunication;
    using HslCommunication.Core.Net;
    

    关于两种模式


    本组件所提供的所有客户端类,包括三菱,西门子,欧姆龙,modbus-tcp,以及SimplifyNet都是继承自双模式基类,双模式包含了短连接和长连接,下面就具体介绍下两个模式的区别

    短连接:每次读写都是一个单独的请求,请求完毕也就关闭了,如果服务器的端口仅仅支持单连接,那么关闭后这个端口可以被其他连接复用,但是在频繁的网络请求下,容易发生异常,会有其他的请求不成功,尤其是多线程的情况下。

    长连接:创建一个公用的连接通道,所有的读写请求都利用这个通道来完成,这样的话,读写性能更快速,即时多线程调用也不会影响,内部有同步机制。如果服务器的端口仅仅支持单连接,那么这个端口就被占用了,比如三菱的端口机制,西门子的Modbus tcp端口机制也是这样的。以下代码默认使用长连接,性能更高,还支持多线程同步。

    在短连接的模式下,每次请求都是单独的访问,所以没有重连的困扰,在长连接的模式下,如果本次请求失败了,在下次请求的时候,会自动重新连接服务器,直到请求成功为止。另外,尽量所有的读写都对结果的成功进行判断。

    关于日志记录


    不管是三菱的数据访问类,还是西门子的,还是Modbus tcp访问类,都有一个LogNet属性用来记录日志,该属性是一个接口类,ILogNet,凡事继承该接口的都可以用来记录日志,该日志会在访问失败时,尤其是因为网络的原因导致访问失败时会进行日志记录(如果你为这个 LogNet 属性配置了真实的日志记录器的话):如果你想使用该记录日志的功能,请参照如下的博客进行实例化:

    http://www.cnblogs.com/dathlin/p/7691693.html

    关于基类:public class NetworkDoubleBase<TNetMessage, TTransform> : NetworkBase where TNetMessage : INetMessage, new() where TTransform : IByteTransform, new()


    该基类定义了连接方法,单次的数据请求方法,但是需要指定消息类型,TNetMessage指示了该消息类型必须继承自接口INetMessage,至于TTransform指示了一些数据类型的变换规则,这两个类型指定完成后,后面的事情就是定义地址解析器,定义读写指令创建,定义基础的读写方法,然后扩展不同类型的数据读写。

    开始二次开发:


    先定义消息:消息的接口指示了如果去接收一条完整的消息,通常都是byte[]数据,我们看一下这个接口的定义

        /// <summary>
        /// 本系统的消息类,包含了各种解析规则,数据信息提取规则
        /// </summary>
        public interface INetMessage
        {
            /// <summary>
            /// 消息头的指令长度
            /// </summary>
            int ProtocolHeadBytesLength { get; }
    
    
            /// <summary>
            /// 从当前的头子节文件中提取出接下来需要接收的数据长度
            /// </summary>
            /// <returns>返回接下来的数据内容长度</returns>
            int GetContentLengthByHeadBytes();
    
    
            /// <summary>
            /// 检查头子节的合法性
            /// </summary>
            /// <param name="token">特殊的令牌,有些特殊消息的验证</param>
            /// <returns></returns>
            bool CheckHeadBytesLegal(byte[] token);
    
    
            /// <summary>
            /// 获取头子节里的消息标识
            /// </summary>
            /// <returns></returns>
            int GetHeadBytesIdentity();
    
    
            /// <summary>
            /// 消息头字节
            /// </summary>
            byte[] HeadBytes { get; set; }
    
    
            /// <summary>
            /// 消息内容字节
            /// </summary>
            byte[] ContentBytes { get; set; }
    
    
            /// <summary>
            /// 发送的字节信息
            /// </summary>
            byte[] SendBytes { get; set; }
        }
        
    
    }
    

    举例来说明:例子一:Modbus-Tcp消息,通常如下:

    byte[0] byte[1]  消息头 byte[0]*256+byte[1]

    byte[2] byte[3] 必须都是0,否则不是Modbus协议

    byte[4] byte[5] 后面跟着的消息长度,长度为byte[4]*256 + byte[5]

    byte[6] 站号

    byte[7] 功能码

    byte[8] byte[9] 地址

    ...

    ...

    等等,不管后面是什么了

    OK,现在已经可以写TNetMessage了,主要思路是先接收6个长度的头子节,接收完后 HeadBytes 就是6个长度的字节,如果需要验证,就判断byte[2],byte[3]是不是都为0,然后写一个方法,从这个头子节数据里分析出接下来的数据长度, 然后就可以按照下面写。

    下面的验证消息接收的合法性,还需要根据发送消息的消息号,接收的消息号要一致。

        /// <summary>
        /// Modbus-Tcp协议支持的消息解析类
        /// </summary>
        public class ModbusTcpMessage : INetMessage
        {
            /// <summary>
            /// 消息头的指令长度
            /// </summary>
            public int ProtocolHeadBytesLength
            {
                get { return 6; }
            }
    
    
            /// <summary>
            /// 从当前的头子节文件中提取出接下来需要接收的数据长度
            /// </summary>
            /// <returns>返回接下来的数据内容长度</returns>
            public int GetContentLengthByHeadBytes( )
            {
                   return = HeadBytes[4] * 256 + HeadBytes[5];
            }
    
    
            /// <summary>
            /// 检查头子节的合法性
            /// </summary>
            /// <param name="token">特殊的令牌,有些特殊消息的验证</param>
            /// <returns></returns>
            public bool CheckHeadBytesLegal( byte[] token )
            {
                if (SendBytes[0] != HeadBytes[0] || SendBytes[1] != HeadBytes[1]) return false;
                return HeadBytes[2] == 0x00 && HeadBytes[3] == 0x00;
            }
    
    
            /// <summary>
            /// 获取头子节里的消息标识
            /// </summary>
            /// <returns></returns>
            public int GetHeadBytesIdentity( )
            {
                return HeadBytes[0] * 256 + HeadBytes[1];// 有些协议没有标识就返回0
            }
    
    
            /// <summary>
            /// 消息头字节
            /// </summary>
            public byte[] HeadBytes { get; set; }
    
    
            /// <summary>
            /// 消息内容字节
            /// </summary>
            public byte[] ContentBytes { get; set; }
    
    
            /// <summary>
            /// 发送的字节信息
            /// </summary>
            public byte[] SendBytes { get; set; }
    
    
        }
    

    消息类写好 ,接下来就选取IByteTransform接口的类,这个接口定义了什么呢?定义了常用的数据类型和byte[]数组之间的转换方法。为什么要实现这个接口呢,因为不同设备的数据定义规则是不一样的,比如C#的类库,地位在前,高位在后,三菱PLC中也是类似的,西门子确实地位在后,高位在前,但是modbus-tcp和fins协议却以双字节为单位。

    所以本系统系统三个常用的数据转换类,如果有其他的机制,后面可以扩展,这三个类如下:

    • RegularByteTransform 常规的数据转换,低位在前,高位在后
    • ReverseBytesTransform 高地位反转的数据转换类,高位在前,地位在后
    • ReverseWordTransform 以字节为单位进行反转的数据类

    那么我们就选择好了类型,然后通讯类已经基本成型了

    public class ModbusTcpNet : NetworkDoubleBase<ModbusTcpMessage, ReverseWordTransform>
    {
    
    }
    

    然后创建基础的读取指令方法,和写入指令方法,此处简便处理,只针对寄存器进行操作

            /// <summary>
            /// 读取数据的基础指令,需要指定指令码,地址,长度
            /// </summary>
            /// <param name="code"></param>
            /// <param name="address"></param>
            /// <param name="count"></param>
            /// <returns></returns>
            private OperateResult<byte[]> BuildReadCommandBase( byte code, string address, ushort count )
            {
                ushort add = ushort.Parse( address );
                ushort messageId = (ushort)softIncrementCount.GetCurrentValue( );
                byte[] buffer = new byte[12];
                buffer[0] = (byte)(messageId / 256);
                buffer[1] = (byte)(messageId % 256);
                buffer[2] = 0x00;
                buffer[3] = 0x00;
                buffer[4] = 0x00;
                buffer[5] = 0x06;
                buffer[6] = station;
                buffer[7] = code;
                buffer[8] = (byte)(add / 256);
                buffer[9] = (byte)(add % 256);
                buffer[10] = (byte)(count / 256);
                buffer[11] = (byte)(count % 256);
    
                return OperateResult.CreateSuccessResult( buffer );
            }
    

    然后读取寄存器的基础方法是这样设计,基类里有个方法:

            /// <summary>
            /// 使用底层的数据报文来通讯,传入需要发送的消息,返回一条完整的数据指令
            /// </summary>
            /// <param name="send">发送的完整的报文信息</param>
            /// <returns>接收的完整的报文信息</returns>
            public OperateResult<byte[]> ReadFromCoreServer( byte[] send );
    

    这个方法是一次数据交互的成功与否,所以我们要封装一个二次方法,不仅仅是进行数据交互,进行消息的二次验证,如果验证失败,就返回错误还有相关的消息

            private OperateResult<byte[]> CheckModbusTcpResponse( byte[] send )
            {
                OperateResult<byte[]> result = ReadFromCoreServer( send );
                if (result.IsSuccess)
                {
                    if ((send[7] + 0x80) == result.Content[7])
                    {
                        // 发生了错误
                        result.IsSuccess = false;
                        result.Message = GetDescriptionByErrorCode( result.Content[8] );
                        result.ErrorCode = result.Content[8];
                    }
                }
                return result;
            }

    然后在封装一层基础的通信方法,在读取到数据并且验证成功之后,把读取到的数据内容单独提取出来,好让后续进行更加方便的处理。

            /// <summary>
            /// 读取服务器的数据,需要指定不同的功能码
            /// </summary>
            /// <param name="code">指令</param>
            /// <param name="address">地址</param>
            /// <param name="length">长度</param>
            /// <returns></returns>
            private OperateResult<byte[]> ReadModBusBase( byte code, string address, ushort length )
            {
                OperateResult<byte[]> command = BuildReadCommandBase( code, address, length );
                if (!command.IsSuccess) return OperateResult.CreateFailedResult<byte[]>( command );
    
                OperateResult<byte[]> resultBytes = CheckModbusTcpResponse( command.Content );
                if (resultBytes.IsSuccess)
                {
                    // 二次数据处理
                    if (resultBytes.Content?.Length >= 9)
                    {
                        byte[] buffer = new byte[resultBytes.Content.Length - 9];
                        Array.Copy( resultBytes.Content, 9, buffer, 0, buffer.Length );
                        resultBytes.Content = buffer;
                    }
                }
                return resultBytes;
            }
    

    有了上面两层的基础,最终提供了一个读取寄存器的基础方法,也就是第三层的方法

            /// <summary>
            /// 从Modbus服务器批量读取寄存器的信息,需要指定起始地址,读取长度
            /// </summary>
            /// <param name="address">起始地址,格式为"1234"</param>
            /// <param name="length">读取的数量</param>
            /// <returns>带有成功标志的字节信息</returns>
            public OperateResult<byte[]> Read( string address, ushort length )
            {
                OperateResult<byte[]> read = ReadModBusBase( ModbusInfo.ReadRegister, address, length );
                if (!read.IsSuccess) return OperateResult.CreateFailedResult<byte[]>( read );
                return read;
            }
    

    有了上面的读取寄存器的方法,那么我们可以方便的扩展其他基础类型的数据读取了。

            /// <summary>
            /// 读取指定地址的short数据
            /// </summary>
            /// <param name="address">起始地址,格式为"1234"</param>
            /// <returns>带有成功标志的short数据</returns>
            public OperateResult<short> ReadInt16( string address )
            {
                return GetInt16ResultFromBytes( Read( address, 1 ) );
            }
    
    
            /// <summary>
            /// 读取指定地址的ushort数据
            /// </summary>
            /// <param name="address">起始地址,格式为"1234"</param>
            /// <returns>带有成功标志的ushort数据</returns>
            public OperateResult<ushort> ReadUInt16( string address )
            {
                return GetUInt16ResultFromBytes( Read( address, 1 ) );
            }
    
            /// <summary>
            /// 读取指定地址的int数据
            /// </summary>
            /// <param name="address">起始地址,格式为"1234"</param>
            /// <returns>带有成功标志的int数据</returns>
            public OperateResult<int> ReadInt32( string address )
            {
                return GetInt32ResultFromBytes( Read( address, 2 ) );
            }
    
            /// <summary>
            /// 读取指定地址的uint数据
            /// </summary>
            /// <param name="address">起始地址,格式为"1234"</param>
            /// <returns>带有成功标志的uint数据</returns>
            public OperateResult<uint> ReadUInt32( string address )
            {
                return GetUInt32ResultFromBytes( Read( address, 2 ) );
            }
    
            /// <summary>
            /// 读取指定地址的float数据
            /// </summary>
            /// <param name="address">起始地址,格式为"1234"</param>
            /// <returns>带有成功标志的float数据</returns>
            public OperateResult<float> ReadFloat( string address )
            {
                return GetSingleResultFromBytes( Read( address, 2 ) );
            }
    
            /// <summary>
            /// 读取指定地址的long数据
            /// </summary>
            /// <param name="address">起始地址,格式为"1234"</param>
            /// <returns>带有成功标志的long数据</returns>
            public OperateResult<long> ReadInt64( string address )
            {
                return GetInt64ResultFromBytes( Read( address, 4 ) );
            }
    
            /// <summary>
            /// 读取指定地址的ulong数据
            /// </summary>
            /// <param name="address">起始地址,格式为"1234"</param>
            /// <returns>带有成功标志的ulong数据</returns>
            public OperateResult<ulong> ReadUInt64( string address )
            {
                return GetUInt64ResultFromBytes( Read( address, 4 ) );
            }
    
            /// <summary>
            /// 读取指定地址的double数据
            /// </summary>
            /// <param name="address">起始地址,格式为"1234"</param>
            /// <returns>带有成功标志的double数据</returns>
            public OperateResult<double> ReadDouble( string address )
            {
                return GetDoubleResultFromBytes( Read( address, 4 ) );
            }
    
            /// <summary>
            /// 读取地址地址的String数据,字符串编码为ASCII
            /// </summary>
            /// <param name="address">起始地址,格式为"1234"</param>
            /// <param name="length">字符串长度</param>
            /// <returns>带有成功标志的string数据</returns>
            public OperateResult<string> ReadString( string address, ushort length )
            {
                return GetStringResultFromBytes( Read( address, length ) );
            }
    

    到这里为止,就写完了寄存器的读取方法,实际上会更加复杂点,会把地址解析专门拿出来做成地址解析器,因为有些PLC的地址是比较复杂,例如西门子的"M100.2",就需要写个专门的解析器来解析,针对单次读取上限,也可以支持更具地址来多次访问等等操作。

    写入数据的例子:

    写入的操作通常不会返回数据,只要验证完指令的逻辑性即可,我们把地址解析器拿出来看看,先写地址解析器

            /// <summary>
            /// 解析数据地址,解析出地址类型,起始地址
            /// </summary>
            /// <param name="address">数据地址</param>
            /// <returns>解析出地址类型,起始地址,DB块的地址</returns>
            private OperateResult<int> AnalysisAddress( string address )
            {
                try
                {
                    return OperateResult.CreateSuccessResult( Convert.ToInt32( address ) );
                }
                catch (Exception ex)
                {
                    return new OperateResult<int>( )
                    {
                        Message = ex.Message
                    };
                }
            }
    

    解析完地址后,就创建写入的基础指令,需要指定字节数组,如下的创建方式是针对了多个寄存器写入的代码

            private OperateResult<byte[]> BuildWriteRegisterCommand( string address, byte[] data )
            {
                OperateResult<int> analysis = AnalysisAddress( address );
                if (!analysis.IsSuccess) return OperateResult.CreateFailedResult<byte[]>( analysis );
    
                ushort messageId = (ushort)softIncrementCount.GetCurrentValue( );
                byte[] buffer = new byte[13 + data.Length];
                buffer[0] = (byte)(messageId / 256);
                buffer[1] = (byte)(messageId % 256);
                buffer[2] = 0x00;
                buffer[3] = 0x00;
                buffer[4] = (byte)((buffer.Length - 6) / 256);
                buffer[5] = (byte)((buffer.Length - 6) % 256);
                buffer[6] = station;
                buffer[7] = ModbusInfo.WriteRegister;
                buffer[8] = (byte)(analysis.Content / 256);
                buffer[9] = (byte)(analysis.Content % 256);
                buffer[10] = (byte)(data.Length / 2 / 256);
                buffer[11] = (byte)(data.Length / 2 % 256);
    
                buffer[12] = (byte)(data.Length);
                data.CopyTo( buffer, 13 );
                return OperateResult.CreateSuccessResult( buffer );
            }
    

    那么写入数据基础方法就是

            /// <summary>
            /// 将数据写入到Modbus的寄存器上去,需要指定起始地址和数据内容
            /// </summary>
            /// <param name="address">起始地址,格式为"1234"</param>
            /// <param name="value">写入的数据,长度根据data的长度来指示</param>
            /// <returns>返回写入结果</returns>
            public OperateResult Write( string address, byte[] value )
            {
                OperateResult<byte[]> command = BuildWriteRegisterCommand( address, value );
                if (!command.IsSuccess)
                {
                    return command;
                }
    
                return CheckModbusTcpResponse( command.Content );
            }
    

    然后我们再想支持其他的数据类型,就好办很多了

            #region Write Short
    
            /// <summary>
            /// 向寄存器中写入short数组,返回值说明
            /// </summary>
            /// <param name="address">要写入的数据地址</param>
            /// <param name="values">要写入的实际数据</param>
            /// <returns>返回写入结果</returns>
            public OperateResult Write( string address, short[] values )
            {
                return Write( address, ByteTransform.TransByte( values ) );
            }
    
            /// <summary>
            /// 向寄存器中写入short数据,返回值说明
            /// </summary>
            /// <param name="address">要写入的数据地址</param>
            /// <param name="value">要写入的实际数据</param>
            /// <returns>返回写入结果</returns>
            public OperateResult Write( string address, short value )
            {
                return Write( address, new short[] { value } );
            }
    
            #endregion
    
            #region Write UShort
    
    
            /// <summary>
            /// 向寄存器中写入ushort数组,返回值说明
            /// </summary>
            /// <param name="address">要写入的数据地址</param>
            /// <param name="values">要写入的实际数据</param>
            /// <returns>返回写入结果</returns>
            public OperateResult Write( string address, ushort[] values )
            {
                return Write( address, ByteTransform.TransByte( values ) );
            }
    
    
            /// <summary>
            /// 向寄存器中写入ushort数据,返回值说明
            /// </summary>
            /// <param name="address">要写入的数据地址</param>
            /// <param name="value">要写入的实际数据</param>
            /// <returns>返回写入结果</returns>
            public OperateResult Write( string address, ushort value )
            {
                return Write( address, new ushort[] { value } );
            }
    
    
            #endregion
    
            #region Write Int
    
            /// <summary>
            /// 向寄存器中写入int数组,返回值说明
            /// </summary>
            /// <param name="address">要写入的数据地址</param>
            /// <param name="values">要写入的实际数据</param>
            /// <returns>返回写入结果</returns>
            public OperateResult Write( string address, int[] values )
            {
                return Write( address, ByteTransform.TransByte( values ) );
            }
    
            /// <summary>
            /// 向寄存器中写入int数据,返回值说明
            /// </summary>
            /// <param name="address">要写入的数据地址</param>
            /// <param name="value">要写入的实际数据</param>
            /// <returns>返回写入结果</returns>
            public OperateResult Write( string address, int value )
            {
                return Write( address, new int[] { value } );
            }
    
            #endregion
    
            #region Write UInt
    
            /// <summary>
            /// 向寄存器中写入uint数组,返回值说明
            /// </summary>
            /// <param name="address">要写入的数据地址</param>
            /// <param name="values">要写入的实际数据</param>
            /// <returns>返回写入结果</returns>
            public OperateResult Write( string address, uint[] values )
            {
                return Write( address, ByteTransform.TransByte( values ) );
            }
    
            /// <summary>
            /// 向寄存器中写入uint数据,返回值说明
            /// </summary>
            /// <param name="address">要写入的数据地址</param>
            /// <param name="value">要写入的实际数据</param>
            /// <returns>返回写入结果</returns>
            public OperateResult Write( string address, uint value )
            {
                return Write( address, new uint[] { value } );
            }
    
            #endregion
    
            #region Write Float
    
            /// <summary>
            /// 向寄存器中写入float数组,返回值说明
            /// </summary>
            /// <param name="address">要写入的数据地址</param>
            /// <param name="values">要写入的实际数据</param>
            /// <returns>返回写入结果</returns>
            public OperateResult Write( string address, float[] values )
            {
                return Write( address, ByteTransform.TransByte( values ) );
            }
    
            /// <summary>
            /// 向寄存器中写入float数据,返回值说明
            /// </summary>
            /// <param name="address">要写入的数据地址</param>
            /// <param name="value">要写入的实际数据</param>
            /// <returns>返回写入结果</returns>
            public OperateResult Write( string address, float value )
            {
                return Write( address, new float[] { value } );
            }
    
    
            #endregion
    
            #region Write Long
    
            /// <summary>
            /// 向寄存器中写入long数组,返回值说明
            /// </summary>
            /// <param name="address">要写入的数据地址</param>
            /// <param name="values">要写入的实际数据</param>
            /// <returns>返回写入结果</returns>
            public OperateResult Write( string address, long[] values )
            {
                return Write( address, ByteTransform.TransByte( values ) );
            }
    
            /// <summary>
            /// 向寄存器中写入long数据,返回值说明
            /// </summary>
            /// <param name="address">要写入的数据地址</param>
            /// <param name="value">要写入的实际数据</param>
            /// <returns>返回写入结果</returns>
            public OperateResult Write( string address, long value )
            {
                return Write( address, new long[] { value } );
            }
    
            #endregion
    
            #region Write ULong
    
            /// <summary>
            /// 向寄存器中写入ulong数组,返回值说明
            /// </summary>
            /// <param name="address">要写入的数据地址</param>
            /// <param name="values">要写入的实际数据</param>
            /// <returns>返回写入结果</returns>
            public OperateResult Write( string address, ulong[] values )
            {
                return Write( address, ByteTransform.TransByte( values ) );
            }
    
            /// <summary>
            /// 向寄存器中写入ulong数据,返回值说明
            /// </summary>
            /// <param name="address">要写入的数据地址</param>
            /// <param name="value">要写入的实际数据</param>
            /// <returns>返回写入结果</returns>
            public OperateResult Write( string address, ulong value )
            {
                return Write( address, new ulong[] { value } );
            }
    
            #endregion
    
            #region Write Double
    
            /// <summary>
            /// 向寄存器中写入double数组,返回值说明
            /// </summary>
            /// <param name="address">要写入的数据地址</param>
            /// <param name="values">要写入的实际数据</param>
            /// <returns>返回写入结果</returns>
            public OperateResult Write( string address, double[] values )
            {
                return Write( address, ByteTransform.TransByte( values ) );
            }
    
            /// <summary>
            /// 向寄存器中写入double数据,返回值说明
            /// </summary>
            /// <param name="address">要写入的数据地址</param>
            /// <param name="value">要写入的实际数据</param>
            /// <returns>返回写入结果</returns>
            public OperateResult Write( string address, double value )
            {
                return Write( address, new double[] { value } );
            }
    
            #endregion
    

    到这里为止,基础的操作和扩展讲的差不多了。接下来就要针对某些特殊的设备进行适配,比如我在实际的开发中,发现西门子,欧姆龙的通信协议中,没有一个握手信号交互的过程,在西门子里还要进行2次握手,在欧姆龙里要进行一次握手,这些握手信息在网络连接上之后就需要进行交互,不然无法现在读取。在上述的MODBUS协议了就不需要握手信号,如果想支持握手信号,那么就要重写一个方法

            /// <summary>
            /// 在连接上欧姆龙PLC后,需要进行一步握手协议
            /// </summary>
            /// <param name="socket"></param>
            /// <returns></returns>
            protected override OperateResult InitilizationOnConnect( Socket socket )
            {
                // handSingle就是握手信号字节
                OperateResult<byte[], byte[]> read = ReadFromCoreServerBase( socket, handSingle );
                if (!read.IsSuccess) return read;
                
                // 检查返回的状态
                byte[] buffer = new byte[4];
                buffer[0] = read.Content2[7];
                buffer[1] = read.Content2[6];
                buffer[2] = read.Content2[5];
                buffer[3] = read.Content2[4];
                int status = BitConverter.ToInt32( buffer, 0 );
                if(status != 0)
                {
                    return new OperateResult( )
                    {
                        ErrorCode = status,
                        Message = "初始化失败,具体原因请根据错误码查找"
                    };
                }
    
                // 提取PLC的节点地址
                if (read.Content2.Length >= 16)
                {
                    DA1 = read.Content2[15];
                }
                return OperateResult.CreateSuccessResult( ) ;
            }
    

    上面的代码所示就是,欧姆龙协议的握手信号的处理方式,处理成功就返回为真的Result对象,处理失败就返回假的结果对象。

    注意:握手信号使用的方法必须是ReadFromCoreServerBase方法。

    更复杂的实际开发例子,可以参见项目的源代码,欢迎大家完善开发其他的通讯协议。

    创作不易,感谢打赏


     

  • 相关阅读:
    okhttp进行网络传输文件
    bazel、tensorflow_serving、opencv编译问题
    Linux下设置和查看环境变量(转)
    std::move的实际工作过程
    虚拷贝
    移动构造函数和移动赋值
    while(cin>>word)时的结束方法
    转:windows下命令行工具
    eclipse大括号高亮显示---颜色很淡,改为显眼的颜色
    转: Eclipse 分屏显示同一个文件
  • 原文地址:https://www.cnblogs.com/dathlin/p/8723573.html
Copyright © 2011-2022 走看看