From 62dc9166ce95a9dbab9a3e0733b89a6750df92d6 Mon Sep 17 00:00:00 2001 From: ChsBuffer <33744752+chsbuffer@users.noreply.github.com> Date: Sat, 26 Jun 2021 06:14:08 +0800 Subject: [PATCH] Refactor Guard.cs --- Netch/Controllers/Guard.cs | 355 +++++++------------- Netch/Controllers/NTTController.cs | 16 +- Netch/Controllers/PcapController.cs | 40 +-- Netch/Interfaces/IController.cs | 6 - Netch/Interfaces/IModeController.cs | 7 +- Netch/Servers/Shadowsocks/SSController.cs | 17 +- Netch/Servers/ShadowsocksR/SSRController.cs | 17 +- Netch/Servers/Trojan/TrojanController.cs | 17 +- Netch/Servers/V2ray/V2rayController.cs | 26 +- Netch/Utils/Bandwidth.cs | 26 +- 10 files changed, 195 insertions(+), 332 deletions(-) diff --git a/Netch/Controllers/Guard.cs b/Netch/Controllers/Guard.cs index 472dd27a..91be8ebb 100644 --- a/Netch/Controllers/Guard.cs +++ b/Netch/Controllers/Guard.cs @@ -1,6 +1,3 @@ -using Netch.Models; -using Netch.Utils; -using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; @@ -9,83 +6,157 @@ using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; +using Netch.Models; +using Netch.Utils; using Serilog; -using Timer = System.Timers.Timer; namespace Netch.Controllers { public abstract class Guard { - private readonly Timer _flushFileStreamTimer = new(300) { AutoReset = true }; - private FileStream? _logFileStream; - private StreamWriter? _logStreamWriter; - private bool _redirectToFile = true; - /// - /// 日志文件(重定向输出文件) - /// + /// application path relative of Netch\bin + /// + /// application output encode + protected Guard(string mainFile, bool redirectOutput = true, Encoding? encoding = null) + { + RedirectOutput = redirectOutput; + + var fileName = Path.GetFullPath($"bin\\{mainFile}"); + + if (!File.Exists(fileName)) + throw new MessageException(i18N.Translate($"bin\\{mainFile} file not found!")); + + Instance = new Process + { + StartInfo = + { + FileName = fileName, + WorkingDirectory = $"{Global.NetchDir}\\bin", + CreateNoWindow = true, + UseShellExecute = !RedirectOutput, + RedirectStandardOutput = RedirectOutput, + StandardOutputEncoding = RedirectOutput ? encoding : null, + RedirectStandardError = RedirectOutput, + StandardErrorEncoding = RedirectOutput ? encoding : null, + WindowStyle = ProcessWindowStyle.Hidden + } + }; + } + protected string LogPath => Path.Combine(Global.NetchDir, $"logging\\{Name}.log"); - /// - /// 成功启动关键词 - /// - protected virtual IEnumerable StartedKeywords { get; set; } = new List(); + protected virtual IEnumerable StartedKeywords { get; } = new List(); - /// - /// 启动失败关键词 - /// - protected virtual IEnumerable StoppedKeywords { get; set; } = new List(); + protected virtual IEnumerable FailedKeywords { get; } = new List(); public abstract string Name { get; } - /// - /// 主程序名 - /// - public abstract string MainFile { get; protected set; } + private State State { get; set; } = State.Waiting; - protected State State { get; set; } = State.Waiting; + private bool RedirectOutput { get; } - /// - /// 进程是否可以重定向输出 - /// - protected bool RedirectStd { get; set; } = true; + public Process Instance { get; } - protected bool RedirectToFile + ~Guard() { - get => RedirectStd && _redirectToFile; - set => _redirectToFile = value; + _logFileStream?.Dispose(); + _logStreamWriter?.Dispose(); + Instance.Dispose(); } - /// - /// 进程实例 - /// - public Process? Instance { get; private set; } - - /// - /// 程序输出的编码, - /// - protected virtual Encoding? InstanceOutputEncoding { get; } = null; - - public abstract void Stop(); - - /// - /// 停止进程 - /// - protected void StopInstance() + protected void StartGuard(string argument, ProcessPriorityClass priority = ProcessPriorityClass.Normal) { + State = State.Starting; + + _logFileStream = File.Open(LogPath, FileMode.Create, FileAccess.ReadWrite, FileShare.Read); + _logStreamWriter = new StreamWriter(_logFileStream) { AutoFlush = true }; + + Instance.StartInfo.Arguments = argument; + Instance.Start(); + + if (priority != ProcessPriorityClass.Normal) + Instance.PriorityClass = priority; + + if (RedirectOutput) + { + Task.Run(() => ReadOutput(Instance.StandardOutput)); + Task.Run(() => ReadOutput(Instance.StandardError)); + + if (!StartedKeywords.Any()) + { + // Skip, No started keyword + State = State.Started; + return; + } + + // wait ReadOutput change State + for (var i = 0; i < 1000; i++) + { + Thread.Sleep(10); + switch (State) + { + case State.Started: + OnStarted(); + return; + case State.Stopped: + StopGuard(); + OnStartFailed(); + throw new MessageException($"{Name} 控制器启动失败"); + } + } + + StopGuard(); + throw new MessageException($"{Name} 控制器启动超时"); + } + } + + private void ReadOutput(TextReader reader) + { + string? line; + while ((line = reader.ReadLine()) != null) + { + _logStreamWriter!.WriteLine(line); + OnReadNewLine(line); + + if (State == State.Starting) + { + if (StartedKeywords.Any(s => line.Contains(s))) + State = State.Started; + else if (FailedKeywords.Any(s => line.Contains(s))) + { + OnStartFailed(); + State = State.Stopped; + } + } + } + + State = State.Stopped; + } + + public virtual void Stop() + { + StopGuard(); + } + + protected void StopGuard() + { + _logStreamWriter?.Close(); + _logFileStream?.Close(); + try { - if (Instance == null || Instance.HasExited) - return; - - Instance.Kill(); - Instance.WaitForExit(); + if (Instance is { HasExited: false }) + { + Instance.Kill(); + Instance.WaitForExit(); + } } catch (Win32Exception e) { - Log.Error(e, "停止 {MainFile} 异常", MainFile); + Log.Error(e, "停止 {Name} 异常", Instance.ProcessName); } catch { @@ -93,191 +164,17 @@ namespace Netch.Controllers } } - /// - /// 仅初始化 ,不设定事件处理方法 - /// - /// - protected virtual void InitInstance(string argument) + protected virtual void OnStarted() { - Instance = new Process - { - StartInfo = - { - FileName = Path.GetFullPath($"bin\\{MainFile}"), - WorkingDirectory = $"{Global.NetchDir}\\bin", - Arguments = argument, - CreateNoWindow = true, - UseShellExecute = !RedirectStd, - RedirectStandardOutput = RedirectStd, - StandardOutputEncoding = RedirectStd ? InstanceOutputEncoding : null, - RedirectStandardError = RedirectStd, - StandardErrorEncoding = RedirectStd ? InstanceOutputEncoding : null, - WindowStyle = ProcessWindowStyle.Hidden - } - }; - - if (!File.Exists(Instance.StartInfo.FileName)) - throw new MessageException(i18N.Translate($"bin\\{MainFile} file not found!")); } - /// - /// 默认行为启动主程序 - /// - /// 主程序启动参数 - /// 进程优先级 - /// 是否成功启动 - protected void StartInstanceAuto(string argument, ProcessPriorityClass priority = ProcessPriorityClass.Normal) - { - State = State.Starting; - // 初始化程序 - InitInstance(argument); - - if (RedirectToFile) - OpenLogFile(); - - // 启动程序 - Instance!.Start(); - if (priority != ProcessPriorityClass.Normal) - Instance.PriorityClass = priority; - - if (RedirectStd) - { - Task.Run(() => ReadOutput(Instance.StandardOutput)); - Task.Run(() => ReadOutput(Instance.StandardError)); - - if (!StartedKeywords.Any()) - { - State = State.Started; - return; - } - } - else - { - return; - } - - // 等待启动 - for (var i = 0; i < 1000; i++) - { - Thread.Sleep(10); - switch (State) - { - case State.Started: - Task.Run(OnKeywordStarted); - return; - case State.Stopped: - Stop(); - CloseLogFile(); - OnKeywordStopped(); - throw new MessageException($"{Name} 控制器启动失败"); - } - } - - Stop(); - OnKeywordTimeout(); - throw new MessageException($"{Name} 控制器启动超时"); - } - - #region FileStream - - private void OpenLogFile() - { - if (!RedirectToFile) - return; - - _logFileStream = File.Open(LogPath, FileMode.Create, FileAccess.ReadWrite, FileShare.Read); - _logStreamWriter = new StreamWriter(_logFileStream); - - _flushFileStreamTimer.Elapsed += FlushFileStreamTimerEvent; - _flushFileStreamTimer.Enabled = true; - } - - private void WriteLog(string line) - { - if (!RedirectToFile) - return; - - _logStreamWriter!.WriteLine(line); - } - - private readonly object _logStreamLock = new(); - private void CloseLogFile() - { - if (!RedirectToFile) - return; - - lock (_logStreamLock) - { - if (_logFileStream == null) - return; - - _flushFileStreamTimer.Enabled = false; - _logStreamWriter?.Close(); - _logFileStream?.Close(); - _logStreamWriter = _logStreamWriter = null; - } - } - - #endregion - - #region virtual - protected virtual void OnReadNewLine(string line) { } - protected virtual void OnKeywordStarted() - { - } - - protected virtual void OnKeywordStopped() + protected virtual void OnStartFailed() { Utils.Utils.Open(LogPath); } - - protected virtual void OnKeywordTimeout() - { - } - - #endregion - - protected void ReadOutput(TextReader reader) - { - string? line; - while ((line = reader.ReadLine()) != null) - { - WriteLog(line); - OnReadNewLine(line); - - // State == State.Started if !StartedKeywords.Any() - if (State == State.Starting) - { - if (StartedKeywords.Any(s => line.Contains(s))) - State = State.Started; - else if (StoppedKeywords.Any(s => line.Contains(s))) - State = State.Stopped; - } - } - - CloseLogFile(); - State = State.Stopped; - } - - /// - /// 计时器存储日志 - /// - /// - /// - private void FlushFileStreamTimerEvent(object sender, EventArgs e) - { - try - { - _logStreamWriter!.Flush(); - } - catch (Exception ex) - { - Log.Warning(ex, "写入 {Name} 日志异常", Name); - } - } } } \ No newline at end of file diff --git a/Netch/Controllers/NTTController.cs b/Netch/Controllers/NTTController.cs index 4f818d13..96f49660 100644 --- a/Netch/Controllers/NTTController.cs +++ b/Netch/Controllers/NTTController.cs @@ -10,15 +10,12 @@ namespace Netch.Controllers { public class NTTController : Guard, IController { - public override string MainFile { get; protected set; } = "NTT.exe"; - - public override string Name { get; } = "NTT"; - - public override void Stop() + public NTTController() : base("NTT.exe") { - StopInstance(); } + public override string Name => "NTT"; + /// /// 启动 NatTypeTester /// @@ -29,8 +26,8 @@ namespace Netch.Controllers try { - InitInstance($" {Global.Settings.STUN_Server} {Global.Settings.STUN_Server_Port}"); - Instance!.Start(); + Instance.StartInfo.Arguments = $" {Global.Settings.STUN_Server} {Global.Settings.STUN_Server_Port}"; + Instance.Start(); var output = await Instance.StandardOutput.ReadToEndAsync(); var error = await Instance.StandardError.ReadToEndAsync(); @@ -47,8 +44,7 @@ namespace Netch.Controllers if (output.IsNullOrWhiteSpace()) if (!error.IsNullOrWhiteSpace()) { - error = error.Trim(); - var errorFirst = error.Substring(0, error.IndexOf('\n')).Trim(); + var errorFirst = error.GetLines().First(); return (errorFirst.SplitTrimEntries(':').Last(), null, null); } diff --git a/Netch/Controllers/PcapController.cs b/Netch/Controllers/PcapController.cs index 765cf3e0..500adc6a 100644 --- a/Netch/Controllers/PcapController.cs +++ b/Netch/Controllers/PcapController.cs @@ -15,23 +15,27 @@ namespace Netch.Controllers { public class PcapController : Guard, IModeController { - public override string Name { get; } = "pcap2socks"; + public PcapController() : base("pcap2socks.exe", encoding: Encoding.UTF8) + { + _form = new LogForm(Global.MainForm); + _form.CreateControl(); + } - public override string MainFile { get; protected set; } = "pcap2socks.exe"; + ~PcapController() + { + _form.Dispose(); + } - protected override IEnumerable StartedKeywords { get; set; } = new[] { "└" }; + public override string Name => "pcap2socks"; - protected override Encoding? InstanceOutputEncoding { get; } = Encoding.UTF8; + protected override IEnumerable StartedKeywords { get; } = new[] { "└" }; - private LogForm? _form; + private readonly LogForm _form; public void Start(in Mode mode) { var server = MainController.Server!; - _form = new LogForm(Global.MainForm); - _form.CreateControl(); - var outboundNetworkInterface = NetworkInterfaceUtils.GetBest(); var argument = new StringBuilder($@"-i \Device\NPF_{outboundNetworkInterface.Id}"); @@ -41,26 +45,22 @@ namespace Netch.Controllers argument.Append($" --destination 127.0.0.1:{Global.Settings.Socks5LocalPort}"); argument.Append($" {mode.GetRules().FirstOrDefault() ?? "-P n"}"); - StartInstanceAuto(argument.ToString()); + StartGuard(argument.ToString()); } protected override void OnReadNewLine(string line) { - Global.MainForm.BeginInvoke(new Action(() => - { - if (!_form!.IsDisposed) - _form.richTextBox1.AppendText(line + "\n"); - })); + Global.MainForm.BeginInvoke(new Action(() => _form.richTextBox1.AppendText(line + "\n"))); } - protected override void OnKeywordStarted() + protected override void OnStarted() { - Global.MainForm.BeginInvoke(new Action(() => { _form!.Show(); })); + Global.MainForm.BeginInvoke(new Action(() => _form.Show())); } - protected override void OnKeywordStopped() + protected override void OnStartFailed() { - if (File.ReadAllText(LogPath).Length == 0) + if (new FileInfo(LogPath).Length == 0) { Task.Run(() => { @@ -76,8 +76,8 @@ namespace Netch.Controllers public override void Stop() { - _form!.Close(); - StopInstance(); + _form.Close(); + StopGuard(); } } } \ No newline at end of file diff --git a/Netch/Interfaces/IController.cs b/Netch/Interfaces/IController.cs index 9d914b02..a7837ec2 100644 --- a/Netch/Interfaces/IController.cs +++ b/Netch/Interfaces/IController.cs @@ -2,14 +2,8 @@ { public interface IController { - /// - /// 控制器名 - /// public string Name { get; } - /// - /// 停止 - /// public void Stop(); } } \ No newline at end of file diff --git a/Netch/Interfaces/IModeController.cs b/Netch/Interfaces/IModeController.cs index 76d78b42..8d5c933f 100644 --- a/Netch/Interfaces/IModeController.cs +++ b/Netch/Interfaces/IModeController.cs @@ -4,11 +4,6 @@ namespace Netch.Interfaces { public interface IModeController : IController { - /// - /// 启动 - /// - /// 模式 - /// 是否成功 - public abstract void Start(in Mode mode); + public void Start(in Mode mode); } } \ No newline at end of file diff --git a/Netch/Servers/Shadowsocks/SSController.cs b/Netch/Servers/Shadowsocks/SSController.cs index 6490bdd4..cbd563b8 100644 --- a/Netch/Servers/Shadowsocks/SSController.cs +++ b/Netch/Servers/Shadowsocks/SSController.cs @@ -7,13 +7,15 @@ namespace Netch.Servers.Shadowsocks { public class SSController : Guard, IServerController { - public override string MainFile { get; protected set; } = "Shadowsocks.exe"; + public SSController() : base("Shadowsocks.exe") + { + } - protected override IEnumerable StartedKeywords { get; set; } = new[] { "listening at" }; + protected override IEnumerable StartedKeywords => new[] { "listening at" }; - protected override IEnumerable StoppedKeywords { get; set; } = new[] { "Invalid config path", "usage", "plugin service exit unexpectedly" }; + protected override IEnumerable FailedKeywords => new[] { "Invalid config path", "usage", "plugin service exit unexpectedly" }; - public override string Name { get; } = "Shadowsocks"; + public override string Name => "Shadowsocks"; public ushort? Socks5LocalPort { get; set; } @@ -36,7 +38,7 @@ namespace Netch.Servers.Shadowsocks plugin_opts = server.PluginOption }; - StartInstanceAuto(command.ToString()); + StartGuard(command.ToString()); } [Verb] @@ -70,10 +72,5 @@ namespace Netch.Servers.Shadowsocks [Optional] public string? acl { get; set; } } - - public override void Stop() - { - StopInstance(); - } } } \ No newline at end of file diff --git a/Netch/Servers/ShadowsocksR/SSRController.cs b/Netch/Servers/ShadowsocksR/SSRController.cs index 80d9843c..01b5b571 100644 --- a/Netch/Servers/ShadowsocksR/SSRController.cs +++ b/Netch/Servers/ShadowsocksR/SSRController.cs @@ -7,13 +7,15 @@ namespace Netch.Servers.ShadowsocksR { public class SSRController : Guard, IServerController { - public override string MainFile { get; protected set; } = "ShadowsocksR.exe"; + public SSRController() : base("ShadowsocksR.exe") + { + } - protected override IEnumerable StartedKeywords { get; set; } = new[] { "listening at" }; + protected override IEnumerable StartedKeywords => new[] { "listening at" }; - protected override IEnumerable StoppedKeywords { get; set; } = new[] { "Invalid config path", "usage" }; + protected override IEnumerable FailedKeywords => new[] { "Invalid config path", "usage" }; - public override string Name { get; } = "ShadowsocksR"; + public override string Name => "ShadowsocksR"; public ushort? Socks5LocalPort { get; set; } @@ -39,7 +41,7 @@ namespace Netch.Servers.ShadowsocksR u = true }; - StartInstanceAuto(command.ToString()); + StartGuard(command.ToString()); } [Verb] @@ -79,10 +81,5 @@ namespace Netch.Servers.ShadowsocksR [Optional] public string? acl { get; set; } } - - public override void Stop() - { - StopInstance(); - } } } \ No newline at end of file diff --git a/Netch/Servers/Trojan/TrojanController.cs b/Netch/Servers/Trojan/TrojanController.cs index 69678730..0b3fc262 100644 --- a/Netch/Servers/Trojan/TrojanController.cs +++ b/Netch/Servers/Trojan/TrojanController.cs @@ -10,13 +10,15 @@ namespace Netch.Servers.Trojan { public class TrojanController : Guard, IServerController { - public override string MainFile { get; protected set; } = "Trojan.exe"; + public TrojanController() : base("Trojan.exe") + { + } - protected override IEnumerable StartedKeywords { get; set; } = new[] { "started" }; + protected override IEnumerable StartedKeywords => new[] { "started" }; - protected override IEnumerable StoppedKeywords { get; set; } = new[] { "exiting" }; + protected override IEnumerable FailedKeywords => new[] { "exiting" }; - public override string Name { get; } = "Trojan"; + public override string Name => "Trojan"; public ushort? Socks5LocalPort { get; set; } @@ -45,12 +47,7 @@ namespace Netch.Servers.Trojan File.WriteAllBytes(Constants.TempConfig, JsonSerializer.SerializeToUtf8Bytes(trojanConfig, Global.NewDefaultJsonSerializerOptions)); - StartInstanceAuto("-c ..\\data\\last.json"); - } - - public override void Stop() - { - StopInstance(); + StartGuard("-c ..\\data\\last.json"); } } } \ No newline at end of file diff --git a/Netch/Servers/V2ray/V2rayController.cs b/Netch/Servers/V2ray/V2rayController.cs index 134d0aad..ba0686d0 100644 --- a/Netch/Servers/V2ray/V2rayController.cs +++ b/Netch/Servers/V2ray/V2rayController.cs @@ -9,13 +9,17 @@ namespace Netch.Servers.V2ray { public class V2rayController : Guard, IServerController { - public override string MainFile { get; protected set; } = "xray.exe"; + public V2rayController() : base("xray.exe") + { + if (!Global.Settings.V2RayConfig.XrayCone) + Instance.StartInfo.Environment["XRAY_CONE_DISABLED"] = "true"; + } - protected override IEnumerable StartedKeywords { get; set; } = new[] { "started" }; + protected override IEnumerable StartedKeywords => new[] { "started" }; - protected override IEnumerable StoppedKeywords { get; set; } = new[] { "config file not readable", "failed to" }; + protected override IEnumerable FailedKeywords => new[] { "config file not readable", "failed to" }; - public override string Name { get; } = "Xray"; + public override string Name => "Xray"; public ushort? Socks5LocalPort { get; set; } @@ -24,19 +28,7 @@ namespace Netch.Servers.V2ray public virtual void Start(in Server s, in Mode mode) { File.WriteAllText(Constants.TempConfig, V2rayConfigUtils.GenerateClientConfig(s, mode)); - StartInstanceAuto("-config ..\\data\\last.json"); - } - - public override void Stop() - { - StopInstance(); - } - - protected override void InitInstance(string argument) - { - base.InitInstance(argument); - if (!Global.Settings.V2RayConfig.XrayCone) - Instance!.StartInfo.Environment["XRAY_CONE_DISABLED"] = "true"; + StartGuard("-config ..\\data\\last.json"); } } } \ No newline at end of file diff --git a/Netch/Utils/Bandwidth.cs b/Netch/Utils/Bandwidth.cs index 54e3d1e8..ffcf095e 100644 --- a/Netch/Utils/Bandwidth.cs +++ b/Netch/Utils/Bandwidth.cs @@ -1,12 +1,12 @@ -using Microsoft.Diagnostics.Tracing.Parsers; -using Microsoft.Diagnostics.Tracing.Session; -using Netch.Controllers; -using Netch.Models; -using System.Collections.Generic; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.Diagnostics.Tracing.Parsers; +using Microsoft.Diagnostics.Tracing.Session; +using Netch.Controllers; +using Netch.Models; using Serilog; namespace Netch.Utils @@ -58,9 +58,8 @@ namespace Netch.Utils { case null: break; - case Guard instanceController: - if (instanceController.Instance != null) - instances.Add(instanceController.Instance); + case Guard guard: + instances.Add(guard.Instance); break; } @@ -70,18 +69,17 @@ namespace Netch.Utils { case null: break; - case NFController _: + case NFController: instances.Add(Process.GetCurrentProcess()); break; - case Guard instanceController: - instances.Add(instanceController.Instance!); + case Guard guard: + instances.Add(guard.Instance); break; } - var processList = instances.Select(instance => instance.Id).ToList(); + var processList = instances.Select(instance => instance.Id).ToHashSet(); - Log.Information("流量统计进程: {Processes}", - $"{string.Join(",", instances.Select(instance => $"({instance.Id})" + instance.ProcessName).ToArray())}"); + Log.Information("流量统计进程: {Processes}", string.Join(',', instances.Select(v => $"({v.Id}){v.ProcessName}"))); received = 0;