HslCommunication库的二次协议扩展,适配第三方通讯协议开发,基础框架支持长短连接模式

Posted dathlin

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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方法。

 

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

 

创作不易,感谢打赏


 

 

 

以上是关于HslCommunication库的二次协议扩展,适配第三方通讯协议开发,基础框架支持长短连接模式的主要内容,如果未能解决你的问题,请参考以下文章

还在用ABAP进行SAP产品的二次开发?来了解下这种全新的二次开发理念吧

iOS(Swift) 基于 Moya 的二次封装

性能测试基础---jmeter二次开发

Python网络爬虫与信息提取——HTTP协议及Requests库的方法

水利遥测终端下的二次供水泵房远程监管系统

华为Atlas的二次创业