Compare commits

..
1 Commits
Author SHA1 Message Date
kislovdm dc92c80b43 Исправлена нагрузка на проц
.NET Core Desktop / build (Debug) (push) Has been cancelled
2026-06-15 09:23:49 +03:00
6 changed files with 34 additions and 210 deletions
+1 -1
View File
@@ -1 +1 @@
1.0.27
1.0.23
-22
View File
@@ -19,22 +19,8 @@ namespace MonsterMonitor.Models
public bool SavePassword { get; set; } = false;
public int RemotePort { get; set; } = 3328;
public int LocalPort { get; set; } = 7829;
// Устарело: раньше использовалось как порог молчания heartbeat в секундах,
// но код брал Math.Max(5, MaxPingFailures), поэтому значение <5 ничего не меняло.
// Оставлено для совместимости со старыми settings.ini, см. HeartbeatSilenceSec.
public int MaxPingFailures { get; set; } = 3;
public int ReconnectTimeoutSec { get; set; } = 45;
// Сколько секунд молчания heartbeat считать подозрительным. Само по себе
// молчание больше НЕ рвёт соединение - оно лишь повод сделать e2e-проверку.
// Большая выгрузка легко забивает канал на десятки секунд.
public int HeartbeatSilenceSec { get; set; } = 60;
// e2e-проверка проброса: раз в сколько секунд, таймаут и сколько подряд
// неудач требуется, чтобы переподключаться.
public int ProbeIntervalSec { get; set; } = 30;
public int ProbeTimeoutSec { get; set; } = 10;
public int ProbeFailuresBeforeReconnect { get; set; } = 2;
public string Proxy { get; set; } = string.Empty;
public string SsProcessPath { get; set; } = Path.Combine("App_Data", "ss", "ss.exe");
public string SsArguments { get; set; } = string.Empty;
@@ -178,10 +164,6 @@ namespace MonsterMonitor.Models
result.LocalPort = GetInt(map, nameof(LocalPort), result.LocalPort);
result.MaxPingFailures = GetInt(map, nameof(MaxPingFailures), result.MaxPingFailures);
result.ReconnectTimeoutSec = GetInt(map, nameof(ReconnectTimeoutSec), result.ReconnectTimeoutSec);
result.HeartbeatSilenceSec = GetInt(map, nameof(HeartbeatSilenceSec), result.HeartbeatSilenceSec);
result.ProbeIntervalSec = GetInt(map, nameof(ProbeIntervalSec), result.ProbeIntervalSec);
result.ProbeTimeoutSec = GetInt(map, nameof(ProbeTimeoutSec), result.ProbeTimeoutSec);
result.ProbeFailuresBeforeReconnect = GetInt(map, nameof(ProbeFailuresBeforeReconnect), result.ProbeFailuresBeforeReconnect);
result.Proxy = Get(map, nameof(Proxy), result.Proxy);
result.SsProcessPath = Get(map, nameof(SsProcessPath), result.SsProcessPath);
result.SsArguments = Get(map, nameof(SsArguments), result.SsArguments);
@@ -211,10 +193,6 @@ namespace MonsterMonitor.Models
$"{nameof(LocalPort)}={LocalPort}",
$"{nameof(MaxPingFailures)}={MaxPingFailures}",
$"{nameof(ReconnectTimeoutSec)}={ReconnectTimeoutSec}",
$"{nameof(HeartbeatSilenceSec)}={HeartbeatSilenceSec}",
$"{nameof(ProbeIntervalSec)}={ProbeIntervalSec}",
$"{nameof(ProbeTimeoutSec)}={ProbeTimeoutSec}",
$"{nameof(ProbeFailuresBeforeReconnect)}={ProbeFailuresBeforeReconnect}",
$"{nameof(Proxy)}={Proxy}",
$"{nameof(SsProcessPath)}={SsProcessPath}",
$"{nameof(SsArguments)}={SsArguments}"
+4 -4
View File
@@ -6,10 +6,10 @@
<LangVersion>7.3</LangVersion>
<RootNamespace>MonsterMonitor</RootNamespace>
<AssemblyName>devenv</AssemblyName>
<AssemblyVersion>1.0.27</AssemblyVersion>
<Version>1.0.27</Version>
<FileVersion>1.0.27</FileVersion>
<InformationalVersion>1.0.27</InformationalVersion>
<AssemblyVersion>1.0.23</AssemblyVersion>
<Version>1.0.23</Version>
<FileVersion>1.0.23</FileVersion>
<InformationalVersion>1.0.23</InformationalVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Costura.Fody" Version="6.0.0">
+7 -171
View File
@@ -1,7 +1,5 @@
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using MonsterMonitor.Models;
@@ -16,13 +14,6 @@ namespace MonsterMonitor.Services
private readonly object _sync = new object();
private SshClient _client;
private ForwardedPortRemote _forwardedPort;
// Локальный проброс на серверный RemotePort: даёт замкнутый e2e-маршрут
// local -> SSH -> сервер:RemotePort -> обратный проброс -> наш LocalPort.
private ForwardedPortLocal _probePort;
private int _probeLocalPort;
private int _probeFailures;
private DateTime _lastProbeUtc = DateTime.MinValue;
private DateTime _lastGoodProbeUtc = DateTime.MinValue;
private SshCommand _heartbeatCommand;
private CancellationTokenSource _heartbeatReadTokenSource;
private Task _heartbeatReadTask;
@@ -72,11 +63,7 @@ namespace MonsterMonitor.Services
_forwardedPort.Exception += ForwardedPortOnException;
_client.AddForwardedPort(_forwardedPort);
_forwardedPort.Start();
StartProbePortNoLock();
StartRemoteHeartbeatNoLock();
_probeFailures = 0;
_lastProbeUtc = DateTime.UtcNow;
_lastGoodProbeUtc = DateTime.UtcNow;
_log.Info(
$"SSH подключен. Туннель remote:{_settings.RemotePort} -> local:{_settings.LocalPort}");
@@ -107,63 +94,20 @@ namespace MonsterMonitor.Services
{
try
{
if (!IsConnected())
{
_log.Warn("SSH-сессия не подключена. Переподключаю.");
await Reconnect().ConfigureAwait(false);
}
else
{
var now = DateTime.UtcNow;
var silenceThresholdSec = Math.Max(5, _settings.HeartbeatSilenceSec);
var probeIntervalSec = Math.Max(5, _settings.ProbeIntervalSec);
var silenceThresholdSec = Math.Max(
5,
_settings.MaxPingFailures);
var lastHeartbeat = _lastHeartbeatUtc;
var heartbeatSilentSec = lastHeartbeat == DateTime.MinValue
? double.MaxValue
: (now - lastHeartbeat).TotalSeconds;
var heartbeatSuspicious = heartbeatSilentSec > silenceThresholdSec;
var isHeartbeatAlive = lastHeartbeat != DateTime.MinValue &&
(DateTime.UtcNow - lastHeartbeat).TotalSeconds <= silenceThresholdSec;
// Heartbeat живёт в том же SSH-соединении, что и полезный трафик,
// поэтому под большой выгрузкой он опаздывает - это НЕ повод рвать
// туннель. Решение принимает только e2e-проверка проброса.
var needProbe = heartbeatSuspicious ||
(now - _lastProbeUtc).TotalSeconds >= probeIntervalSec;
if (needProbe)
if (!IsConnected() || !isHeartbeatAlive)
{
_lastProbeUtc = now;
var ok = await ProbeForwardAsync().ConfigureAwait(false);
if (ok)
{
if (_probeFailures > 0)
{
_log.Info("e2e-проверка проброса снова проходит.");
}
_probeFailures = 0;
_lastGoodProbeUtc = DateTime.UtcNow;
}
else
{
_probeFailures++;
_log.Warn($"e2e-проверка проброса не прошла ({_probeFailures} подряд).");
}
}
if (heartbeatSuspicious && _probeFailures == 0)
{
_log.Debug(
$"Heartbeat молчит {heartbeatSilentSec:F0}с, но проброс работает - вероятно идёт передача. Не переподключаю.");
}
if (_probeFailures >= Math.Max(1, _settings.ProbeFailuresBeforeReconnect))
{
_log.Warn("Проброс портов не пропускает трафик. Переподключаю SSH.");
_log.Warn("Нет живого вывода heartbeat-команды на удаленном сервере. Переподключаю SSH.");
await Reconnect().ConfigureAwait(false);
}
}
}
catch (Exception ex)
{
_log.Warn("Исключение мониторинга heartbeat: " + ex.Message);
@@ -239,7 +183,6 @@ namespace MonsterMonitor.Services
private void DisconnectCore()
{
StopRemoteHeartbeatNoLock();
StopProbePortNoLock();
try
{
@@ -276,113 +219,6 @@ namespace MonsterMonitor.Services
}
}
/// <summary>
/// Поднимает локальный проброс на серверный RemotePort. Подключение к нему
/// проходит весь маршрут целиком: наш процесс -> SSH -> слушатель обратного
/// проброса на сервере -> SSH обратно -> наш LocalPort (ss.exe).
/// Успешный TCP-connect означает, что проброс реально пропускает трафик,
/// а не просто "сессия жива".
/// </summary>
private void StartProbePortNoLock()
{
StopProbePortNoLock();
try
{
_probeLocalPort = FindFreeLocalPort();
_probePort = new ForwardedPortLocal(
"127.0.0.1",
(uint)_probeLocalPort,
"127.0.0.1",
(uint)_settings.RemotePort);
_client.AddForwardedPort(_probePort);
_probePort.Start();
_log.Info($"e2e-проверка проброса включена (127.0.0.1:{_probeLocalPort}).");
}
catch (Exception ex)
{
_probePort = null;
_log.Warn("Не удалось поднять порт e2e-проверки: " + ex.Message);
}
}
private void StopProbePortNoLock()
{
try
{
if (_probePort != null)
{
if (_probePort.IsStarted)
{
_probePort.Stop();
}
_probePort.Dispose();
_probePort = null;
}
}
catch
{
// Ignore errors on shutdown.
}
}
private static int FindFreeLocalPort()
{
var listener = new TcpListener(IPAddress.Loopback, 0);
listener.Start();
try
{
return ((IPEndPoint)listener.LocalEndpoint).Port;
}
finally
{
listener.Stop();
}
}
private async Task<bool> ProbeForwardAsync()
{
ForwardedPortLocal probe;
int port;
lock (_sync)
{
probe = _probePort;
port = _probeLocalPort;
}
if (probe == null || !probe.IsStarted || port <= 0)
{
// Порт проверки поднять не удалось - не выдумываем отказ проброса,
// иначе будем рвать рабочий туннель из-за собственной диагностики.
return true;
}
var timeoutMs = Math.Max(1, _settings.ProbeTimeoutSec) * 1000;
try
{
using (var tcp = new TcpClient())
{
var connectTask = tcp.ConnectAsync(IPAddress.Loopback, port);
var completed = await Task.WhenAny(connectTask, Task.Delay(timeoutMs)).ConfigureAwait(false);
if (completed != connectTask)
{
_log.Debug("e2e-проверка: таймаут подключения через проброс.");
return false;
}
await connectTask.ConfigureAwait(false);
return tcp.Connected;
}
}
catch (Exception ex)
{
_log.Debug("e2e-проверка: " + ex.Message);
return false;
}
}
private void StartRemoteHeartbeatNoLock()
{
if (_client == null || !_client.IsConnected)
+14 -3
View File
@@ -1,10 +1,11 @@
using MonsterMonitor.Models;
using MonsterMonitor.Services;
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows.Forms;
using MonsterMonitor.Models;
using MonsterMonitor.Services;
using static System.Net.Mime.MediaTypeNames;
namespace MonsterMonitor.UI
{
@@ -29,7 +30,7 @@ namespace MonsterMonitor.UI
public MainForm()
{
Text = string.Format("Monster Monitor v{0}", Application.ProductVersion);
Text = string.Format("Monster Monitor v{0}", System.Windows.Forms.Application.ProductVersion);
Width = 980;
Height = 620;
StartPosition = FormStartPosition.CenterScreen;
@@ -190,11 +191,18 @@ namespace MonsterMonitor.UI
private void AppendLog(LogEntry entry)
{
if (_console.Text.Length > 100000)
{
_console.ResetText();
}
System.Windows.Forms.Application.DoEvents();
if (InvokeRequired)
{
BeginInvoke(new Action<LogEntry>(AppendLog), entry);
return;
}
System.Windows.Forms.Application.DoEvents();
var wasNearBottom = IsConsoleNearBottom();
var firstVisibleLineBeforeAppend = GetFirstVisibleLine(_console);
@@ -204,12 +212,14 @@ namespace MonsterMonitor.UI
_console.SelectionColor = GetColor(entry.Level);
_console.AppendText($"[{entry.Timestamp:HH:mm:ss}] [{entry.Level}] {entry.Message}{Environment.NewLine}");
_console.SelectionColor = _console.ForeColor;
System.Windows.Forms.Application.DoEvents();
if (wasNearBottom)
{
_console.ScrollToCaret();
return;
}
System.Windows.Forms.Application.DoEvents();
var firstVisibleLineAfterAppend = GetFirstVisibleLine(_console);
var linesToRestore = firstVisibleLineBeforeAppend - firstVisibleLineAfterAppend;
@@ -217,6 +227,7 @@ namespace MonsterMonitor.UI
{
SendMessage(_console.Handle, EmLineScroll, IntPtr.Zero, (IntPtr)linesToRestore);
}
System.Windows.Forms.Application.DoEvents();
}
private bool IsConsoleNearBottom()
+5 -6
View File
@@ -1,4 +1,3 @@
using System;
using System.Drawing;
using System.Security.Cryptography;
using System.Windows.Forms;
@@ -65,7 +64,7 @@ namespace MonsterMonitor.UI
AddRow(panel, string.Empty, _chkSavePassword, 4);
AddRow(panel, "Удаленный порт:", _numRemotePort, 5);
AddRow(panel, "Локальный порт:", _numLocalPort, 6);
AddRow(panel, "Молчание heartbeat (сек):", _numMaxFailures, 7);
AddRow(panel, "Макс. потерь:", _numMaxFailures, 7);
AddRow(panel, "Таймаут reconnect (сек):", _numReconnectTimeout, 8);
AddRow(panel, "Прокси (http://host:port):", _txtProxy, 9);
AddRow(panel, "Путь к ss:", _txtSsPath, 10);
@@ -79,8 +78,8 @@ namespace MonsterMonitor.UI
num.Maximum = 65535;
}
_numMaxFailures.Minimum = 10;
_numMaxFailures.Maximum = 600;
_numMaxFailures.Minimum = 1;
_numMaxFailures.Maximum = 10;
_numReconnectTimeout.Minimum = 5;
_numReconnectTimeout.Maximum = 60;
@@ -201,7 +200,7 @@ namespace MonsterMonitor.UI
_chkSavePassword.Checked = _settings.SavePassword;
_numRemotePort.Value = _settings.RemotePort;
_numLocalPort.Value = _settings.LocalPort;
_numMaxFailures.Value = Math.Min(600, Math.Max(10, _settings.HeartbeatSilenceSec));
_numMaxFailures.Value = _settings.MaxPingFailures;
_numReconnectTimeout.Value = _settings.ReconnectTimeoutSec;
_txtProxy.Text = _settings.Proxy;
_txtSsPath.Text = _settings.SsProcessPath;
@@ -225,7 +224,7 @@ namespace MonsterMonitor.UI
_settings.SetPassword(_txtPassword.Text);
_settings.RemotePort = (int)_numRemotePort.Value;
_settings.LocalPort = (int)_numLocalPort.Value;
_settings.HeartbeatSilenceSec = (int)_numMaxFailures.Value;
_settings.MaxPingFailures = (int)_numMaxFailures.Value;
_settings.ReconnectTimeoutSec = (int)_numReconnectTimeout.Value;
_settings.Proxy = _txtProxy.Text.Trim();
_settings.SsProcessPath = string.IsNullOrWhiteSpace(_txtSsPath.Text)