using System.Collections.Concurrent;
|
using System.Linq;
|
using System.Net.NetworkInformation;
|
using HslCommunication;
|
using HslCommunication.Profinet.Siemens;
|
using HslCommunication.Reflection;
|
using Microsoft.Extensions.Logging;
|
using WIDESEAWCS_S7Simulator.Core.Entities;
|
using WIDESEAWCS_S7Simulator.Core.Enums;
|
using WIDESEAWCS_S7Simulator.Core.Interfaces;
|
using WIDESEAWCS_S7Simulator.Core.Memory;
|
|
namespace WIDESEAWCS_S7Simulator.Core.Server
|
{
|
/// <summary>
|
/// S7服务器实例实现
|
/// 使用HSL Communication库实现S7 PLC仿真服务器
|
/// </summary>
|
public class S7ServerInstance : IS7ServerInstance
|
{
|
private readonly ILogger<S7ServerInstance> _logger;
|
private readonly object _lock = new();
|
private SiemensS7Server? _server;
|
private bool _disposed;
|
|
/// <inheritdoc/>
|
public InstanceConfig Config { get; }
|
|
/// <inheritdoc/>
|
public InstanceState State { get; private set; }
|
|
/// <inheritdoc/>
|
public IMemoryStore MemoryStore { get; }
|
|
/// <summary>
|
/// 客户端连接追踪
|
/// </summary>
|
private readonly ConcurrentDictionary<string, S7ClientConnection> _clients = new();
|
|
/// <summary>
|
/// 连接监控定时器
|
/// </summary>
|
private System.Threading.Timer? _connectionMonitorTimer;
|
|
/// <summary>
|
/// 构造函数
|
/// </summary>
|
/// <param name="config">实例配置</param>
|
/// <param name="logger">日志记录器</param>
|
public S7ServerInstance(InstanceConfig config, ILogger<S7ServerInstance> logger)
|
{
|
Config = config ?? throw new ArgumentNullException(nameof(config));
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
|
// 初始化内存存储
|
MemoryStore = new MemoryStore(config.MemoryConfig);
|
|
// 初始化状态
|
State = new InstanceState
|
{
|
InstanceId = config.Id,
|
Status = InstanceStatus.Stopped,
|
ClientCount = 0,
|
TotalRequests = 0
|
};
|
|
_logger.LogInformation("S7服务器实例 {InstanceId} ({InstanceName}) 已创建,PLC类型: {PLCType}, 端口: {Port}",
|
config.Id, config.Name, config.PLCType, config.Port);
|
}
|
|
/// <inheritdoc/>
|
public bool Start()
|
{
|
lock (_lock)
|
{
|
if (_disposed)
|
{
|
_logger.LogError("无法启动已释放的实例 {InstanceId}", Config.Id);
|
return false;
|
}
|
|
if (State.Status == InstanceStatus.Running)
|
{
|
_logger.LogWarning("实例 {InstanceId} 已在运行中", Config.Id);
|
return true;
|
}
|
|
try
|
{
|
// 创建S7服务器
|
_server = new SiemensS7Server();
|
|
// 设置激活码
|
if (!string.IsNullOrWhiteSpace(Config.ActivationKey))
|
{
|
HslCommunication.Authorization.SetAuthorizationCode(Config.ActivationKey);
|
_logger.LogDebug("已设置激活码");
|
}
|
|
// 初始化DB块(根据配置)
|
InitializeDbBlocks();
|
|
// 从MemoryStore同步初始数据到服务器
|
SynchronizeMemoryToServer();
|
|
// 启动服务器
|
try
|
{
|
_server.ServerStart(Config.Port);
|
}
|
catch (Exception ex)
|
{
|
_logger.LogError(ex, "启动S7服务器失败");
|
State.Status = InstanceStatus.Error;
|
State.ErrorMessage = ex.Message;
|
return false;
|
}
|
|
// 更新状态
|
State.Status = InstanceStatus.Running;
|
State.StartTime = DateTime.Now;
|
State.ErrorMessage = null;
|
|
// 启动连接监控定时器(每5秒检查一次)
|
_connectionMonitorTimer = new System.Threading.Timer(
|
_ => MonitorConnections(),
|
null,
|
TimeSpan.FromSeconds(5),
|
TimeSpan.FromSeconds(5));
|
|
_logger.LogInformation("S7服务器实例 {InstanceId} 已成功启动,监听端口: {Port}", Config.Id, Config.Port);
|
return true;
|
}
|
catch (Exception ex)
|
{
|
_logger.LogError(ex, "启动S7服务器实例 {InstanceId} 时发生异常", Config.Id);
|
State.Status = InstanceStatus.Error;
|
State.ErrorMessage = ex.Message;
|
return false;
|
}
|
}
|
}
|
|
/// <inheritdoc/>
|
public void Stop()
|
{
|
lock (_lock)
|
{
|
if (_disposed || State.Status == InstanceStatus.Stopped)
|
{
|
return;
|
}
|
|
try
|
{
|
// 停止连接监控定时器
|
_connectionMonitorTimer?.Dispose();
|
_connectionMonitorTimer = null;
|
|
if (_server != null)
|
{
|
// 停止前同步服务器数据到MemoryStore
|
SynchronizeServerToMemory();
|
|
_server.ServerClose();
|
_server = null;
|
}
|
|
// 清空客户端连接
|
_clients.Clear();
|
|
// 更新状态
|
State.Status = InstanceStatus.Stopped;
|
State.ClientCount = 0;
|
State.StartTime = null;
|
|
_logger.LogInformation("S7服务器实例 {InstanceId} 已停止", Config.Id);
|
}
|
catch (Exception ex)
|
{
|
_logger.LogError(ex, "停止S7服务器实例 {InstanceId} 时发生异常", Config.Id);
|
State.Status = InstanceStatus.Error;
|
State.ErrorMessage = ex.Message;
|
}
|
}
|
}
|
|
/// <inheritdoc/>
|
public bool Restart()
|
{
|
Stop();
|
return Start();
|
}
|
|
/// <inheritdoc/>
|
public InstanceState GetState()
|
{
|
lock (_lock)
|
{
|
// 更新客户端连接数
|
State.ClientCount = _clients.Count;
|
State.Clients = _clients.Values.ToList();
|
|
// 返回状态副本,包含配置信息
|
return new InstanceState
|
{
|
InstanceId = State.InstanceId,
|
Status = State.Status,
|
ClientCount = State.ClientCount,
|
TotalRequests = State.TotalRequests,
|
StartTime = State.StartTime,
|
LastActivityTime = State.LastActivityTime,
|
Clients = new List<S7ClientConnection>(State.Clients),
|
ErrorMessage = State.ErrorMessage
|
};
|
}
|
}
|
|
/// <summary>
|
/// 获取完整状态(包含配置信息)
|
/// </summary>
|
public object GetFullState()
|
{
|
lock (_lock)
|
{
|
// 更新客户端连接数
|
State.ClientCount = _clients.Count;
|
State.Clients = _clients.Values.ToList();
|
|
return new
|
{
|
InstanceId = State.InstanceId,
|
Name = Config.Name,
|
PLCType = Config.PLCType.ToString(),
|
Port = Config.Port,
|
Status = State.Status.ToString(),
|
ClientCount = State.ClientCount,
|
TotalRequests = State.TotalRequests,
|
StartTime = State.StartTime,
|
LastActivityTime = State.LastActivityTime,
|
Clients = State.Clients,
|
ErrorMessage = State.ErrorMessage
|
};
|
}
|
}
|
|
/// <inheritdoc/>
|
public void ClearMemory()
|
{
|
lock (_lock)
|
{
|
MemoryStore.Clear();
|
|
// 同时清空服务器内部数据
|
if (_server != null && State.Status == InstanceStatus.Running)
|
{
|
// 清空各内存区域
|
for (int i = 0; i < 100; i++)
|
{
|
_server.Write($"M{i}", (byte)0);
|
_server.Write($"I{i}", (byte)0);
|
_server.Write($"Q{i}", (byte)0);
|
}
|
|
// 清空DB块
|
for (ushort db = 1; db <= Config.MemoryConfig.DBBlockCount; db++)
|
{
|
for (int i = 0; i < 10; i++)
|
{
|
_server.Write($"DB{db}.DBD{i}", (byte)0);
|
}
|
}
|
}
|
|
_logger.LogInformation("实例 {InstanceId} 内存已清空", Config.Id);
|
}
|
}
|
|
/// <inheritdoc/>
|
public Dictionary<string, byte[]> ExportMemory()
|
{
|
lock (_lock)
|
{
|
// 先同步服务器数据到MemoryStore
|
if (_server != null && State.Status == InstanceStatus.Running)
|
{
|
SynchronizeServerToMemory();
|
}
|
return MemoryStore.Export();
|
}
|
}
|
|
/// <inheritdoc/>
|
public void ImportMemory(Dictionary<string, byte[]> data)
|
{
|
lock (_lock)
|
{
|
MemoryStore.Import(data);
|
|
// 同步到服务器
|
if (_server != null && State.Status == InstanceStatus.Running)
|
{
|
SynchronizeMemoryToServer();
|
}
|
|
_logger.LogInformation("实例 {InstanceId} 内存数据已导入", Config.Id);
|
}
|
}
|
|
/// <summary>
|
/// 初始化DB块
|
/// </summary>
|
private void InitializeDbBlocks()
|
{
|
if (_server == null)
|
return;
|
|
try
|
{
|
// 根据配置添加DB块
|
for (ushort i = 1; i <= Config.MemoryConfig.DBBlockCount; i++)
|
{
|
_server.AddDbBlock(i, Config.MemoryConfig.DBBlockSize);
|
_logger.LogDebug("已添加DB块: DB{DbNumber}, 大小: {Size}", i, Config.MemoryConfig.DBBlockSize);
|
}
|
}
|
catch (Exception ex)
|
{
|
_logger.LogWarning(ex, "初始化DB块时发生警告");
|
}
|
}
|
|
/// <summary>
|
/// 从MemoryStore同步数据到服务器
|
/// </summary>
|
private void SynchronizeMemoryToServer()
|
{
|
if (_server == null)
|
return;
|
|
try
|
{
|
var data = MemoryStore.Export();
|
|
// 同步M区
|
if (data.ContainsKey("M"))
|
{
|
var mBytes = data["M"];
|
for (int i = 0; i < Math.Min(mBytes.Length, Config.MemoryConfig.MRegionSize); i++)
|
{
|
_server.Write($"M{i}", mBytes[i]);
|
}
|
}
|
|
// 同步I区
|
if (data.ContainsKey("I"))
|
{
|
var iBytes = data["I"];
|
for (int i = 0; i < Math.Min(iBytes.Length, Config.MemoryConfig.IRegionSize); i++)
|
{
|
_server.Write($"I{i}", iBytes[i]);
|
}
|
}
|
|
// 同步Q区
|
if (data.ContainsKey("Q"))
|
{
|
var qBytes = data["Q"];
|
for (int i = 0; i < Math.Min(qBytes.Length, Config.MemoryConfig.QRegionSize); i++)
|
{
|
_server.Write($"Q{i}", qBytes[i]);
|
}
|
}
|
|
// 同步DB区
|
if (data.ContainsKey("DB"))
|
{
|
var dbBytes = data["DB"];
|
int offset = 0;
|
for (ushort db = 1; db <= Config.MemoryConfig.DBBlockCount; db++)
|
{
|
int blockSize = Math.Min(Config.MemoryConfig.DBBlockSize, dbBytes.Length - offset);
|
for (int i = 0; i < blockSize; i++)
|
{
|
_server.Write($"DB{db}.DBD{i}", dbBytes[offset + i]);
|
}
|
offset += Config.MemoryConfig.DBBlockSize;
|
}
|
}
|
|
_logger.LogDebug("已将MemoryStore数据同步到S7服务器");
|
}
|
catch (Exception ex)
|
{
|
_logger.LogWarning(ex, "同步数据到服务器时发生警告");
|
}
|
}
|
|
/// <summary>
|
/// 从服务器同步数据到MemoryStore
|
/// </summary>
|
private void SynchronizeServerToMemory()
|
{
|
if (_server == null)
|
return;
|
|
try
|
{
|
var data = new Dictionary<string, byte[]>();
|
|
// 读取M区
|
var mResult = _server.Read("M0", (ushort)Config.MemoryConfig.MRegionSize);
|
if (mResult.IsSuccess)
|
{
|
data["M"] = mResult.Content;
|
}
|
|
// 读取I区
|
var iResult = _server.Read("I0", (ushort)Config.MemoryConfig.IRegionSize);
|
if (iResult.IsSuccess)
|
{
|
data["I"] = iResult.Content;
|
}
|
|
// 读取Q区
|
var qResult = _server.Read("Q0", (ushort)Config.MemoryConfig.QRegionSize);
|
if (qResult.IsSuccess)
|
{
|
data["Q"] = qResult.Content;
|
}
|
|
// 读取DB区
|
var dbBytes = new List<byte>();
|
for (ushort db = 1; db <= Config.MemoryConfig.DBBlockCount; db++)
|
{
|
var dbResult = _server.Read($"DB{db}.DBD0", (ushort)Config.MemoryConfig.DBBlockSize);
|
if (dbResult.IsSuccess)
|
{
|
dbBytes.AddRange(dbResult.Content);
|
}
|
}
|
data["DB"] = dbBytes.ToArray();
|
|
// 导入到MemoryStore
|
MemoryStore.Import(data);
|
|
_logger.LogDebug("已将S7服务器数据同步到MemoryStore");
|
}
|
catch (Exception ex)
|
{
|
_logger.LogWarning(ex, "从服务器同步数据时发生警告");
|
}
|
}
|
|
/// <summary>
|
/// 监控客户端连接(通过检查TCP连接)
|
/// </summary>
|
private void MonitorConnections()
|
{
|
try
|
{
|
if (_server == null || State.Status != InstanceStatus.Running)
|
return;
|
|
var currentConnections = new HashSet<string>();
|
var now = DateTime.Now;
|
|
// 获取当前所有TCP连接
|
var tcpConnections = IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections();
|
|
// 筛选出监听端口的已建立连接
|
var connectionsOnPort = tcpConnections.Where(c =>
|
c.LocalEndPoint.Port == Config.Port &&
|
c.State == TcpState.Established);
|
|
foreach (var connection in connectionsOnPort)
|
{
|
var endPoint = connection.RemoteEndPoint.ToString();
|
currentConnections.Add(endPoint);
|
|
// 检查是否是新连接
|
if (!_clients.ContainsKey(endPoint))
|
{
|
var clientConnection = new S7ClientConnection
|
{
|
ClientId = endPoint,
|
RemoteEndPoint = endPoint,
|
ConnectedTime = now,
|
LastActivityTime = now
|
};
|
|
_clients[endPoint] = clientConnection;
|
_logger.LogInformation("实例 {InstanceId} 检测到新客户端连接: {EndPoint}, 当前连接数: {Count}",
|
Config.Id, endPoint, _clients.Count);
|
}
|
else
|
{
|
// 更新活动时间
|
_clients[endPoint].LastActivityTime = now;
|
}
|
}
|
|
// 移除断开的连接
|
var disconnectedClients = _clients.Keys.Where(k => !currentConnections.Contains(k)).ToList();
|
foreach (var disconnected in disconnectedClients)
|
{
|
if (_clients.TryRemove(disconnected, out var client))
|
{
|
_logger.LogInformation("实例 {InstanceId} 客户端断开: {EndPoint}, 当前连接数: {Count}",
|
Config.Id, disconnected, _clients.Count);
|
}
|
}
|
|
// 更新状态
|
State.ClientCount = _clients.Count;
|
State.LastActivityTime = now;
|
}
|
catch (Exception ex)
|
{
|
_logger.LogWarning(ex, "监控客户端连接时发生错误");
|
}
|
}
|
|
/// <summary>
|
/// 增加请求计数并更新活动时间
|
/// </summary>
|
private void IncrementRequestCount()
|
{
|
State.TotalRequests++;
|
State.LastActivityTime = DateTime.Now;
|
}
|
|
/// <summary>
|
/// 释放资源
|
/// </summary>
|
public void Dispose()
|
{
|
Dispose(true);
|
GC.SuppressFinalize(this);
|
}
|
|
/// <summary>
|
/// 释放资源
|
/// </summary>
|
/// <param name="disposing">是否正在释放托管资源</param>
|
protected virtual void Dispose(bool disposing)
|
{
|
if (!_disposed)
|
{
|
if (disposing)
|
{
|
Stop();
|
MemoryStore?.Dispose();
|
}
|
_disposed = true;
|
}
|
}
|
}
|
}
|