using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Net;
using System.Threading;
using MECF.Framework.RT.Core;
using MECF.Framework.RT.Core.IoProviders.Siemens;
using MECF.Framework.RT.Core.IoProviders.Siemens.IMessage;
using MECF.Framework.RT.Core.IoProviders.Siemens.Transfer;
using MECF.Framework.RT.Core.ThreadLock;
using MECF.Framework.RT.Core.IoProviders.Siemens.Net.StateOne;
namespace MECF.Framework.RT.Core.IoProviders.Siemens.Net.NetworkBase
{
///
/// 支持长连接,短连接两个模式的通用客户端基类 ->
/// Universal client base class that supports long connections and short connections to two modes
///
///
/// 无,请使用继承类实例化,然后进行数据交互。
///
public class NetworkDoubleBase : NetworkBase where TNetMessage : INetMessage, new() where TTransform : IByteTransform, new()
{
#region Constructor
///
/// 默认的无参构造函数 -> Default no-parameter constructor
///
public NetworkDoubleBase( )
{
ByteTransform = new TTransform( ); // 实例化变换类的对象
InteractiveLock = new SimpleHybirdLock( ); // 实例化数据访问锁
connectionId = SoftBasic.GetUniqueStringByGuidAndRandom( ); // 设备的唯一的编号
}
#endregion
#region Private Member
private TTransform byteTransform; // 数据变换的接口
private string ipAddress = "127.0.0.1"; // 连接的IP地址
private int port = 10000; // 端口号
private int connectTimeOut = 10000; // 连接超时时间设置
private int receiveTimeOut = 10000; // 数据接收的超时时间
private bool isPersistentConn = false; // 是否处于长连接的状态
private SimpleHybirdLock InteractiveLock; // 一次正常的交互的互斥锁
private bool IsSocketError = false; // 指示长连接的套接字是否处于错误的状态
private bool isUseSpecifiedSocket = false; // 指示是否使用指定的网络套接字访问数据
private string connectionId = string.Empty; // 当前连接
#endregion
#region Public Member
///
/// 当前客户端的数据变换机制,当你需要从字节数据转换类型数据的时候需要。->
/// The current client's data transformation mechanism is required when you need to convert type data from byte data.
///
///
/// 主要是用来转换数据类型的,下面仅仅演示了2个方法,其他的类型转换,类似处理。
///
///
public TTransform ByteTransform
{
get { return byteTransform; }
set { byteTransform = value; }
}
///
/// 获取或设置连接的超时时间,单位是毫秒 -> Gets or sets the timeout for the connection, in milliseconds
///
///
/// 设置1秒的超时的示例
///
///
///
/// 不适用于异形模式的连接。
///
public int ConnectTimeOut
{
get{return connectTimeOut;}
set { if (value >= 0) connectTimeOut = value; }
}
///
/// 获取或设置接收服务器反馈的时间,如果为负数,则不接收反馈 ->
/// Gets or sets the time to receive server feedback, and if it is a negative number, does not receive feedback
///
///
/// 设置1秒的接收超时的示例
///
///
///
/// 超时的通常原因是服务器端没有配置好,导致访问失败,为了不卡死软件,所以有了这个超时的属性。
///
public int ReceiveTimeOut
{
get { return receiveTimeOut; }
set { receiveTimeOut = value; }
}
///
/// 获取或是设置服务器的IP地址
///
///
/// 最好实在初始化的时候进行指定,当使用短连接的时候,支持动态更改,切换;当使用长连接后,无法动态更改
///
///
/// 以下举例modbus-tcp的短连接及动态更改ip地址的示例
///
///
public virtual string IpAddress
{
get
{
return ipAddress;
}
set
{
if (!string.IsNullOrEmpty( value ))
{
if (!IPAddress.TryParse( value, out IPAddress address ))
{
throw new Exception( StringResources.Language.IpAddresError );
}
ipAddress = value;
}
else
{
ipAddress = "127.0.0.1";
}
}
}
///
/// 获取或设置服务器的端口号
///
///
/// 最好实在初始化的时候进行指定,当使用短连接的时候,支持动态更改,切换;当使用长连接后,无法动态更改
///
///
/// 动态更改请参照IpAddress属性的更改。
///
public virtual int Port
{
get
{
return port;
}
set
{
port = value;
}
}
///
/// 当前连接的唯一ID号,默认为长度20的guid码加随机数组成,方便列表管理,也可以自己指定
///
///
/// Current Connection ID, conclude guid and random data, also, you can spcified
///
public string ConnectionId
{
get { return connectionId; }
set { connectionId = value; }
}
///
/// 当前的异形连接对象,如果设置了异形连接的话
///
///
/// 具体的使用方法请参照Demo项目中的异形modbus实现。
///
public AlienSession AlienSession { get; set; }
#endregion
#region Public Method
///
/// 在读取数据之前可以调用本方法将客户端设置为长连接模式,相当于跳过了ConnectServer的结果验证,对异形客户端无效
///
///
/// 以下的方式演示了另一种长连接的机制
///
///
public void SetPersistentConnection( )
{
isPersistentConn = true;
}
#endregion
#region Connect Close
///
/// 切换短连接模式到长连接模式,后面的每次请求都共享一个通道
///
/// 返回连接结果,如果失败的话(也即IsSuccess为False),包含失败信息
///
/// 简单的连接示例,调用该方法后,连接设备,创建一个长连接的对象,后续的读写操作均公用一个连接对象。
///
/// 如果想知道是否连接成功,请参照下面的代码。
///
///
public OperateResult ConnectServer( )
{
isPersistentConn = true;
OperateResult result = new OperateResult( );
// 重新连接之前,先将旧的数据进行清空
CoreSocket?.Close( );
OperateResult rSocket = CreateSocketAndInitialication( );
if (!rSocket.IsSuccess)
{
IsSocketError = true;
rSocket.Content = null;
result.Message = rSocket.Message;
}
else
{
CoreSocket = rSocket.Content;
result.IsSuccess = true;
}
return result;
}
///
/// 使用指定的套接字创建异形客户端
///
/// 异形客户端对象,查看类型创建的客户端
/// 通常都为成功
///
/// 简单的创建示例。
///
/// 如果想知道是否创建成功。通常都是成功。
///
///
///
/// 不能和之前的长连接和短连接混用,详细参考 Demo程序
///
public OperateResult ConnectServer( AlienSession session )
{
isPersistentConn = true;
isUseSpecifiedSocket = true;
if (session != null)
{
AlienSession?.Socket?.Close( );
if (string.IsNullOrEmpty( ConnectionId ))
{
ConnectionId = session.DTU;
}
if (ConnectionId == session.DTU)
{
CoreSocket = session.Socket;
IsSocketError = false;
AlienSession = session;
return InitializationOnConnect( session.Socket );
}
else
{
IsSocketError = true;
return new OperateResult( );
}
}
else
{
IsSocketError = true;
return new OperateResult( );
}
}
///
/// 在长连接模式下,断开服务器的连接,并切换到短连接模式
///
/// 关闭连接,不需要查看IsSuccess属性查看
///
/// 直接关闭连接即可,基本上是不需要进行成功的判定
///
///
public OperateResult ConnectClose( )
{
OperateResult result = new OperateResult( );
isPersistentConn = false;
InteractiveLock.Enter( );
// 额外操作
result = ExtraOnDisconnect( CoreSocket );
// 关闭信息
CoreSocket?.Close( );
CoreSocket = null;
InteractiveLock.Leave( );
return result;
}
#endregion
#region Initialization And Extra
///
/// 连接上服务器后需要进行的初始化操作
///
/// 网络套接字
/// 是否初始化成功,依据具体的协议进行重写
///
/// 有些协议不需要握手信号,比如三菱的MC协议,Modbus协议,西门子和欧姆龙就存在握手信息,此处的例子是继承本类后重写的西门子的协议示例
///
///
protected virtual OperateResult InitializationOnConnect( Socket socket )
{
return OperateResult.CreateSuccessResult( );
}
///
/// 在将要和服务器进行断开的情况下额外的操作,需要根据对应协议进行重写
///
/// 网络套接字
///
/// 目前暂无相关的示例,组件支持的协议都不用实现这个方法。
///
/// 当断开连接时额外的操作结果
protected virtual OperateResult ExtraOnDisconnect( Socket socket )
{
return OperateResult.CreateSuccessResult( );
}
#endregion
#region Core Communication
/***************************************************************************************
*
* 主要的数据交互分为4步
* 1. 连接服务器,或是获取到旧的使用的网络信息
* 2. 发送数据信息
* 3. 接收反馈的数据信息
* 4. 关闭网络连接,如果是短连接的话
*
**************************************************************************************/
///
/// 获取本次操作的可用的网络套接字
///
/// 是否成功,如果成功,使用这个套接字
private OperateResult GetAvailableSocket( )
{
if (isPersistentConn)
{
// 如果是异形模式
if (isUseSpecifiedSocket)
{
if(IsSocketError)
{
return new OperateResult( StringResources.Language.ConnectionIsNotAvailable );
}
else
{
return OperateResult.CreateSuccessResult( CoreSocket );
}
}
else
{
// 长连接模式
if (IsSocketError || CoreSocket == null)
{
OperateResult connect = ConnectServer( );
if (!connect.IsSuccess)
{
IsSocketError = true;
return OperateResult.CreateFailedResult( connect );
}
else
{
IsSocketError = false;
return OperateResult.CreateSuccessResult( CoreSocket );
}
}
else
{
return OperateResult.CreateSuccessResult( CoreSocket );
}
}
}
else
{
// 短连接模式
return CreateSocketAndInitialication( );
}
}
///
/// 连接并初始化网络套接字
///
/// 带有socket的结果对象
private OperateResult CreateSocketAndInitialication( )
{
OperateResult result = CreateSocketAndConnect( new IPEndPoint( IPAddress.Parse( ipAddress ), port ), connectTimeOut );
if (result.IsSuccess)
{
// 初始化
OperateResult initi = InitializationOnConnect( result.Content );
if (!initi.IsSuccess)
{
result.Content?.Close( );
result.IsSuccess = initi.IsSuccess;
result.CopyErrorFromOther( initi );
}
}
return result;
}
///
/// 指示如何创建一个新的消息对象
///
/// 消息对象
protected virtual INetMessage GetNewNetMessage()
{
return null;
}
///
/// 在其他指定的套接字上,使用报文来通讯,传入需要发送的消息,返回一条完整的数据指令
///
/// 指定的套接字
/// 发送的完整的报文信息
///
/// 无锁的基于套接字直接进行叠加协议的操作。
///
///
/// 假设你有一个自己的socket连接了设备,本组件可以直接基于该socket实现modbus读取,三菱读取,西门子读取等等操作,前提是该服务器支持多协议,虽然这个需求听上去比较变态,但本组件支持这样的操作。
///
///
/// 接收的完整的报文信息
public virtual OperateResult ReadFromCoreServer( Socket socket, byte[] send )
{
TNetMessage netMessage = new TNetMessage( );
netMessage.SendBytes = send;
// send
OperateResult sendResult = Send( socket, send );
if (!sendResult.IsSuccess)
{
socket?.Close( );
return OperateResult.CreateFailedResult( sendResult );
}
if (receiveTimeOut < 0) return OperateResult.CreateSuccessResult( new byte[0] );
// receive msg
OperateResult resultReceive = ReceiveByMessage( socket, receiveTimeOut, netMessage );
if (!resultReceive.IsSuccess)
{
socket?.Close( );
return new OperateResult( StringResources.Language.ReceiveDataTimeout + receiveTimeOut );
}
// check
if (!netMessage.CheckHeadBytesLegal( Token.ToByteArray( ) ))
{
socket?.Close( );
return new OperateResult( StringResources.Language.CommandHeadCodeCheckFailed );
}
// Success
return OperateResult.CreateSuccessResult( resultReceive.Content );
}
///
/// 使用底层的数据报文来通讯,传入需要发送的消息,返回一条完整的数据指令
///
/// 发送的完整的报文信息
/// 接收的完整的报文信息
///
/// 本方法用于实现本组件还未实现的一些报文功能,例如有些modbus服务器会有一些特殊的功能码支持,需要收发特殊的报文,详细请看示例
///
///
/// 此处举例有个modbus服务器,有个特殊的功能码0x09,后面携带子数据0x01即可,发送字节为 0x00 0x00 0x00 0x00 0x00 0x03 0x01 0x09 0x01
///
///
public OperateResult ReadFromCoreServer( byte[] send )
{
var result = new OperateResult( );
InteractiveLock.Enter( );
// 获取有用的网络通道,如果没有,就建立新的连接
OperateResult resultSocket = GetAvailableSocket( );
if (!resultSocket.IsSuccess)
{
IsSocketError = true;
if (AlienSession != null) AlienSession.IsStatusOk = false;
InteractiveLock.Leave( );
result.CopyErrorFromOther( resultSocket );
return result;
}
OperateResult read = ReadFromCoreServer( resultSocket.Content, send );
if (read.IsSuccess)
{
IsSocketError = false;
result.IsSuccess = read.IsSuccess;
result.Content = read.Content;
result.Message = StringResources.Language.SuccessText;
}
else
{
IsSocketError = true;
if (AlienSession != null) AlienSession.IsStatusOk = false;
result.CopyErrorFromOther( read );
}
InteractiveLock.Leave( );
if (!isPersistentConn) resultSocket.Content?.Close( );
return result;
}
#endregion
#region Object Override
///
/// 返回表示当前对象的字符串
///
/// 字符串信息
public override string ToString( )
{
return $"NetworkDoubleBase<{typeof( TNetMessage )}, {typeof( TTransform )}>";
}
#endregion
}
}