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库的二次协议扩展,适配第三方通讯协议开发,基础框架支持长短连接模式的更多相关文章
- PPI协议详解 ppi通讯协议 ppi通信协议 vb与ppi协议通讯
转自:http://blog.csdn.net/vbvcde/article/details/7660497 我们提供 PPI协议的官方文档,协议更新时间为2005年,下面是我们根据文档解析的PPI读 ...
- Htpp通讯协议详解
转自:http://blog.csdn.net/gueter/archive/2007/03/08/1524447.aspx Author :Jeffrey 引言 HTTP是一个属于应用层的面向对象的 ...
- MODBUS-RTU通讯协议简介
MODBUS-RTU通讯协议简介 什么是MODBUS? MODBUS 是MODICON公司最先倡导的一种软的通讯规约,经过大多数公司 的实际应用,逐渐被认可,成为一种标准的通讯规约,只要按照这种规 ...
- 【读书笔记】iOS-防止通讯协议被轻易破解的方法
开发者可以选择类似Protobuf之类的二进制通讯协议或者自己实现通讯协议,对于传输的内容进行一定程度的加密,以增加黑客破解协议的难度. 参考资料: <iOS开发进阶> --唐巧
- java基础55 UDP通讯协议和TCP通讯协议
本文知识点(目录): 1.概述 2.UDP通讯协议 3.TCPP通讯协议 1.概述 1.在java中网络通讯作为Socket(插座)通讯,要求两台都必须安装socket. 2.不同的 ...
- hdfs 3种 通讯协议
http://hadoop.apache.org/docs/r1.0.4/cn/hdfs_design.html 通讯协议 所有的HDFS通讯协议都是建立在TCP/IP协议之上.客户端通过一个可配置的 ...
- 网络编程介绍,C/S 架构,网络通讯协议,osi七层
网络编程: 什么是网络编程: 网络通常指的是计算机中的互联网,是由多台计算机通过网线或其他媒介相互链接组成的 编写基于网络的应用程序的过程序称之为网络编程 为什么要学习网络编程: 我们已经知道计算机, ...
- 微博CacheService架构浅析 对底层协议进行适配
https://mp.weixin.qq.com/s/wPR0j2bmHBF6z0ZjTlz_4A 麦俊生 InfoQ 2014-04-21 微博作为国内最大的社交媒体网站之一,每天承载着亿万用户的服 ...
- ReactiveCocoa源码解析(四) Signal中的静态属性静态方法以及面向协议扩展
上篇博客我们聊了Signal的几种状态.Signal与Observer的关联方式以及Signal是如何向关联的Observer发送事件的.本篇博客继续上篇博客的内容,来聊一下Signal类中静态的ne ...
随机推荐
- ubuntu下 gedit中文乱码
Gedit 3.x 版本设置 (适用于Ubuntu 11.10及以后) 命令方式 gsettings set org.gnome.gedit.preferences.encodings auto-de ...
- SVN一直提示需要clean up
无论到那一级都提示clean up, 这是陷入clean up 死循环的结果. 解决办法: 使用任何一款可以连sqllit 的数据库管理软件例如(Navicat Premium),连入 项目跟目录/. ...
- params
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Ch06 ...
- [小问题笔记(四)] Enum枚举类型转换为DataTable( C# )
枚举: public enum ProductType { 小产品=, 大产品, 超大产品 } 转换方法: /// <summary> /// 枚举类型转化为DataTable /// & ...
- LA 5135 井下矿工(点—双连通分量模板题)
https://vjudge.net/problem/UVALive-5135 题意:在一个无向图上选择尽量少的点涂黑,使得任意删除一个点后,每个连通分量至少有一个黑点. 思路: 首先dfs遍历求出割 ...
- 自定义ajax
// 动态添加script获取里面的数据,,可实现跨域,不跨的当然也可以 getFile:function(params){ try{ //创建script标签 var cbName=params.c ...
- ubuntu mac terminal install software
http-server // ubuntu sudo npm install http-server -g npm node.js yarn
- 讲一下numpy的矩阵特征值分解与奇异值分解
1.特征值分解 主要还是调包: from numpy.linalg import eig 特征值分解: A = P*B*PT 当然也可以写成 A = QT*B*Q 其中B为对角元为A的特征值的对 ...
- webstorm的安装、激活码、更换主题颜色的修改、汉化
一.安装 1.解压webstorm11zh.rar,双击.exe文件,下一步安装,在安装结束前会提示输入激活码,这个从网上随便找一个可用的即可. 二.更换主题颜色: 1.先从网上找一个喜欢的主题颜色, ...
- [eclipse]Syntax error on tokens, delete these tokens问题解决
错误:Syntax error on tokens, delete these tokens 出现这样的错误一般是括号.中英文字符.中英文标点.代码前面的空格,尤其是复制粘贴的代码,去掉即可. 如下图 ...