diff --git a/Helper.cs b/Helper.cs index 27e03a9f..8c383abb 100644 --- a/Helper.cs +++ b/Helper.cs @@ -105,10 +105,10 @@ public static void StartUpCheck() Program.TasksManager.SignalRun(nameof(SignalsNames.MainWindowInitSignal), () => { - new Thread(() => + new Thread(async () => { Thread.Sleep(Program.Config.Web.DelayStartSeconds * 1000); - Program.WebManager = new WebManager().Start(); + Program.WebManager = await new WebManager().Start(); }).Start(); }); diff --git a/Interfaces/IModuleController.cs b/Interfaces/IModuleController.cs new file mode 100644 index 00000000..71f0733d --- /dev/null +++ b/Interfaces/IModuleController.cs @@ -0,0 +1,13 @@ +using System; +using System.Threading.Tasks; + +namespace KitX_Dashboard.Interfaces; + +internal interface IModuleController : IDisposable +{ + Task Start(); + + Task Stop(); + + Task Restart(); +} diff --git a/Interfaces/Network/IKitXClient.cs b/Interfaces/Network/IKitXClient.cs new file mode 100644 index 00000000..86a6f658 --- /dev/null +++ b/Interfaces/Network/IKitXClient.cs @@ -0,0 +1,15 @@ +using System; +using System.Threading.Tasks; + +namespace KitX_Dashboard.Interfaces.Network; + +internal interface IKitXClient : IModuleController +{ + Task Connect(); + + Task Disconnect(); + + Task Send(byte[] content); + + T OnReceive(Action action); +} diff --git a/Interfaces/Network/IKitXServer.cs b/Interfaces/Network/IKitXServer.cs new file mode 100644 index 00000000..4a432be0 --- /dev/null +++ b/Interfaces/Network/IKitXServer.cs @@ -0,0 +1,16 @@ +using System; +using System.Net.Sockets; +using System.Threading.Tasks; + +namespace KitX_Dashboard.Interfaces.Network; + +internal interface IKitXServer : IModuleController +{ + Task Broadcast(byte[] content); + + Task BroadCast(byte[] content, Func? pattern); + + Task Send(byte[] content, string target); + + T OnReceive(Action action); +} diff --git a/Managers/DevicesManager.cs b/Managers/DevicesManager.cs index 3d7c1927..b0fee46b 100644 --- a/Managers/DevicesManager.cs +++ b/Managers/DevicesManager.cs @@ -1,7 +1,7 @@ using Avalonia.Threading; using KitX.Web.Rules; using KitX_Dashboard.Data; -using KitX_Dashboard.Servers; +using KitX_Dashboard.Network; using KitX_Dashboard.Services; using KitX_Dashboard.Views.Pages.Controls; using Serilog; @@ -16,19 +16,23 @@ internal class DevicesManager { internal static void InitEvents() { - EventService.OnReceivingDeviceInfoStruct4DeviceNet += dis => + EventService.OnReceivingDeviceInfoStruct += dis => { - if (GlobalInfo.IsMainMachine) - if (dis.DeviceServerBuildTime < GlobalInfo.ServerBuildTime) - { - Program.WebManager?.devicesServer?.CancleBuildServer(); - Log.Information($"In DevicesManager: Watched for earlier built server. " + - $"DeviceServerAddress: {dis.IPv4}:{dis.DeviceServerPort} " + - $"DeviceServerBuildTime: {dis.DeviceServerBuildTime}"); - } + if (dis.IsMainDevice && dis.DeviceServerBuildTime < GlobalInfo.ServerBuildTime) + { + Program.WebManager?.devicesServer?.Stop(); + + Watch4MainDevice(); + + Log.Information($"In DevicesManager: Watched earlier built server. " + + $"DeviceServerAddress: {dis.IPv4}:{dis.DeviceServerPort} " + + $"DeviceServerBuildTime: {dis.DeviceServerBuildTime}"); + } }; } + private static readonly object _receivedDeviceInfoStruct4WatchLock = new(); + internal static List? receivedDeviceInfoStruct4Watch; internal static readonly Queue deviceInfoStructs = new(); @@ -53,7 +57,7 @@ private static bool CheckDeviceIsOffline(DeviceInfoStruct info) /// 是否是本机卡片 private static bool CheckIsCurrentMachine(DeviceInfoStruct info) { - var self = DevicesServer.DefaultDeviceInfoStruct; + var self = DevicesDiscoveryServer.DefaultDeviceInfoStruct; return info.DeviceMacAddress.Equals(self.DeviceMacAddress) && info.DeviceName.Equals(self.DeviceName); } @@ -266,11 +270,16 @@ internal static void KeepCheckAndRemove() internal static void Update(DeviceInfoStruct deviceInfo) { deviceInfoStructs.Enqueue(deviceInfo); - receivedDeviceInfoStruct4Watch?.Add(deviceInfo); - if (deviceInfo.IsMainDevice) - EventService.Invoke(nameof(EventService.OnReceivingDeviceInfoStruct4DeviceNet), - deviceInfo); + if (receivedDeviceInfoStruct4Watch is not null) + { + lock (_receivedDeviceInfoStruct4WatchLock) + { + receivedDeviceInfoStruct4Watch.Add(deviceInfo); + } + } + + EventService.Invoke(nameof(EventService.OnReceivingDeviceInfoStruct), deviceInfo); } /// @@ -278,30 +287,31 @@ internal static void Update(DeviceInfoStruct deviceInfo) /// internal static void Watch4MainDevice() { + var location = $"{nameof(DevicesManager)}.{nameof(Watch4MainDevice)}"; + new Thread(() => { - try - { - receivedDeviceInfoStruct4Watch = new(); + receivedDeviceInfoStruct4Watch = new(); - var checkedTime = 0; - var hadMainDevice = false; - var earliestBuiltServerTime = DateTime.UtcNow; - var serverPort = 0; - var serverAddress = string.Empty; + var checkedTime = 0; + var hadMainDevice = false; + var earliestBuiltServerTime = DateTime.UtcNow; + var serverPort = 0; + var serverAddress = string.Empty; - while (checkedTime < 7) + while (checkedTime < 7) + { + try { - try - { - if (receivedDeviceInfoStruct4Watch is null) continue; + if (receivedDeviceInfoStruct4Watch is null) continue; + lock (_receivedDeviceInfoStruct4WatchLock) + { foreach (var item in receivedDeviceInfoStruct4Watch) { if (item.IsMainDevice) { - if (item.DeviceServerBuildTime.ToUniversalTime() - < earliestBuiltServerTime) + if (item.DeviceServerBuildTime.ToUniversalTime() < earliestBuiltServerTime) { serverPort = item.DeviceServerPort; serverAddress = item.IPv4; @@ -309,36 +319,31 @@ internal static void Watch4MainDevice() hadMainDevice = true; } } + } - ++checkedTime; - - Log.Information($"In Watch4MainDevice: " + - $"Watched for {checkedTime} times."); + ++checkedTime; - if (checkedTime == 7) - { - receivedDeviceInfoStruct4Watch?.Clear(); - receivedDeviceInfoStruct4Watch = null; - WatchingOver(hadMainDevice, serverAddress, serverPort); - } + Log.Information($"In {location}: Watched for {checkedTime} times."); - Thread.Sleep(1 * 1000); // Sleep 1 second. - } - catch (Exception e) + if (checkedTime == 7) { receivedDeviceInfoStruct4Watch?.Clear(); receivedDeviceInfoStruct4Watch = null; - Log.Error(e, "In Watch4MainDevice"); + WatchingOver(hadMainDevice, serverAddress, serverPort); } + + Thread.Sleep(1 * 1000); // Sleep 1 second. } - } - catch (Exception ex) - { - receivedDeviceInfoStruct4Watch?.Clear(); - receivedDeviceInfoStruct4Watch = null; + catch (Exception e) + { + receivedDeviceInfoStruct4Watch?.Clear(); + receivedDeviceInfoStruct4Watch = null; + + Log.Error(e, $"In {location}: {e.Message} Rewatch."); - Log.Error(ex, "In Watch4MainDevice"); + Watch4MainDevice(); + } } }).Start(); } @@ -346,18 +351,30 @@ internal static void Watch4MainDevice() /// /// 观察结束 /// - internal static void WatchingOver(bool hadMainDevice, string serverAddress, int serverPort) + internal static async void WatchingOver(bool foundMainDevice, string serverAddress, int serverPort) { - Log.Information($"In WatchingOver: hadMainDevice: {hadMainDevice}, " + - $"serverAddress: {serverAddress}, serverPort: {serverPort}"); + var location = $"{nameof(DevicesManager)}.{nameof(WatchingOver)}"; + + Log.Information($"In {location}: " + + $"{nameof(foundMainDevice)} -> {foundMainDevice}, " + + $"{nameof(serverAddress)} -> {serverAddress}, " + + $"{nameof(serverPort)} -> {serverPort}"); - if (hadMainDevice) + if (foundMainDevice) { - Program.WebManager?.devicesServer?.AttendServer(serverAddress, serverPort); + var client = Program.WebManager?.devicesClient; + + if (client is null) return; + + await client + .SetServerAddress(serverAddress) + .SetServerPort(serverPort) + .Start() + ; } else { - Program.WebManager?.devicesServer?.BuildServer(); + Program.WebManager?.devicesServer?.Start(); } } } diff --git a/Managers/TasksManager.cs b/Managers/TasksManager.cs index 167a4dab..ccfaa85b 100644 --- a/Managers/TasksManager.cs +++ b/Managers/TasksManager.cs @@ -1,6 +1,8 @@ using Common.BasicHelper.Utils.Extensions; +using Serilog; using System; using System.Collections.Generic; +using System.Threading.Tasks; namespace KitX_Dashboard.Managers; @@ -33,4 +35,40 @@ internal void SignalRun(string signal, Action action) SignalTasks[signal].Enqueue(action); else SignalTasks.Add(signal, new Queue().Push(action)); } + + /// + /// 执行任务, 并带有更好的日志显示 + /// + /// 要执行的动作 + /// 日志显示名称 + /// 日志提示 + public static void RunTask( + Action action, + string name = nameof(Action), + string prompt = ">>> ") + { + Log.Information($"{prompt}Task `{name}` began."); + + action(); + + Log.Information($"{prompt}Task `{name}` done."); + } + + /// + /// 异步执行任务, 并带有更好的日志显示 + /// + /// 要执行的动作 + /// 任务名称 + /// 日志提示 + public static async Task RunTaskAsync( + Action action, + string name = nameof(Action), + string prompt = ">>> ") + { + Log.Information($"{prompt}Task `{name}` began."); + + await Task.Run(action); + + Log.Information($"{prompt}Task `{name}` done."); + } } diff --git a/Managers/WebManager.cs b/Managers/WebManager.cs index 2583ccc2..99906e6a 100644 --- a/Managers/WebManager.cs +++ b/Managers/WebManager.cs @@ -1,8 +1,7 @@ -using KitX_Dashboard.Servers; +using KitX_Dashboard.Network; using Serilog; using System; using System.Collections.ObjectModel; -using System.Threading; using System.Threading.Tasks; namespace KitX_Dashboard.Managers; @@ -16,47 +15,63 @@ public WebManager() internal PluginsServer? pluginsServer; internal DevicesServer? devicesServer; + internal DevicesClient? devicesClient; + internal DevicesDiscoveryServer? devicesDiscoveryServer; internal ObservableCollection? NetworkInterfaceRegistered; /// /// 开始执行网络相关服务 /// - /// 是否启动插件服务器 + /// 是否启动全部 + /// 是否启动插件服务器 /// 是否启动设备服务器 + /// 是否启动设备自发现服务器 /// 网络管理器本身 - public WebManager Start(bool startPluginsServer = true, bool startDevicesServer = true) + public async Task Start + ( + bool startAll = true, + + bool startPluginsServices = false, + bool startDevicesServices = false, + bool startDevicesDiscoveryServer = false + ) { - new Thread(() => + var location = $"{nameof(WebManager)}.{nameof(Start)}"; + + await TasksManager.RunTaskAsync(async () => { try { - Log.Information("WebManager: Starting..."); + if (startAll || startDevicesDiscoveryServer) + devicesDiscoveryServer = await new DevicesDiscoveryServer().Start(); - if (startDevicesServer) + if (startAll || startDevicesServices) { + devicesServer = new(); + devicesClient = new(); + DevicesManager.InitEvents(); DevicesManager.KeepCheckAndRemove(); DevicesManager.Watch4MainDevice(); - - devicesServer = new(); - devicesServer.Start(); } - if (startPluginsServer) + if (startAll || startPluginsServices) { PluginsManager.KeepCheckAndRemove(); PluginsManager.KeepCheckAndRemoveOrDelete(); - pluginsServer = new(); - pluginsServer.Start(); + pluginsServer = await new PluginsServer().Start(); } } catch (Exception ex) { - Log.Error(ex, "In WebManager Start"); + Log.Error(ex, $"In {location}: " + + $"{nameof(startPluginsServices)}: {startPluginsServices}," + + $"{nameof(startDevicesServices)}: {startDevicesServices}," + + $"{nameof(startDevicesDiscoveryServer)}: {startDevicesDiscoveryServer}"); } - }).Start(); + }, location); return this; } @@ -64,37 +79,66 @@ public WebManager Start(bool startPluginsServer = true, bool startDevicesServer /// /// 停止执行网络相关服务 /// - /// 是否停止插件服务器 - /// 是否停止设备服务器 + /// 是否停止插件服务器 + /// 是否停止设备服务器 /// 网络管理器本身 - public WebManager Stop(bool stopPluginsServer = true, bool stopDevicesServer = true) + public WebManager Stop + ( + bool stopAll = true, + + bool stopPluginsServices = true, + bool stopDevicesServices = true, + bool stopDevicesDiscoveryServer = true + ) { - if (stopPluginsServer) - { - pluginsServer?.Stop(); - pluginsServer?.Dispose(); - } + if (stopAll || stopPluginsServices) + pluginsServer?.Stop().ContinueWith( + server => server.Dispose() + ); - if (stopDevicesServer) + if (stopAll || stopDevicesServices) { - devicesServer?.Stop(); - devicesServer?.Dispose(); + devicesServer?.Stop().ContinueWith( + server => server.Dispose() + ); + + devicesClient?.Stop().ContinueWith( + client => client.Dispose() + ); } + if (stopAll || stopDevicesDiscoveryServer) + devicesDiscoveryServer?.Stop().ContinueWith( + server => server.Dispose() + ); + return this; } /// /// 重启网络相关服务 /// - /// 是否重启插件服务器 - /// 是否重启设备服务器 + /// 是否重启插件服务器 + /// 是否重启设备服务器 /// 重新启动前要执行的操作 /// 网络管理器本身 - public WebManager Restart(bool restartPluginsServer = true, bool restartDevicesServer = true, - Action? actionBeforeStarting = null) + public WebManager Restart + ( + bool restartAll = true, + + bool restartPluginsServices = false, + bool restartDevicesServices = false, + bool restartDevicesDiscoveryServer = false, + + Action? actionBeforeStarting = null + ) { - Stop(stopPluginsServer: restartPluginsServer, stopDevicesServer: restartDevicesServer); + Stop( + restartAll, + restartPluginsServices, + restartDevicesServices, + restartDevicesDiscoveryServer + ); Task.Run(async () => { @@ -102,8 +146,14 @@ public WebManager Restart(bool restartPluginsServer = true, bool restartDevicesS actionBeforeStarting?.Invoke(); - Start(startPluginsServer: restartPluginsServer, startDevicesServer: restartDevicesServer); + await Start( + restartAll, + restartPluginsServices, + restartDevicesServices, + restartDevicesDiscoveryServer + ); }); + return this; } @@ -114,6 +164,9 @@ public void Dispose() { pluginsServer?.Dispose(); devicesServer?.Dispose(); + devicesClient?.Dispose(); + devicesDiscoveryServer?.Dispose(); + GC.SuppressFinalize(this); } } diff --git a/Network/DevicesClient.cs b/Network/DevicesClient.cs new file mode 100644 index 00000000..8c1744a0 --- /dev/null +++ b/Network/DevicesClient.cs @@ -0,0 +1,246 @@ +using Common.BasicHelper.Utils.Extensions; +using KitX_Dashboard.Interfaces.Network; +using KitX_Dashboard.Managers; +using Serilog; +using System; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +namespace KitX_Dashboard.Network; + +internal class DevicesClient : IKitXClient +{ + private static TcpClient? client = null; + + private static bool keepReceiving = false; + + private static ClientStatus status = ClientStatus.Unknown; + + public static string ServerAddress { get; set; } = string.Empty; + + public static int ServerPort { get; set; } = 0; + + public static ClientStatus Status + { + get => status; + set + { + status = value; + + if (status == ClientStatus.Errored) + DevicesManager.Watch4MainDevice(); + } + } + + public static Action? onReceive = null; + + public DevicesClient SetServerAddress(string address) + { + ServerAddress = address; + + return this; + } + + public DevicesClient SetServerPort(int port) + { + if (port <= 0 || port >= 65536) + throw new ArgumentOutOfRangeException(nameof(port), "0 < port < 65536"); + + ServerPort = port; + + return this; + } + + public async void Receive() + { + var location = $"{nameof(DevicesClient)}.{nameof(Receive)}"; + + if (client is null) return; + + var stream = client?.GetStream(); + + if (stream is null) return; + + var buffer = new byte[Program.Config.Web.SocketBufferSize]; // Default 10 MB buffer + + try + { + while (keepReceiving) + { + if (buffer is null) break; + + var length = await stream.ReadAsync(buffer); + + if (length > 0) + { + var msg = buffer.ToUTF8(0, length); + + Log.Information($"Receive from Host: {msg}"); + } + else + { + keepReceiving = false; + break; + } + } + } + catch (Exception e) + { + Log.Error(e, $"In {location}: {e.Message}"); + + Status = ClientStatus.Errored; + } + + stream.CloseAndDispose(); + + client?.CloseAndDispose(); + } + + public async Task Connect() + { + var location = $"{nameof(DevicesClient)}.{nameof(Connect)}"; + + if (client is null) return this; + + Status = ClientStatus.Connecting; + + await TasksManager.RunTaskAsync(async () => + { + try + { + await client.ConnectAsync(ServerAddress, ServerPort); + + new Thread(Receive).Start(); + } + catch (Exception ex) + { + Log.Error(ex, $"In {location}: {ex.Message}"); + + await Stop(); + + Status = ClientStatus.Errored; + } + + }, location); + + Status = ClientStatus.Running; + + return this; + } + + public async Task Disconnect() + { + var location = $"{nameof(DevicesClient)}.{nameof(Disconnect)}"; + + Status = ClientStatus.Disconnecting; + + await TasksManager.RunTaskAsync(() => + { + keepReceiving = false; + + client?.Close(); + + }, location); + + Status = ClientStatus.Pending; + + return this; + } + + public DevicesClient OnReceive(Action action) + { + onReceive = action; + + return this; + } + + public async Task Send(byte[] content) + { + var location = $"{nameof(DevicesClient)}.{nameof(Send)}"; + + await TasksManager.RunTaskAsync(async () => + { + try + { + var stream = client?.GetStream(); + + if (stream is null) return; + + stream.Write(content, 0, content.Length); + stream.Flush(); + + Log.Information($"Sent Message to Host, msg: {content.ToUTF8()}"); + } + catch (Exception e) + { + Log.Error(e, $"In {location}: {e.Message}"); + + await Stop(); + + Status = ClientStatus.Errored; + } + }, location); + + return this; + } + + private static void Init() + { + keepReceiving = true; + + client = new(); + } + + public async Task Start() + { + var location = $"{nameof(DevicesClient)}.{nameof(Start)}"; + + await TasksManager.RunTaskAsync(async () => + { + Status = ClientStatus.Pending; + + Init(); + + await Connect(); + + }, location); + + return this; + } + + public async Task Stop() + { + var location = $"{nameof(DevicesClient)}.{nameof(Stop)}"; + + await TasksManager.RunTaskAsync(async () => + { + keepReceiving = false; + + await Disconnect(); + + }, location); + + return this; + } + + public async Task Restart() + { + var location = $"{nameof(DevicesClient)}.{nameof(Restart)}"; + + await TasksManager.RunTaskAsync(async () => + { + await Stop(); + + await Start(); + + }, location); + + return this; + } + + public void Dispose() + { + client?.Dispose(); + } +} diff --git a/Network/DevicesDiscoveryServer.cs b/Network/DevicesDiscoveryServer.cs new file mode 100644 index 00000000..90ee473d --- /dev/null +++ b/Network/DevicesDiscoveryServer.cs @@ -0,0 +1,419 @@ +using Common.BasicHelper.Utils.Extensions; +using KitX.Web.Rules; +using KitX_Dashboard.Data; +using KitX_Dashboard.Interfaces.Network; +using KitX_Dashboard.Managers; +using KitX_Dashboard.Names; +using Serilog; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; + +namespace KitX_Dashboard.Network; + +/// +/// 设备自发现网络服务器 +/// +internal class DevicesDiscoveryServer : IKitXServer +{ + private static UdpClient? UdpSender = null; + + private static UdpClient? UdpReceiver = null; + + private static System.Timers.Timer? UdpSendTimer = null; + + private static int DeviceInfoStructUpdatedTimes = 0; + + private static int LastTimeToOSVersionUpdated = 0; + + private static bool CloseDevicesDiscoveryServerRequest = false; + + private static readonly List SupportedNetworkInterfacesIndexes = new(); + + private static ServerStatus status = ServerStatus.Unknown; + + private static Action? onReceive = null; + + internal static readonly Queue Messages2BroadCast = new(); + + internal static DeviceInfoStruct DefaultDeviceInfoStruct = NetworkHelper.GetDeviceInfo(); + + internal static ServerStatus Status + { + get => status; + set + { + status = value; + } + } + + /// + /// 寻找受支持的网络适配器并把 UDP 客户端加入组播 + /// + private static void FindSupportNetworkInterfaces(List clients, IPAddress multicastAddress) + { + var multicastGroupJoinedInterfacesCount = 0; + + foreach (var adapter in NetworkInterface.GetAllNetworkInterfaces()) + { + var adapterProperties = adapter.GetIPProperties(); + if (adapterProperties is null) continue; + + try + { + var logs = adapter.Dump2Lines(); + for (int i = 0; i < logs.Length; i++) + Log.Information(logs[i]); + } + catch (Exception ex) + { + Log.Warning(ex, "Logging network interface items."); + } + + if (!NetworkHelper.CheckNetworkInterface(adapter, adapterProperties)) continue; + + var unicastIPAddresses = adapterProperties.UnicastAddresses; + if (unicastIPAddresses is null) continue; + + var p = adapterProperties.GetIPv4Properties(); + if (p is null) continue; // IPv4 is not configured on this adapter + + SupportedNetworkInterfacesIndexes.Add(IPAddress.HostToNetworkOrder(p.Index)); + + foreach (var ipAddress in unicastIPAddresses + .Select(x => x.Address) + .Where(x => x.AddressFamily == AddressFamily.InterNetwork)) + { + try + { + foreach (var udpClient in clients) + udpClient?.JoinMulticastGroup(multicastAddress, ipAddress); + + Program.WebManager?.NetworkInterfaceRegistered?.Add(adapter.Name); + + ++multicastGroupJoinedInterfacesCount; + } + catch (Exception ex) + { + var location = $"{nameof(DevicesServer)}.{nameof(FindSupportNetworkInterfaces)}"; + Log.Error(ex, $"In {location}: {ex.Message}"); + } + } + } + + Program.TasksManager?.RaiseSignal(nameof(SignalsNames.FinishedFindingNetworkInterfacesSignal)); + + Log.Information($"" + + $"Find {SupportedNetworkInterfacesIndexes.Count} supported network interfaces."); + Log.Information($"" + + $"Joined {multicastGroupJoinedInterfacesCount} multicast groups."); + } + + /// + /// 更新默认设备信息结构 + /// + private static void UpdateDefaultDeviceInfoStruct() + { + DefaultDeviceInfoStruct.IsMainDevice = GlobalInfo.IsMainMachine; + DefaultDeviceInfoStruct.SendTime = DateTime.UtcNow; + DefaultDeviceInfoStruct.IPv4 = NetworkHelper.GetInterNetworkIPv4(); + DefaultDeviceInfoStruct.IPv6 = NetworkHelper.GetInterNetworkIPv6(); + DefaultDeviceInfoStruct.PluginServerPort = GlobalInfo.PluginServerPort; + DefaultDeviceInfoStruct.PluginsCount = Program.PluginCards.Count; + DefaultDeviceInfoStruct.IsMainDevice = GlobalInfo.IsMainMachine; + DefaultDeviceInfoStruct.DeviceServerPort = GlobalInfo.DeviceServerPort; + DefaultDeviceInfoStruct.DeviceServerBuildTime = GlobalInfo.ServerBuildTime; + + if (LastTimeToOSVersionUpdated > Program.Config.IO.OperatingSystemVersionUpdateInterval) + { + LastTimeToOSVersionUpdated = 0; + DefaultDeviceInfoStruct.DeviceOSVersion = NetworkHelper.TryGetOSVersionString(); + } + + ++DeviceInfoStructUpdatedTimes; + ++LastTimeToOSVersionUpdated; + + if (DeviceInfoStructUpdatedTimes < 0) DeviceInfoStructUpdatedTimes = 0; + } + + /// + /// 多设备广播发送方法 + /// + private void MultiDevicesBroadCastSend() + { + var location = $"{nameof(DevicesDiscoveryServer)}.{nameof(MultiDevicesBroadCastSend)}"; + + IPEndPoint multicast = new( + IPAddress.Parse(Program.Config.Web.UDPBroadcastAddress), + Program.Config.Web.UDPPortReceive + ); + UdpSender?.Client.SetSocketOption( + SocketOptionLevel.Socket, + SocketOptionName.ReuseAddress, + true + ); + + var erroredInterfacesIndexes = new List(); + var erroredInterfacesIndexesTTL = 60; + + UdpSendTimer = new() + { + Interval = Program.Config.Web.UDPSendFrequency, + AutoReset = true + }; + UdpSendTimer.Elapsed += async (_, _) => + { + --erroredInterfacesIndexesTTL; + + if (erroredInterfacesIndexesTTL <= 0) + { + erroredInterfacesIndexesTTL = 60; + erroredInterfacesIndexes.Clear(); + } + + UpdateDefaultDeviceInfoStruct(); + + if (CloseDevicesDiscoveryServerRequest) + DefaultDeviceInfoStruct.SendTime -= TimeSpan.FromSeconds(20); + + var sendText = JsonSerializer.Serialize(DefaultDeviceInfoStruct); + var sendBytes = sendText.FromUTF8(); + + foreach (var item in SupportedNetworkInterfacesIndexes) + { + if (!GlobalInfo.Running || CloseDevicesDiscoveryServerRequest) break; + + // 如果错误网络适配器中存在当前项的记录, 跳过 + if (erroredInterfacesIndexes.Contains(item)) continue; + + try + { + UdpSender?.Client.SetSocketOption( + SocketOptionLevel.IP, + SocketOptionName.MulticastInterface, + item + ); + UdpSender?.Send(sendBytes, sendBytes.Length, multicast); + + // 将自定义广播消息全部发送 + while (Messages2BroadCast.Count > 0) + { + var messageBytes = Messages2BroadCast.Dequeue().FromUTF8(); + UdpSender?.Send(messageBytes, messageBytes.Length, multicast); + } + } + catch (Exception ex) + { + // 该网络适配器存在异常, 暂时记录到错误网络适配器中 + if (!erroredInterfacesIndexes.Contains(item)) + erroredInterfacesIndexes.Add(item); + + Log.Warning( + ex, + $"In {location}: Errored interface index: {item}, recorded." + ); + } + } + + if (CloseDevicesDiscoveryServerRequest) + CloseDevicesDiscoveryServerRequest = false; + + if (!GlobalInfo.Running || CloseDevicesDiscoveryServerRequest) await Stop(); + }; + UdpSendTimer.Start(); + } + + /// + /// 多设备广播接收方法 + /// + private void MultiDevicesBroadCastReceive() + { + var location = $"{nameof(DevicesDiscoveryServer)}.{nameof(MultiDevicesBroadCastReceive)}"; + + IPEndPoint multicast = new(IPAddress.Any, 0); + UdpReceiver?.Client.SetSocketOption( + SocketOptionLevel.Socket, + SocketOptionName.ReuseAddress, + true + ); + + new Thread(async () => + { + try + { + while (GlobalInfo.Running && !CloseDevicesDiscoveryServerRequest) + { + var bytes = UdpReceiver?.Receive(ref multicast); + var client = $"{multicast.Address}:{multicast.Port}"; + + if (bytes is null) continue; // null byte[] cause exception in next line. + + onReceive?.Invoke(bytes, null, client); + + var result = bytes.ToUTF8(); + + Log.Information($"UDP From: {client,-21}, Receive: {result}"); + + try + { + DevicesManager.Update( + JsonSerializer.Deserialize(result) + ); + } + catch (Exception ex) + { + Log.Warning(ex, $"When trying to deserialize `{result}`"); + } + } + } + catch (Exception e) + { + Log.Error(e, $"In {location}: {e.Message}"); + + Status = ServerStatus.Errored; + } + + await Stop(); + + }).Start(); + } + + private static void Init() + { + DeviceInfoStructUpdatedTimes = 0; + LastTimeToOSVersionUpdated = 0; + CloseDevicesDiscoveryServerRequest = false; + SupportedNetworkInterfacesIndexes.Clear(); + Messages2BroadCast.Clear(); + DefaultDeviceInfoStruct = NetworkHelper.GetDeviceInfo(); + } + + public async Task Start() + { + Status = ServerStatus.Starting; + + Init(); + + UdpSender = new(Program.Config.Web.UDPPortSend, AddressFamily.InterNetwork) + { + EnableBroadcast = true, + MulticastLoopback = true + }; + + UdpReceiver = new( + new IPEndPoint(IPAddress.Any, Program.Config.Web.UDPPortReceive) + ); + + await TasksManager.RunTaskAsync(() => + { + try + { + FindSupportNetworkInterfaces( + new() + { + UdpSender, UdpReceiver + }, + IPAddress.Parse(Program.Config.Web.UDPBroadcastAddress) + ); // 寻找所有支持的网络适配器 + } + catch (Exception ex) + { + var location = $"{nameof(DevicesServer)}.{nameof(Start)}"; + Log.Warning(ex, $"In {location}: {ex.Message}"); + } + }, nameof(FindSupportNetworkInterfaces)); + + await TasksManager.RunTaskAsync( + MultiDevicesBroadCastSend, + nameof(MultiDevicesBroadCastSend) + ); // 开始发送组播报文 + + await TasksManager.RunTaskAsync( + MultiDevicesBroadCastReceive, + nameof(MultiDevicesBroadCastReceive) + ); // 开始接收组播报文 + + Status = ServerStatus.Running; + + return this; + } + + public async Task Stop() + { + Status = ServerStatus.Stopping; + + await Task.Run(() => + { + CloseDevicesDiscoveryServerRequest = true; + + while (CloseDevicesDiscoveryServerRequest) { } + + UdpSendTimer?.Close(); + + UdpSender?.Close(); + UdpReceiver?.Close(); + }); + + Status = ServerStatus.Pending; + + return this; + } + + public async Task Restart() + { + await Task.Run(async () => + { + await Stop(); + await Start(); + }); + + return this; + } + + public async Task Send(byte[] content, string target) + { + await Task.Run(() => { }); + + return this; + } + + public async Task Broadcast(byte[] content) + { + await Task.Run(() => { }); + + return this; + } + + public async Task BroadCast(byte[] content, Func? pattern) + { + await Task.Run(() => { }); + + return this; + } + + /// + /// 设定当接收到数据时的处理代码 + /// + /// 处理代码, 参数一为接收到的数据 (byte[]), 参数二是数据发送者, ip:port + /// 设备自发现网络服务器本身 + public DevicesDiscoveryServer OnReceive(Action action) + { + onReceive = action; + + return this; + } + + public void Dispose() + { + UdpSender?.Dispose(); + UdpReceiver?.Dispose(); + + GC.SuppressFinalize(this); + } +} diff --git a/Network/DevicesServer.cs b/Network/DevicesServer.cs new file mode 100644 index 00000000..15d8c73d --- /dev/null +++ b/Network/DevicesServer.cs @@ -0,0 +1,253 @@ +using Common.BasicHelper.Utils.Extensions; +using KitX_Dashboard.Data; +using KitX_Dashboard.Interfaces.Network; +using KitX_Dashboard.Managers; +using KitX_Dashboard.Services; +using Serilog; +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +namespace KitX_Dashboard.Network; + +internal class DevicesServer : IKitXServer +{ + private static TcpListener? listener = null; + + private static bool keepListen = true; + + private static readonly Dictionary clients = new(); + + private static Action? onReceive = null; + + private static ServerStatus status = ServerStatus.Unknown; + + public static ServerStatus Status + { + get => status; + set + { + status = value; + + if (status == ServerStatus.Errored) + DevicesManager.Watch4MainDevice(); + } + } + + /// + /// 接收客户端 + /// + private static void AcceptClient() + { + var location = $"{nameof(DevicesServer)}.{nameof(AcceptClient)}"; + + try + { + while (keepListen && listener is not null) + { + if (listener.Pending()) + { + var client = listener.AcceptTcpClient(); + + if (client.Client.RemoteEndPoint is not IPEndPoint endpoint) continue; + + clients.Add(endpoint.ToString(), client); + + Log.Information($"New device connection: {endpoint}"); + + ReceiveMessage(client); + } + } + } + catch (Exception ex) + { + Log.Error(ex, $"In {nameof(location)}: {ex.Message}"); + } + } + + /// + /// 接收客户端消息 + /// + /// TcpClient + private static void ReceiveMessage(TcpClient client) + { + var location = $"{nameof(DevicesServer)}.{nameof(ReceiveMessage)}"; + + IPEndPoint? endpoint = null; + NetworkStream? stream = null; + + new Thread(async () => + { + try + { + endpoint = client?.Client.RemoteEndPoint as IPEndPoint; + + stream = client?.GetStream(); + + if (endpoint is null || stream is null) return; + + while (keepListen) + { + var buffer = new byte[Program.Config.Web.SocketBufferSize]; + + var length = await stream.ReadAsync(buffer); + + if (length > 0) + { + onReceive?.Invoke(buffer, length, endpoint.ToString()); + + var msg = buffer.ToUTF8(0, length); + + Log.Information($"From: {endpoint}\tReceive: {msg}"); + } + else break; // 客户端断开连接 跳出循环 + } + } + catch (Exception ex) + { + Log.Error($"In {location}: {ex.Message}"); + Log.Information($"Connection broke from: {endpoint}"); + } + finally + { + if (endpoint is not null) + { + clients.Remove(endpoint.ToString()); + } + + stream?.CloseAndDispose(); + + client.Dispose(); + } + }).Start(); + } + + public async Task Broadcast(byte[] content) + { + await Task.Run(() => + { + foreach (var client in clients) + client.Value.Client.Send(content); + }); + + return this; + } + + public async Task BroadCast(byte[] content, Func? pattern) + { + await Task.Run(() => + { + foreach (var client in clients) + { + if (pattern is not null && pattern.Invoke(client.Value)) + client.Value.Client.Send(content); + else client.Value.Client.Send(content); + } + }); + + return this; + } + + public async Task Send(byte[] content, string target) + { + await Task.Run(() => + { + if (clients.ContainsKey(target)) + clients[target].Client.Send(content); + }); + + return this; + } + + public DevicesServer OnReceive(Action action) + { + onReceive = action; + + return this; + } + + public async Task Start() + { + var location = $"{nameof(DevicesServer)}.{nameof(Start)}"; + + Status = ServerStatus.Pending; + + keepListen = true; + + await TasksManager.RunTaskAsync(() => + { + clients.Clear(); + + listener = new(IPAddress.Any, 0); + listener.Start(); + + var port = ((IPEndPoint)listener.LocalEndpoint).Port; // 取服务端口号 + + GlobalInfo.DeviceServerPort = port; // 全局端口号标明 + GlobalInfo.ServerBuildTime = DateTime.UtcNow; + GlobalInfo.IsMainMachine = true; + + Log.Information($"DevicesServer Port: {port}"); + + EventService.Invoke(nameof(EventService.DevicesServerPortChanged)); + + Status = ServerStatus.Running; + + new Thread(AcceptClient).Start(); + + }, location); + + return this; + } + + public async Task Stop() + { + var location = $"{nameof(DevicesServer)}.{nameof(Stop)}"; + + Status = ServerStatus.Stopping; + + await TasksManager.RunTaskAsync(() => + { + listener?.Stop(); + + keepListen = false; + + foreach (KeyValuePair item in clients) + { + item.Value.Close(); + item.Value.Dispose(); + } + + GlobalInfo.IsMainMachine = false; + GlobalInfo.DeviceServerPort = -1; + + EventService.Invoke(nameof(EventService.DevicesServerPortChanged)); + + Status = ServerStatus.Pending; + + }, location); + + return this; + } + + public async Task Restart() + { + var location = $"{nameof(DevicesServer)}.{nameof(Restart)}"; + + await TasksManager.RunTaskAsync(async () => + { + await Start(); + await Stop(); + }, location); + + return this; + } + + public void Dispose() + { + GC.SuppressFinalize(this); + } +} diff --git a/Network/NetworkHelper.cs b/Network/NetworkHelper.cs new file mode 100644 index 00000000..ef463c55 --- /dev/null +++ b/Network/NetworkHelper.cs @@ -0,0 +1,219 @@ +using Common.BasicHelper.Core.Shell; +using Common.BasicHelper.Utils.Extensions; +using KitX.Web.Rules; +using KitX_Dashboard.Converters; +using KitX_Dashboard.Data; +using Serilog; +using System; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; + +namespace KitX_Dashboard.Network; + +internal static class NetworkHelper +{ + /// + /// 检查网络适配器是否符合要求 + /// + /// 网络适配器 + /// 是否符合要求 + internal static bool CheckNetworkInterface + ( + NetworkInterface adapter, + IPInterfaceProperties adapterProperties + ) + { + var userPointed = Program.Config.Web.AcceptedNetworkInterfaces; + if (userPointed is not null) + if (userPointed.Contains(adapter.Name)) + return true; + else return false; + + if (adapter.NetworkInterfaceType != NetworkInterfaceType.Ethernet && + adapter.NetworkInterfaceType != NetworkInterfaceType.Wireless80211 + && + ( + adapterProperties.MulticastAddresses.Count == 0 || + // most of VPN adapters will be skipped + !adapter.SupportsMulticast || + // multicast is meaningless for this type of connection + OperationalStatus.Up != adapter.OperationalStatus || + // this adapter is off or not connected + !adapter.Supports(NetworkInterfaceComponent.IPv4) + ) + ) return false; + return true; + } + + /// + /// 将 IPv4 的十进制表示按点分制拆分 + /// + /// IPv4 的十进制表示 + /// 拆分 + internal static (int, int, int, int) IPv4_2_4Parts(string ip) + { + string[] p = ip.Split('.'); + int a = int.Parse(p[0]), b = int.Parse(p[1]), c = int.Parse(p[2]), d = int.Parse(p[3]); + return (a, b, c, d); + } + + /// + /// 获取本机内网 IPv4 地址 + /// + /// 使用点分十进制表示法的本机内网IPv4地址 + internal static string GetInterNetworkIPv4() + { + try + { + return (from ip in Dns.GetHostEntry(Dns.GetHostName()).AddressList + where ip.AddressFamily == AddressFamily.InterNetwork + && !ip.ToString().Equals("127.0.0.1") + && (ip.ToString().StartsWith("192.168") // 192.168.x.x + || ip.ToString().StartsWith("10") // 10.x.x.x + || IPv4_2_4Parts(ip.ToString()).Item1 == 172 // 172.16-31.x.x + && IPv4_2_4Parts(ip.ToString()).Item2 >= 16 + && IPv4_2_4Parts(ip.ToString()).Item2 <= 31) + && ip.ToString().StartsWith(Program.Config.Web.IPFilter) // 满足自定义规则 + select ip).First().ToString(); + } + catch (Exception ex) + { + var location = $"{nameof(NetworkHelper)}.{nameof(GetInterNetworkIPv4)}"; + Log.Warning(ex, $"In {location}: {ex.Message}"); + return string.Empty; + } + } + + /// + /// 获取本机内网 IPv6 地址 + /// + /// IPv6 地址 + internal static string GetInterNetworkIPv6() + { + try + { + return (from ip in Dns.GetHostEntry(Dns.GetHostName()).AddressList + where ip.AddressFamily == AddressFamily.InterNetworkV6 + && !ip.ToString().Equals("::1") + select ip).First().ToString(); + } + catch (Exception ex) + { + var location = $"{nameof(NetworkHelper)}.{nameof(GetInterNetworkIPv6)}"; + Log.Warning(ex, $"In {location}: {ex.Message}"); + return string.Empty; + } + } + + /// + /// 尝试获取设备 MAC 地址 + /// + /// MAC 地址 + internal static string? TryGetDeviceMacAddress() + { + try + { + var mac = NetworkInterface.GetAllNetworkInterfaces() + .Where(nic => nic.OperationalStatus == OperationalStatus.Up + && nic.NetworkInterfaceType != NetworkInterfaceType.Loopback) + .Select(nic => nic.GetPhysicalAddress().ToString()).FirstOrDefault(); + + return mac?.SeparateGroup(2, sb => sb.Append(':')); + } + catch (Exception ex) + { + var location = $"{nameof(NetworkHelper)}.{nameof(TryGetDeviceMacAddress)}"; + Log.Warning(ex, $"In {location}: {ex.Message}"); + return string.Empty; + } + } + + /// + /// 尝试获取系统版本 + /// + /// 系统版本 + internal static string? TryGetOSVersionString() + { + var result = Environment.OSVersion.VersionString; + try + { + switch (OperatingSystem2Enum.GetOSType()) + { + case OperatingSystems.Linux: + + var versionFilePath = "/etc/os-release"; + var versionSegament = "PRETTY_NAME"; + var needFindInIssue = false; + + if (File.Exists(versionFilePath)) + { + var osRelease = File.ReadAllLines(versionFilePath) + .Select(line => line.Split('=')) + .ToDictionary + ( + parts => parts[0], + parts => parts[1].Trim('"') + ); + + if (osRelease.TryGetValue(versionSegament, out var version)) + result = version; + else needFindInIssue = true; + } + + if (needFindInIssue) + { + var issueFilePath = "/etc/issue"; + if (File.Exists(issueFilePath)) + { + var issue = File.ReadAllText(issueFilePath); + var lines = issue.Split('\n'); + result = lines.First(x => !x.Equals(string.Empty)); + } + } + + break; + case OperatingSystems.MacOS: + var command = "sw_vers"; + + var productName = command.ExecuteAsCommand("-productName"); + var productVersion = command.ExecuteAsCommand("-productVersion"); + var buildVersion = command.ExecuteAsCommand("-buildVersion"); + + if (productName is not null && productVersion is not null && buildVersion is not null) + result = $"{productName} {productVersion} {buildVersion}" + .Replace("\n", ""); + + break; + } + } + catch (Exception ex) + { + var location = $"{nameof(NetworkHelper)}.{nameof(TryGetOSVersionString)}"; + Log.Error(ex, $"In {location}: {ex.Message}"); + } + return result; + } + + /// + /// 获取设备信息 + /// + /// 设备信息结构体 + internal static DeviceInfoStruct GetDeviceInfo() => new() + { + DeviceName = Environment.MachineName, + DeviceMacAddress = TryGetDeviceMacAddress(), + IsMainDevice = GlobalInfo.IsMainMachine, + SendTime = DateTime.UtcNow, + DeviceOSType = OperatingSystem2Enum.GetOSType(), + DeviceOSVersion = TryGetOSVersionString(), + IPv4 = GetInterNetworkIPv4(), + IPv6 = GetInterNetworkIPv6(), + PluginServerPort = GlobalInfo.PluginServerPort, + DeviceServerPort = GlobalInfo.DeviceServerPort, + DeviceServerBuildTime = new(), + PluginsCount = Program.PluginCards.Count, + }; +} diff --git a/Network/PluginsServer.cs b/Network/PluginsServer.cs new file mode 100644 index 00000000..ace9a0bf --- /dev/null +++ b/Network/PluginsServer.cs @@ -0,0 +1,234 @@ +using Common.BasicHelper.Utils.Extensions; +using KitX_Dashboard.Data; +using KitX_Dashboard.Interfaces.Network; +using KitX_Dashboard.Managers; +using KitX_Dashboard.Services; +using Serilog; +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +namespace KitX_Dashboard.Network; + +internal class PluginsServer : IKitXServer +{ + private static TcpListener? listener = null; + + private static bool keepListen = true; + + private static readonly Dictionary clients = new(); + + private static Action? onReceive = null; + + public static ServerStatus? Status { get; set; } = ServerStatus.Unknown; + + /// + /// 接收客户端 + /// + private static void AcceptClient() + { + var location = $"{nameof(PluginsServer)}.{nameof(AcceptClient)}"; + + try + { + while (keepListen && listener is not null) + { + if (listener.Pending()) + { + var client = listener.AcceptTcpClient(); + + if (client.Client.RemoteEndPoint is not IPEndPoint endpoint) continue; + + clients.Add(endpoint.ToString(), client); + + Log.Information($"New plugin connection: {endpoint}"); + + ReceiveMessage(client); + } + } + } + catch (Exception ex) + { + Log.Error(ex, $"In {location}: {ex.Message}"); + } + } + + /// + /// 接收消息 + /// + /// TcpClient + private static void ReceiveMessage(TcpClient client) + { + var location = $"{nameof(PluginsServer)}.{nameof(ReceiveMessage)}"; + + IPEndPoint? endpoint = null; + NetworkStream? stream = null; + + new Thread(async () => + { + try + { + endpoint = client.Client.RemoteEndPoint as IPEndPoint; + + stream = client.GetStream(); + + if (endpoint is null || stream is null) return; + + while (keepListen) + { + var buffer = new byte[Program.Config.Web.SocketBufferSize]; + + var length = stream is null ? 0 : await stream.ReadAsync(buffer); + + if (length > 0) + { + onReceive?.Invoke(buffer, length, endpoint.ToString()); + + var msg = buffer.ToUTF8(0, length); + + Log.Information($"From: {endpoint}\tReceive: {msg}"); + + if (msg.StartsWith("PluginStruct: ")) + { + PluginsManager.Execute(msg[14..], endpoint); + + var workPath = Program.Config.App.LocalPluginsDataFolder.GetFullPath(); + var sendtxt = $"WorkPath: {workPath}"; + var bytes = sendtxt.FromUTF8(); + + stream?.Write(bytes, 0, bytes.Length); + } + + //发送到其他客户端 + //foreach (KeyValuePair kvp in clients) + //{ + // if (kvp.Value != client) + // { + // byte[] writeData = Encoding.UTF8.GetBytes(msg); + // NetworkStream writeStream = kvp.Value.GetStream(); + // writeStream.Write(writeData, 0, writeData.Length); + // } + //} + } + else break; //客户端断开连接 跳出循环 + } + } + catch (Exception ex) + { + Log.Error(ex, $"In {location}: {ex.Message}"); + Log.Information($"Connection broke from: {endpoint}"); + } + finally + { + if (endpoint is not null) + { + PluginsManager.Disconnect(endpoint); + + clients.Remove(endpoint.ToString()); + } + + stream?.CloseAndDispose(); + + client.Dispose(); + } + }).Start(); + } + + private static void Init() + { + clients.Clear(); + + var port = Program.Config.Web.UserSpecifiedPluginsServerPort; + + if (port < 0 || port > 65535) port = null; + + listener = new(IPAddress.Any, port ?? 0); + } + + public async Task Broadcast(byte[] content) + { + await Task.Run(() => { }); + + return this; + } + + public async Task BroadCast(byte[] content, Func? pattern) + { + await Task.Run(() => { }); + + return this; + } + + public async Task Send(byte[] content, string target) + { + await Task.Run(() => { }); + + return this; + } + + public PluginsServer OnReceive(Action action) + { + onReceive = action; + + return this; + } + + public async Task Start() + { + await TasksManager.RunTaskAsync(() => + { + Init(); + + if (listener is null) return; + + listener.Start(); + + var port = ((IPEndPoint)listener.LocalEndpoint).Port; // 取服务端口号 + + GlobalInfo.PluginServerPort = port; // 全局端口号标明 + + EventService.Invoke(nameof(EventService.PluginsServerPortChanged)); + + Log.Information($"PluginsServer Port: {port}"); + + new Thread(AcceptClient).Start(); + }); + + return this; + } + + public async Task Stop() + { + keepListen = false; + + await Task.Run(() => + { + foreach (KeyValuePair item in clients) + { + item.Value.Close(); + item.Value.Dispose(); + } + }); + + return this; + } + + public async Task Restart() + { + await Task.Run(() => { }); + + return this; + } + + public void Dispose() + { + keepListen = false; + + listener?.Stop(); + + GC.SuppressFinalize(this); + } +} diff --git a/Network/Status.cs b/Network/Status.cs new file mode 100644 index 00000000..e451badb --- /dev/null +++ b/Network/Status.cs @@ -0,0 +1,73 @@ +namespace KitX_Dashboard.Network; + +/// +/// 服务器状态 +/// +internal enum ServerStatus +{ + /// + /// 未知状态 + /// + Unknown = 0, + + /// + /// 启动中 + /// + Starting = 1, + + /// + /// 运行中 + /// + Running = 2, + + /// + /// 停止中 + /// + Stopping = 3, + + /// + /// 等待中 + /// + Pending = 4, + + /// + /// 发生错误 + /// + Errored = 5, +} + +/// +/// 客户端状态 +/// +internal enum ClientStatus +{ + /// + /// 未知状态 + /// + Unknown = 0, + + /// + /// 连接中 + /// + Connecting = 1, + + /// + /// 运行中 + /// + Running = 2, + + /// + /// 断开连接中 + /// + Disconnecting = 3, + + /// + /// 等待中 + /// + Pending = 4, + + /// + /// 发生错误 + /// + Errored = 5, +} diff --git a/Servers/DevicesServer.cs b/Servers/DevicesServer.cs deleted file mode 100644 index ced8ec74..00000000 --- a/Servers/DevicesServer.cs +++ /dev/null @@ -1,889 +0,0 @@ -using Common.BasicHelper.Core.Shell; -using Common.BasicHelper.Utils.Extensions; -using KitX.Web.Rules; -using KitX_Dashboard.Converters; -using KitX_Dashboard.Data; -using KitX_Dashboard.Managers; -using KitX_Dashboard.Names; -using KitX_Dashboard.Services; -using Serilog; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.NetworkInformation; -using System.Net.Sockets; -using System.Text; -using System.Text.Json; -using System.Threading; - -namespace KitX_Dashboard.Servers; - -internal class DevicesServer : IDisposable -{ - - public DevicesServer() - { - - } - - public void Start() - { - new Thread(() => - { - try - { - CloseDevicesServerUDPNetworkRequest = false; - - UdpClient_Send = new(Program.Config.Web.UDPPortSend, - AddressFamily.InterNetwork) - { - EnableBroadcast = true, - MulticastLoopback = true - }; - - UdpClient_Receive = new(new IPEndPoint(IPAddress.Any, - Program.Config.Web.UDPPortReceive)); - - // 寻找所有支持的网络适配器 - Log.Information($"Start {nameof(FindSupportNetworkInterfaces)}"); - try - { - FindSupportNetworkInterfaces(new() - { - UdpClient_Send, UdpClient_Receive - }, IPAddress.Parse(Program.Config.Web.UDPBroadcastAddress)); - } - catch (Exception ex) - { - var location = $"{nameof(DevicesServer)}.{nameof(Start)}"; - Log.Warning(ex, $"In {location}: {ex.Message}"); - } - - #region 组播收发消息 - - // 开始组播发送本机信息 - Log.Information($"Start {nameof(MultiDevicesBroadCastSend)}"); - MultiDevicesBroadCastSend(); - - // 开始接收组播消息 - Log.Information($"Start {nameof(MultiDevicesBroadCastReceive)}"); - MultiDevicesBroadCastReceive(); - - #endregion - } - catch (Exception e) - { - var location = $"{nameof(DevicesServer)}.{nameof(Start)}()"; - Log.Error(e, $"In {location}: {e.Message}"); - } - - try - { - Log.Information($"Start Init {nameof(DevicesHost)}"); - // 初始化自组网 - DevicesHost = new(); - } - catch (Exception ex) - { - Log.Error(ex, $"In {nameof(DevicesServer)}, " + - $"Init {nameof(DevicesHost)}"); - } - }).Start(); - } - - public void Stop() - { - CloseDevicesServerUDPNetworkRequest = true; - - keepListen = false; - - foreach (KeyValuePair item in clients) - { - item.Value.Close(); - item.Value.Dispose(); - } - - acceptDeviceThread?.Join(); - - DevicesHost?.Close(); - DevicesHost?.Dispose(); - } - - #region UDP Socket 服务于自发现自组网 - - private static readonly List SurpportedNetworkInterfaces = new(); - - internal static readonly Queue Messages2BroadCast = new(); - - internal static DeviceInfoStruct DefaultDeviceInfoStruct = GetDeviceInfo(); - - internal static bool CloseDevicesServerUDPNetworkRequest = false; - - /// - /// UDP 发包客户端 - /// - private static UdpClient? UdpClient_Send = null; - - /// - /// UDP 收包客户端 - /// - private static UdpClient? UdpClient_Receive = null; - - /// - /// 检查网络适配器是否符合要求 - /// - /// 网络适配器 - /// 是否符合要求 - private static bool CheckNetworkInterface( - NetworkInterface adapter, IPInterfaceProperties adapterProperties) - { - var userPointed = Program.Config.Web.AcceptedNetworkInterfaces; - if (userPointed is not null) - if (userPointed.Contains(adapter.Name)) - return true; - else return false; - - if ( - - adapter.NetworkInterfaceType != NetworkInterfaceType.Ethernet && - adapter.NetworkInterfaceType != NetworkInterfaceType.Wireless80211 - - && - ( - adapterProperties.MulticastAddresses.Count == 0 || - // most of VPN adapters will be skipped - !adapter.SupportsMulticast || - // multicast is meaningless for this type of connection - OperationalStatus.Up != adapter.OperationalStatus || - // this adapter is off or not connected - !adapter.Supports(NetworkInterfaceComponent.IPv4) - ) - ) return false; - return true; - } - - /// - /// 寻找受支持的网络适配器并把UDP客户端加入组播 - /// - private static void FindSupportNetworkInterfaces(List clients, IPAddress multicastAddress) - { - var multicastGroupJoinedInterfacesCount = 0; - - foreach (var adapter in NetworkInterface.GetAllNetworkInterfaces()) - { - var adapterProperties = adapter.GetIPProperties(); - if (adapterProperties is null) continue; - - try - { - var logs = adapter.Dump2Lines(); - for (int i = 0; i < logs.Length; i++) - Log.Information(logs[i]); - } - catch (Exception ex) - { - Log.Warning(ex, "Logging network interface items."); - } - - if (!CheckNetworkInterface(adapter, adapterProperties)) continue; - - var unicastIPAddresses = adapterProperties.UnicastAddresses; - if (unicastIPAddresses is null) continue; - - var p = adapterProperties.GetIPv4Properties(); - if (p is null) continue; // IPv4 is not configured on this adapter - - SurpportedNetworkInterfaces.Add(IPAddress.HostToNetworkOrder(p.Index)); - - foreach (var ipAddress in unicastIPAddresses - .Select(x => x.Address) - .Where(x => x.AddressFamily == AddressFamily.InterNetwork)) - { - try - { - foreach (var udpClient in clients) - udpClient?.JoinMulticastGroup(multicastAddress, ipAddress); - - Program.WebManager?.NetworkInterfaceRegistered?.Add(adapter.Name); - - ++multicastGroupJoinedInterfacesCount; - } - catch (Exception ex) - { - var location = $"{nameof(DevicesServer)}.{nameof(FindSupportNetworkInterfaces)}"; - Log.Error(ex, $"In {location}: {ex.Message}"); - } - } - } - - Program.TasksManager?.RaiseSignal(nameof(SignalsNames.FinishedFindingNetworkInterfacesSignal)); - - Log.Information($"" + - $"Find {SurpportedNetworkInterfaces.Count} supported network interfaces."); - Log.Information($"" + - $"Joined {multicastGroupJoinedInterfacesCount} multicast groups."); - } - - /// - /// 多设备广播发送方法 - /// - public static void MultiDevicesBroadCastSend() - { - #region 初始化 UDP 客户端 - - UdpClient? udpClient = UdpClient_Send; - IPEndPoint multicast = - new(IPAddress.Parse(Program.Config.Web.UDPBroadcastAddress), - Program.Config.Web.UDPPortReceive); - udpClient?.Client.SetSocketOption(SocketOptionLevel.Socket, - SocketOptionName.ReuseAddress, true); - - #endregion - - var erroredInterfacesIndexes = new List(); - var erroredInterfacesIndexesTTL = 60; - - System.Timers.Timer timer = new() - { - Interval = Program.Config.Web.UDPSendFrequency, - AutoReset = true - }; - void EndMultiDevicesBroadCastSend() - { - timer.Stop(); - timer.Dispose(); - udpClient?.Close(); - } - timer.Elapsed += (_, _) => - { - try - { - --erroredInterfacesIndexesTTL; - if (erroredInterfacesIndexesTTL <= 0) - { - erroredInterfacesIndexesTTL = 60; - erroredInterfacesIndexes.Clear(); - } - - UpdateDefaultDeviceInfoStruct(); - - if (CloseDevicesServerUDPNetworkRequest) - DefaultDeviceInfoStruct.SendTime -= TimeSpan.FromSeconds(20); - - string sendText = JsonSerializer.Serialize(DefaultDeviceInfoStruct); - byte[] sendBytes = Encoding.UTF8.GetBytes(sendText); - - foreach (var item in SurpportedNetworkInterfaces) - { - // 如果错误网络适配器中存在当前项的记录, 跳过 - if (erroredInterfacesIndexes.Contains(item)) continue; - - try - { - udpClient?.Client.SetSocketOption(SocketOptionLevel.IP, - SocketOptionName.MulticastInterface, item); - udpClient?.Send(sendBytes, sendBytes.Length, multicast); - - // 将自定义广播消息全部发送 - while (Messages2BroadCast.Count > 0) - { - byte[] messageBytes = Encoding.UTF8.GetBytes(Messages2BroadCast.Dequeue()); - udpClient?.Send(messageBytes, messageBytes.Length, multicast); - } - } - catch (Exception ex) - { - // 该网络适配器存在异常, 暂时记录到错误网络适配器中 - if (!erroredInterfacesIndexes.Contains(item)) - erroredInterfacesIndexes.Add(item); - - var location = $"{nameof(DevicesServer)}.{nameof(MultiDevicesBroadCastSend)}"; - Log.Warning(ex, $"In {location}: {ex.Message} - " + - $"On interface index: {item}, recorded."); - } - } - } - catch (Exception e) - { - Log.Error(e, $"In MultiDevicesBroadCastSend: {e.Message}"); - } - if (!GlobalInfo.Running || CloseDevicesServerUDPNetworkRequest) EndMultiDevicesBroadCastSend(); - }; - timer.Start(); - } - - /// - /// 多设备广播接收方法 - /// - public static void MultiDevicesBroadCastReceive() - { - #region 初始化 UDP 客户端 - - UdpClient? udpClient = UdpClient_Receive; - IPEndPoint multicast = new(IPAddress.Any, 0); - udpClient?.Client.SetSocketOption(SocketOptionLevel.Socket, - SocketOptionName.ReuseAddress, true); - - #endregion - - new Thread(() => - { - try - { - while (GlobalInfo.Running && !CloseDevicesServerUDPNetworkRequest) - { - var bytes = udpClient?.Receive(ref multicast); - if (bytes is null) continue; // null byte[] cause exception in next line. - var result = Encoding.UTF8.GetString(bytes); - if (result is null) continue; // null string skip. - var client = $"{multicast.Address}:{multicast.Port}"; - Log.Information($"UDP " + - $"From: {client,-21}, " + - $"Receive: {result}"); - try - { - DeviceInfoStruct deviceInfo - = JsonSerializer.Deserialize(result); - DevicesManager.Update(deviceInfo); - } - catch (Exception ex) - { - Log.Warning(ex, $"{ex.Message}"); - } - } - udpClient?.Close(); - } - catch (Exception e) - { - Log.Error(e, e.Message); - } - if (!GlobalInfo.Running) - { - udpClient?.Close(); - } - else - { - - } - }).Start(); - } - - /// - /// 将 IPv4 的十进制表示按点分制拆分 - /// - /// IPv4 的十进制表示 - /// 拆分 - private static (int, int, int, int) IPv4_2_4Parts(string ip) - { - string[] p = ip.Split('.'); - int a = int.Parse(p[0]), b = int.Parse(p[1]), c = int.Parse(p[2]), d = int.Parse(p[3]); - return (a, b, c, d); - } - - /// - /// 获取本机内网 IPv4 地址 - /// - /// 使用点分十进制表示法的本机内网IPv4地址 - private static string GetInterNetworkIPv4() - { - try - { - return (from ip in Dns.GetHostEntry(Dns.GetHostName()).AddressList - where ip.AddressFamily == AddressFamily.InterNetwork - && !ip.ToString().Equals("127.0.0.1") - && (ip.ToString().StartsWith("192.168") // 192.168.x.x - || ip.ToString().StartsWith("10") // 10.x.x.x - || IPv4_2_4Parts(ip.ToString()).Item1 == 172 // 172.16-31.x.x - && IPv4_2_4Parts(ip.ToString()).Item2 >= 16 - && IPv4_2_4Parts(ip.ToString()).Item2 <= 31) - && ip.ToString().StartsWith(Program.Config.Web.IPFilter) // 满足自定义规则 - select ip).First().ToString(); - } - catch (Exception ex) - { - var location = $"{nameof(DevicesServer)}.{nameof(GetInterNetworkIPv4)}"; - Log.Warning(ex, $"In {location}: {ex.Message}"); - return string.Empty; - } - } - - /// - /// 获取本机内网 IPv6 地址 - /// - /// IPv6 地址 - private static string GetInterNetworkIPv6() - { - try - { - return (from ip in Dns.GetHostEntry(Dns.GetHostName()).AddressList - where ip.AddressFamily == AddressFamily.InterNetworkV6 - && !ip.ToString().Equals("::1") - select ip).First().ToString(); - } - catch (Exception ex) - { - var location = $"{nameof(DevicesServer)}.{nameof(GetInterNetworkIPv6)}"; - Log.Warning(ex, $"In {location}: {ex.Message}"); - return string.Empty; - } - } - - /// - /// 尝试获取设备 MAC 地址 - /// - /// MAC 地址 - private static string? TryGetDeviceMacAddress() - { - try - { - var mac = NetworkInterface.GetAllNetworkInterfaces() - .Where(nic => nic.OperationalStatus == OperationalStatus.Up - && nic.NetworkInterfaceType != NetworkInterfaceType.Loopback) - .Select(nic => nic.GetPhysicalAddress().ToString()).FirstOrDefault(); - - return mac?.SeparateGroup(2, sb => sb.Append(':')); - } - catch (Exception ex) - { - var location = $"{nameof(DevicesServer)}.{nameof(TryGetDeviceMacAddress)}"; - Log.Warning(ex, $"In {location}: {ex.Message}"); - return string.Empty; - } - } - - /// - /// 尝试获取系统版本 - /// - /// 系统版本 - private static string? TryGetOSVersionString() - { - var result = Environment.OSVersion.VersionString; - try - { - switch (OperatingSystem2Enum.GetOSType()) - { - case OperatingSystems.Linux: - - var versionFilePath = "/etc/os-release"; - var versionSegament = "PRETTY_NAME"; - var needFindInIssue = false; - - if (File.Exists(versionFilePath)) - { - var osRelease = File.ReadAllLines(versionFilePath) - .Select(line => line.Split('=')) - .ToDictionary - ( - parts => parts[0], - parts => parts[1].Trim('"') - ); - - if (osRelease.TryGetValue(versionSegament, out var version)) - result = version; - else needFindInIssue = true; - } - - if (needFindInIssue) - { - var issueFilePath = "/etc/issue"; - if (File.Exists(issueFilePath)) - { - var issue = File.ReadAllText(issueFilePath); - var lines = issue.Split('\n'); - result = lines.First(x => !x.Equals(string.Empty)); - } - } - - break; - case OperatingSystems.MacOS: - var command = "sw_vers"; - - var productName = command.ExecuteAsCommand("-productName"); - var productVersion = command.ExecuteAsCommand("-productVersion"); - var buildVersion = command.ExecuteAsCommand("-buildVersion"); - - if (productName is not null && productVersion is not null && buildVersion is not null) - result = $"{productName} {productVersion} {buildVersion}" - .Replace("\n", ""); - - break; - } - } - catch (Exception ex) - { - var location = $"{nameof(DevicesServer)}.{nameof(TryGetOSVersionString)}"; - Log.Error(ex, $"In {location}: {ex.Message}"); - } - return result; - } - - /// - /// 获取设备信息 - /// - /// 设备信息结构体 - private static DeviceInfoStruct GetDeviceInfo() => new() - { - DeviceName = Environment.MachineName, - DeviceMacAddress = TryGetDeviceMacAddress(), - IsMainDevice = GlobalInfo.IsMainMachine, - SendTime = DateTime.UtcNow, - DeviceOSType = OperatingSystem2Enum.GetOSType(), - DeviceOSVersion = TryGetOSVersionString(), - IPv4 = GetInterNetworkIPv4(), - IPv6 = GetInterNetworkIPv6(), - PluginServerPort = GlobalInfo.PluginServerPort, - DeviceServerPort = GlobalInfo.DeviceServerPort, - DeviceServerBuildTime = new(), - PluginsCount = Program.PluginCards.Count, - }; - - private static int DeviceInfoStructUpdatedTimes = 0; - - private static int LastTimeToOSVersionUpdated = 0; - - /// - /// 更新默认设备信息结构 - /// - private static void UpdateDefaultDeviceInfoStruct() - { - DefaultDeviceInfoStruct.IsMainDevice = GlobalInfo.IsMainMachine; - DefaultDeviceInfoStruct.SendTime = DateTime.UtcNow; - DefaultDeviceInfoStruct.IPv4 = GetInterNetworkIPv4(); - DefaultDeviceInfoStruct.IPv6 = GetInterNetworkIPv6(); - DefaultDeviceInfoStruct.PluginServerPort = GlobalInfo.PluginServerPort; - DefaultDeviceInfoStruct.PluginsCount = Program.PluginCards.Count; - DefaultDeviceInfoStruct.IsMainDevice = GlobalInfo.IsMainMachine; - DefaultDeviceInfoStruct.DeviceServerPort = GlobalInfo.DeviceServerPort; - DefaultDeviceInfoStruct.DeviceServerBuildTime = GlobalInfo.ServerBuildTime; - - if (LastTimeToOSVersionUpdated > Program.Config.IO.OperatingSystemVersionUpdateInterval) - { - LastTimeToOSVersionUpdated = 0; - DefaultDeviceInfoStruct.DeviceOSVersion = TryGetOSVersionString(); - } - - ++DeviceInfoStructUpdatedTimes; - ++LastTimeToOSVersionUpdated; - - if (DeviceInfoStructUpdatedTimes < 0) DeviceInfoStructUpdatedTimes = 0; - } - - #endregion - - #region TCP Socket 服务于设备间组网 - - internal Thread? acceptDeviceThread; - internal Thread? receiveMessageThread; - internal TcpClient? DevicesHost; - internal TcpListener? listener; - internal bool keepListen = true; - - public readonly Dictionary clients = new(); - - /// - /// 建立主控网络 - /// - internal void BuildServer() - { - listener = new(IPAddress.Any, 0); - acceptDeviceThread = new(AcceptClient); - - listener.Start(); - - var port = ((IPEndPoint)listener.LocalEndpoint).Port; // 取服务端口号 - - GlobalInfo.DeviceServerPort = port; // 全局端口号标明 - GlobalInfo.ServerBuildTime = DateTime.UtcNow; - GlobalInfo.IsMainMachine = true; - - Log.Information($"DevicesServer Port: {port}"); - - acceptDeviceThread.Start(); - - EventService.Invoke(nameof(EventService.DevicesServerPortChanged)); - } - - /// - /// 取消建立主控网络 - /// - internal void CancleBuildServer() - { - GlobalInfo.IsMainMachine = false; - GlobalInfo.DeviceServerPort = -1; - - EventService.Invoke(nameof(EventService.DevicesServerPortChanged)); - - keepListen = false; - acceptDeviceThread?.Join(); - - DevicesHost?.Close(); - DevicesHost?.Dispose(); - - DevicesManager.Watch4MainDevice(); // 取消建立之后重新寻找并加入主控网络 - } - - /// - /// 接收客户端 - /// - internal void AcceptClient() - { - try - { - while (keepListen) - { - if (listener != null && listener.Pending()) - { - TcpClient client = listener.AcceptTcpClient(); - if (client.Client.RemoteEndPoint is IPEndPoint endpoint) - { - clients.Add(endpoint.ToString(), client); - - Log.Information($"New device connection: {endpoint}"); - - // 新建并运行接收消息线程 - new Thread(() => - { - try - { - ReceiveMessage(client); - } - catch (Exception e) - { - Log.Error(e, - "In DevicesServer.AcceptClient().ReceiveMessageFromHost()"); - } - }).Start(); - } - } - else - { - Thread.Sleep(100); - } - } - } - catch (Exception ex) - { - Log.Error($"In AcceptClient() : {ex.Message}"); - } - } - - /// - /// 向客户端发送消息 - /// - /// 消息内容 - /// 客户端 - internal void SendMessage(string msg, string client) - { - if (clients.ContainsKey(client)) - clients[client].Client.Send(Encoding.UTF8.GetBytes(msg)); - } - - /// - /// 接收客户端消息 - /// - /// TcpClient - private async void ReceiveMessage(object obj) - { - TcpClient? client = obj as TcpClient; - IPEndPoint? endpoint = null; - NetworkStream? stream = null; - - try - { - endpoint = client?.Client.RemoteEndPoint as IPEndPoint; - stream = client?.GetStream(); - - while (keepListen && stream != null) - { - byte[] data = new byte[Program.Config.Web.SocketBufferSize]; - - //如果远程主机已关闭连接,Read将立即返回零字节 - //int length = await stream.ReadAsync(data, 0, data.Length); - - int length = await stream.ReadAsync(data); - - if (length > 0) - { - string msg = Encoding.UTF8.GetString(data, 0, length); - - Log.Information($"From: {endpoint}\tReceive: {msg}"); - - - //发送到其他客户端 - //foreach (KeyValuePair kvp in clients) - //{ - // if (kvp.Value != client) - // { - // byte[] writeData = Encoding.UTF8.GetBytes(msg); - // NetworkStream writeStream = kvp.Value.GetStream(); - // writeStream.Write(writeData, 0, writeData.Length); - // } - //} - } - else - { - - break; //客户端断开连接 跳出循环 - } - } - } - catch (Exception ex) - { - Log.Error($"Error: In ReceiveMessageFromHost() : {ex.Message}"); - Log.Information($"Connection broke from: {endpoint}"); - - //Read是阻塞方法 客户端退出是会引发异常 释放资源 结束此线程 - } - finally - { - //释放资源 - if (endpoint != null) - { - clients.Remove(endpoint.ToString()); - } - - stream?.Close(); - stream?.Dispose(); - client?.Dispose(); - } - } - - /// - /// 向所有客户端广播消息 - /// - /// 消息 - internal void BroadCastMessage(string msg, Func? pattern) - { - foreach (var client in clients) - { - if (pattern is not null && pattern.Invoke(client.Value)) - client.Value.Client.Send(Encoding.UTF8.GetBytes(msg)); - else client.Value.Client.Send(Encoding.UTF8.GetBytes(msg)); - } - } - - /// - /// 加入主控网络 - /// - /// 主控地址 - /// 主控端口 - internal void AttendServer(string serverAddress, int serverPort) - { - try - { - DevicesHost?.Connect(serverAddress, serverPort); - - keepListen = true; - - receiveMessageThread = new(ReceiveMessageFromHost); - - receiveMessageThread.Start(); - - Log.Information($"Attending Server -> {serverAddress}:{serverPort}"); - } - catch (Exception ex) - { - var location = $"{nameof(DevicesServer)}.{nameof(AttendServer)}"; - - Log.Error(ex, $"In {location}: {ex.Message}"); - } - } - - /// - /// 向主控发送消息 - /// - /// 消息内容 - internal void SendMessageToHost(string msg) - { - try - { - var stream = DevicesHost?.GetStream(); - - if (stream is null) return; - - var data = Encoding.UTF8.GetBytes(msg); - - stream.Write(data, 0, data.Length); - stream.Flush(); - - //DevicesHost?.Client.Send(Encoding.UTF8.GetBytes(msg)); - - Log.Information($"Sent Message to Host, msg: {msg}"); - } - catch (Exception e) - { - var location = $"{nameof(DevicesServer)}.{nameof(SendMessageToHost)}"; - - Log.Error(e, $"In {location}: {e.Message}"); - - Program.WebManager?.Restart(restartPluginsServer: false); - } - } - - /// - /// 从主控接收消息 - /// - internal async void ReceiveMessageFromHost() - { - if (DevicesHost is null) return; - - var stream = DevicesHost?.GetStream(); - - if (stream is null) return; - - var buffer = new byte[Program.Config.Web.SocketBufferSize]; // Default 10 MB buffer - - try - { - while (keepListen) - { - - if (buffer is null) break; - - var length = await stream.ReadAsync(buffer); - - if (length > 0) - { - var msg = Encoding.UTF8.GetString(buffer, 0, length); - - Log.Information($"Receive from Host: {msg}"); - } - else - { - keepListen = false; - break; - } - } - - stream.Close(); - stream.Dispose(); - - Log.Information($"Closing `{nameof(ReceiveMessageFromHost)}` thread."); - - Program.WebManager?.Restart(restartPluginsServer: false); - } - catch (Exception e) - { - var location = $"{nameof(DevicesServer)}.{nameof(ReceiveMessageFromHost)}"; - Log.Error(e, $"In {location}: {e.Message}"); - - stream.Close(); - stream.Dispose(); - - DevicesHost?.Close(); - DevicesHost?.Dispose(); - - Program.WebManager?.Restart(restartPluginsServer: false); - } - } - - #endregion - - public void Dispose() - { - GC.SuppressFinalize(this); - } - -} diff --git a/Servers/PluginsServer.cs b/Servers/PluginsServer.cs deleted file mode 100644 index e43d3f3e..00000000 --- a/Servers/PluginsServer.cs +++ /dev/null @@ -1,200 +0,0 @@ -using KitX_Dashboard.Data; -using KitX_Dashboard.Managers; -using KitX_Dashboard.Services; -using Serilog; -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Net.Sockets; -using System.Text; -using System.Threading; - -#pragma warning disable CS8600 // 将 null 字面量或可能为 null 的值转换为非 null 类型。 -#pragma warning disable CS8602 // 解引用可能出现空引用。 -#pragma warning disable CS8604 // 引用类型参数可能为 null。 - -namespace KitX_Dashboard.Servers; - -internal class PluginsServer : IDisposable -{ - public PluginsServer() - { - var port = Program.Config.Web.UserSpecifiedPluginsServerPort; - if (port < 0 || port >= 65536) port = null; - listener = new(IPAddress.Any, port ?? 0); - acceptPluginThread = new(AcceptClient); - } - - #region TCP Socket 服务于 Loaders 的服务器 - - /// - /// 开始执行 - /// - public void Start() - { - listener.Start(); - - int port = ((IPEndPoint)listener.LocalEndpoint).Port; // 取服务端口号 - GlobalInfo.PluginServerPort = port; // 全局端口号标明 - EventService.Invoke(nameof(EventService.PluginsServerPortChanged)); - - Log.Information($"PluginsServer Port: {port}"); - - acceptPluginThread.Start(); - } - - /// - /// 停止进程 - /// - public void Stop() - { - keepListen = false; - - foreach (KeyValuePair item in clients) - { - item.Value.Close(); - item.Value.Dispose(); - } - - acceptPluginThread.Join(); - } - - public Thread acceptPluginThread; - public TcpListener listener; - public bool keepListen = true; - - public readonly Dictionary clients = new(); - - /// - /// 接收客户端 - /// - private void AcceptClient() - { - try - { - while (keepListen) - { - if (listener.Pending()) - { - TcpClient client = listener.AcceptTcpClient(); - IPEndPoint endpoint = client.Client.RemoteEndPoint as IPEndPoint; - clients.Add(endpoint.ToString(), client); - - Log.Information($"New plugin connection: {endpoint}"); - - // 新建并运行接收消息线程 - new Thread(() => - { - try - { - ReciveMessage(client); - } - catch (Exception ex) - { - Log.Error(ex, "In WebManager.AcceptClient().ReceiveMessageFromHost()", ex); - } - }).Start(); - } - else - { - Thread.Sleep(100); - } - } - } - catch (Exception ex) - { - Log.Error(ex, $"In AcceptClient() : {ex.Message}"); - } - } - - /// - /// 接收消息 - /// - /// TcpClient - private async void ReciveMessage(object obj) - { - TcpClient client = obj as TcpClient; - IPEndPoint endpoint = null; - NetworkStream stream = null; - - try - { - endpoint = client.Client.RemoteEndPoint as IPEndPoint; - stream = client.GetStream(); - - while (keepListen) - { - byte[] data = new byte[Program.Config.Web.SocketBufferSize]; - //如果远程主机已关闭连接,Read将立即返回零字节 - //int length = await stream.ReadAsync(data, 0, data.Length); - int length = await stream.ReadAsync(data); - if (length > 0) - { - string msg = Encoding.UTF8.GetString(data, 0, length); - - Log.Information($"From: {endpoint}\tReceive: {msg}"); - - if (false) - { - - } - else if (msg.StartsWith("PluginStruct: ")) - { - PluginsManager.Execute(msg[14..], endpoint); - string workPath = Path.GetFullPath(Program.Config.App.LocalPluginsDataFolder); - string sendtxt = $"WorkPath: {workPath}"; - byte[] bytes = Encoding.UTF8.GetBytes(sendtxt); - stream.Write(bytes, 0, bytes.Length); - } - - //发送到其他客户端 - //foreach (KeyValuePair kvp in clients) - //{ - // if (kvp.Value != client) - // { - // byte[] writeData = Encoding.UTF8.GetBytes(msg); - // NetworkStream writeStream = kvp.Value.GetStream(); - // writeStream.Write(writeData, 0, writeData.Length); - // } - //} - } - else - { - - break; //客户端断开连接 跳出循环 - } - } - } - catch (Exception ex) - { - Log.Error(ex, $"Error: In ReceiveMessageFromHost() : {ex.Message}"); - Log.Information($"Connection broke from: {endpoint}"); - - //Read是阻塞方法 客户端退出是会引发异常 释放资源 结束此线程 - } - finally - { - //释放资源 - PluginsManager.Disconnect(endpoint); //注销插件 - stream.Close(); - stream.Dispose(); - clients.Remove(endpoint.ToString()); - client.Dispose(); - } - } - #endregion - - public void Dispose() - { - keepListen = false; - listener.Stop(); - acceptPluginThread.Join(); - GC.SuppressFinalize(this); - } - -} - -#pragma warning restore CS8604 // 引用类型参数可能为 null。 -#pragma warning restore CS8602 // 解引用可能出现空引用。 -#pragma warning restore CS8600 // 将 null 字面量或可能为 null 的值转换为非 null 类型。 diff --git a/Services/DebugService.cs b/Services/DebugService.cs index 4480c933..88fe8ae6 100644 --- a/Services/DebugService.cs +++ b/Services/DebugService.cs @@ -1,4 +1,5 @@ -using KitX_Dashboard.Servers; +using Common.BasicHelper.Utils.Extensions; +using KitX_Dashboard.Network; using Serilog; using System; using System.Collections.Generic; @@ -127,7 +128,7 @@ private static string SaveConfig() case "deviceudppack": if (args.ContainsKey("--value")) { - DevicesServer.Messages2BroadCast.Enqueue(args["--value"]); + DevicesDiscoveryServer.Messages2BroadCast.Enqueue(args["--value"]); return "Appended value to broadcast list."; } else return "Missing value of `--value`."; @@ -135,7 +136,7 @@ private static string SaveConfig() case "clientmessage": if (args.ContainsKey("--value")) { - Program.WebManager?.devicesServer?.SendMessageToHost(args["--value"]); + Program.WebManager?.devicesClient?.Send(args["--value"].FromUTF8()); return $"Sent msg: {args["--value"]}"; } else return "Missing value of `--value`."; @@ -145,8 +146,10 @@ private static string SaveConfig() { if (args.ContainsKey("--to")) { - Program.WebManager?.devicesServer - ?.SendMessage(args["--value"], args["--to"]); + Program.WebManager?.devicesServer?.Send( + args["--value"].FromUTF8(), + args["--to"] + ); return $"Sent msg: {args["--value"]}, to: {args["--to"]}"; } else return "Missing value of `--to`."; @@ -156,8 +159,10 @@ private static string SaveConfig() case "hostbroadcast": if (args.ContainsKey("--value")) { - Program.WebManager?.devicesServer - ?.BroadCastMessage(args["--value"], null); + Program.WebManager?.devicesServer?.BroadCast( + args["--value"].FromUTF8(), + null + ); return $"Broadcast msg: {args["--value"]}"; } else return "Missing value of `--value`"; @@ -247,14 +252,17 @@ private static string SaveConfig() { if (args.ContainsKey("help")) return "" + - "- plugins-server\n" + - "- devices-server\n" + + "- plugins-services\n" + + "- devices-services\n" + + "- devices-discovery-server\n" + "- all\n"; - if (args.ContainsKey("plugins-server")) - Program.WebManager?.Start(startDevicesServer: false); - if (args.ContainsKey("devices-server")) - Program.WebManager?.Start(startPluginsServer: false); + if (args.ContainsKey("plugins-services")) + Program.WebManager?.Start(startAll: false, startPluginsServices: true); + if (args.ContainsKey("devices-services")) + Program.WebManager?.Start(startAll: false, startDevicesServices: true); + if (args.ContainsKey("devices-discovery-server")) + Program.WebManager?.Start(startAll: false, startDevicesDiscoveryServer: true); if (args.ContainsKey("all")) Program.WebManager?.Start(); @@ -265,14 +273,17 @@ private static string SaveConfig() { if (args.ContainsKey("help")) return "" + - "- plugins-server\n" + - "- devices-server\n" + + "- plugins-services\n" + + "- devices-services\n" + + "- devices-discovery-server\n" + "- all"; - if (args.ContainsKey("plugins-server")) - Program.WebManager?.Stop(stopDevicesServer: false); - if (args.ContainsKey("devices-server")) - Program.WebManager?.Stop(stopPluginsServer: false); + if (args.ContainsKey("plugins-services")) + Program.WebManager?.Stop(stopAll: false, stopPluginsServices: true); + if (args.ContainsKey("devices-services")) + Program.WebManager?.Stop(stopAll: false, stopDevicesServices: true); + if (args.ContainsKey("devices-discovery-server")) + Program.WebManager?.Stop(stopAll: false, stopDevicesDiscoveryServer: true); if (args.ContainsKey("all")) Program.WebManager?.Stop(); diff --git a/Services/EventService.cs b/Services/EventService.cs index 3fe474a6..effd9613 100644 --- a/Services/EventService.cs +++ b/Services/EventService.cs @@ -59,7 +59,7 @@ internal static class EventService internal static event DevicesServerPortChangedHandler? DevicesServerPortChanged; - internal static event OnReceivingDeviceInfoStructHandler? OnReceivingDeviceInfoStruct4DeviceNet; + internal static event OnReceivingDeviceInfoStructHandler? OnReceivingDeviceInfoStruct; internal static event OnConfigHotReloadedHandler? OnConfigHotReloaded; @@ -80,7 +80,7 @@ internal static void Init() UseStatisticsChanged += () => { }; OnExiting += () => { }; DevicesServerPortChanged += () => { }; - OnReceivingDeviceInfoStruct4DeviceNet += dis => { }; + OnReceivingDeviceInfoStruct += dis => { }; OnConfigHotReloaded += () => { }; PluginsServerPortChanged += () => { }; } @@ -144,8 +144,8 @@ internal static void Invoke(string eventName, object arg) { switch (eventName) { - case nameof(OnReceivingDeviceInfoStruct4DeviceNet): - OnReceivingDeviceInfoStruct4DeviceNet?.Invoke((DeviceInfoStruct)arg); + case nameof(OnReceivingDeviceInfoStruct): + OnReceivingDeviceInfoStruct?.Invoke((DeviceInfoStruct)arg); break; } } diff --git a/ViewModels/Pages/Controls/PluginBarViewModel.cs b/ViewModels/Pages/Controls/PluginBarViewModel.cs index 39940496..8483656e 100644 --- a/ViewModels/Pages/Controls/PluginBarViewModel.cs +++ b/ViewModels/Pages/Controls/PluginBarViewModel.cs @@ -4,7 +4,7 @@ using KitX_Dashboard.Data; using KitX_Dashboard.Managers; using KitX_Dashboard.Models; -using KitX_Dashboard.Servers; +using KitX_Dashboard.Network; using KitX_Dashboard.Services; using KitX_Dashboard.Views; using KitX_Dashboard.Views.Pages.Controls; @@ -147,12 +147,14 @@ internal void Launch(object _) { try { - string? loaderName = PluginDetail?.RequiredLoaderStruct.LoaderName; + var loaderName = PluginDetail?.RequiredLoaderStruct.LoaderName; var pd = PluginDetail?.PluginDetails; string pluginPath = $"{PluginDetail?.InstallPath}/{pd?.RootStartupFileName}"; string pluginFile = Path.GetFullPath(pluginPath); - string connectStr = $"{DevicesServer.DefaultDeviceInfoStruct.IPv4}" + - $":{GlobalInfo.PluginServerPort}"; + string connectStr = "" + + $"{DevicesDiscoveryServer.DefaultDeviceInfoStruct.IPv4}" + + $":" + + $"{GlobalInfo.PluginServerPort}"; if (PluginDetail != null && PluginDetail.RequiredLoaderStruct.SelfLoad) Process.Start(pluginFile, $"--connect {connectStr}"); else diff --git a/ViewModels/Pages/Controls/Settings_UpdateViewModel.cs b/ViewModels/Pages/Controls/Settings_UpdateViewModel.cs index 52044ef7..8d125921 100644 --- a/ViewModels/Pages/Controls/Settings_UpdateViewModel.cs +++ b/ViewModels/Pages/Controls/Settings_UpdateViewModel.cs @@ -6,7 +6,7 @@ using KitX_Dashboard.Commands; using KitX_Dashboard.Converters; using KitX_Dashboard.Data; -using KitX_Dashboard.Servers; +using KitX_Dashboard.Network; using KitX_Dashboard.Services; using MessageBox.Avalonia; using MessageBox.Avalonia.Enums; @@ -310,7 +310,7 @@ HttpClient client string link = "https://" + Program.Config.Web.UpdateServer + Program.Config.Web.UpdatePath.Replace("%platform%", - DevicesServer.DefaultDeviceInfoStruct.DeviceOSType switch + DevicesDiscoveryServer.DefaultDeviceInfoStruct.DeviceOSType switch { OperatingSystems.Windows => "win", OperatingSystems.Linux => "linux", @@ -521,7 +521,7 @@ ref HttpClient client string downloadLinkBase = "https://" + Program.Config.Web.UpdateServer + Program.Config.Web.UpdateDownloadPath.Replace("%platform%", - DevicesServer.DefaultDeviceInfoStruct.DeviceOSType switch + DevicesDiscoveryServer.DefaultDeviceInfoStruct.DeviceOSType switch { OperatingSystems.Windows => "win", OperatingSystems.Linux => "linux", diff --git a/ViewModels/Pages/DevicePageViewModel.cs b/ViewModels/Pages/DevicePageViewModel.cs index 2b8ba55b..e4159736 100644 --- a/ViewModels/Pages/DevicePageViewModel.cs +++ b/ViewModels/Pages/DevicePageViewModel.cs @@ -55,18 +55,25 @@ internal double NoDevice_TipHeight internal static void RestartDevicvesServer(object _) { Program.WebManager?.Restart( - restartPluginsServer: false, + restartAll: false, + restartDevicesServices: false, + restartDevicesDiscoveryServer: true, actionBeforeStarting: () => DeviceCards.Clear() ); } internal static void StopDevicvesServer(object _) { - Program.WebManager?.Stop(stopPluginsServer: false); + Program.WebManager?.Stop( + stopAll: false, + stopDevicesServices: true, + stopDevicesDiscoveryServer: true + ); Task.Run(async () => { await Task.Delay(Program.Config.Web.UDPSendFrequency + 200); + DeviceCards.Clear(); }); }