From 1dce0f0366ead430f2bd7f1ecd91318f441f08f7 Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Tue, 9 May 2023 15:51:32 +0800 Subject: [PATCH] Add speed display for sing-box , using clash api --- v2rayN/v2rayN/Handler/CoreConfigSingbox.cs | 19 +- v2rayN/v2rayN/Handler/StatisticsHandler.cs | 196 ++++++--------------- v2rayN/v2rayN/Handler/StatisticsSingbox.cs | 127 +++++++++++++ v2rayN/v2rayN/Handler/StatisticsV2ray.cs | 120 +++++++++++++ v2rayN/v2rayN/Mode/ServerSpeedItem.cs | 14 ++ v2rayN/v2rayN/Mode/SingboxConfig.cs | 7 + 6 files changed, 335 insertions(+), 148 deletions(-) create mode 100644 v2rayN/v2rayN/Handler/StatisticsSingbox.cs create mode 100644 v2rayN/v2rayN/Handler/StatisticsV2ray.cs diff --git a/v2rayN/v2rayN/Handler/CoreConfigSingbox.cs b/v2rayN/v2rayN/Handler/CoreConfigSingbox.cs index c076b1a4..90642287 100644 --- a/v2rayN/v2rayN/Handler/CoreConfigSingbox.cs +++ b/v2rayN/v2rayN/Handler/CoreConfigSingbox.cs @@ -52,7 +52,7 @@ namespace v2rayN.Handler dns(node, singboxConfig); - //statistic(singboxConfig); + statistic(singboxConfig); msg = string.Format(ResUI.SuccessfulConfiguration, ""); } @@ -695,13 +695,18 @@ namespace v2rayN.Handler { singboxConfig.experimental = new Experimental4Sbox() { - v2ray_api = new V2ray_Api4Sbox() + //v2ray_api = new V2ray_Api4Sbox() + //{ + // listen = $"{Global.Loopback}:{Global.statePort}", + // stats = new Stats4Sbox() + // { + // enabled = true, + // } + //} + clash_api = new Clash_Api4Sbox() { - listen = $"{Global.Loopback}:{Global.statePort}", - stats = new Stats4Sbox() - { - enabled = true, - } + external_controller = $"{Global.Loopback}:{Global.statePort}", + store_selected = true } }; } diff --git a/v2rayN/v2rayN/Handler/StatisticsHandler.cs b/v2rayN/v2rayN/Handler/StatisticsHandler.cs index 23f18ba1..4ca0ae02 100644 --- a/v2rayN/v2rayN/Handler/StatisticsHandler.cs +++ b/v2rayN/v2rayN/Handler/StatisticsHandler.cs @@ -1,7 +1,4 @@ -using Grpc.Core; -using Grpc.Net.Client; -using ProtosLib.Statistics; -using System.Net; +using System.Net; using System.Net.Sockets; using v2rayN.Base; using v2rayN.Mode; @@ -10,51 +7,40 @@ namespace v2rayN.Handler { internal class StatisticsHandler { - private Mode.Config config_; - private GrpcChannel _channel; - private StatsService.StatsServiceClient _client; - private bool _exitFlag; + private Config _config; private ServerStatItem? _serverStatItem; private List _lstServerStat; - public List ServerStat => _lstServerStat; - private Action _updateFunc; + private StatisticsV2ray? _statisticsV2Ray; + private StatisticsSingbox? _statisticsSingbox; - public bool Enable - { - get; set; - } + public List ServerStat => _lstServerStat; + public bool Enable { get; set; } - public StatisticsHandler(Mode.Config config, Action update) + public StatisticsHandler(Config config, Action update) { - config_ = config; + _config = config; Enable = config.guiItem.enableStatistics; + if (!Enable) + { + return; + } + _updateFunc = update; - _exitFlag = false; Init(); - GrpcInit(); + Global.statePort = GetFreePort(); - Task.Run(Run); - } - - private void GrpcInit() - { - if (_channel == null) - { - Global.statePort = GetFreePort(); - - _channel = GrpcChannel.ForAddress($"{Global.httpProtocol}{Global.Loopback}:{Global.statePort}"); - _client = new StatsService.StatsServiceClient(_channel); - } + _statisticsV2Ray = new StatisticsV2ray(config, UpdateServerStat); + _statisticsSingbox = new StatisticsSingbox(config, UpdateServerStat); } public void Close() { try { - _exitFlag = true; - //channel_.ShutdownAsync(); + _statisticsV2Ray?.Close(); + _statisticsSingbox?.Close(); } catch (Exception ex) { @@ -62,57 +48,6 @@ namespace v2rayN.Handler } } - public async void Run() - { - while (!_exitFlag) - { - try - { - if (Enable && _channel.State == ConnectivityState.Ready) - { - QueryStatsResponse? res = null; - try - { - res = await _client.QueryStatsAsync(new QueryStatsRequest() { Pattern = "", Reset = true }); - } - catch (Exception ex) - { - //Utils.SaveLog(ex.Message, ex); - } - - if (res != null) - { - GetServerStatItem(config_.indexId); - ParseOutput(res.Stat, out ServerSpeedItem server); - - if (server.proxyUp != 0 || server.proxyDown != 0) - { - _serverStatItem.todayUp += server.proxyUp; - _serverStatItem.todayDown += server.proxyDown; - _serverStatItem.totalUp += server.proxyUp; - _serverStatItem.totalDown += server.proxyDown; - } - if (Global.ShowInTaskbar) - { - server.indexId = config_.indexId; - server.todayUp = _serverStatItem.todayUp; - server.todayDown = _serverStatItem.todayDown; - server.totalUp = _serverStatItem.totalUp; - server.totalDown = _serverStatItem.totalDown; - _updateFunc(server); - } - } - } - var sleep = config_.guiItem.statisticsFreshRate < 1 ? 1 : config_.guiItem.statisticsFreshRate; - Thread.Sleep(1000 * sleep); - await _channel.ConnectAsync(); - } - catch - { - } - } - } - public void ClearAllServerStatistics() { SqliteHelper.Instance.Execute($"delete from ServerStatItem "); @@ -142,6 +77,28 @@ namespace v2rayN.Handler _lstServerStat = SqliteHelper.Instance.Table().ToList(); } + private void UpdateServerStat(ServerSpeedItem server) + { + GetServerStatItem(_config.indexId); + + if (server.proxyUp != 0 || server.proxyDown != 0) + { + _serverStatItem.todayUp += server.proxyUp; + _serverStatItem.todayDown += server.proxyDown; + _serverStatItem.totalUp += server.proxyUp; + _serverStatItem.totalDown += server.proxyDown; + } + if (Global.ShowInTaskbar) + { + server.indexId = _config.indexId; + server.todayUp = _serverStatItem.todayUp; + server.todayDown = _serverStatItem.todayDown; + server.totalUp = _serverStatItem.totalUp; + server.totalDown = _serverStatItem.totalDown; + _updateFunc(server); + } + } + private void GetServerStatItem(string indexId) { long ticks = DateTime.Now.Date.Ticks; @@ -177,71 +134,28 @@ namespace v2rayN.Handler } } - private void ParseOutput(Google.Protobuf.Collections.RepeatedField source, out ServerSpeedItem server) - { - server = new(); - try - { - foreach (Stat stat in source) - { - string name = stat.Name; - long value = stat.Value / 1024; //KByte - string[] nStr = name.Split(">>>".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); - string type = ""; - - name = name.Trim(); - - name = nStr[1]; - type = nStr[3]; - - if (name == Global.agentTag) - { - if (type == "uplink") - { - server.proxyUp = value; - } - else if (type == "downlink") - { - server.proxyDown = value; - } - } - else if (name == Global.directTag) - { - if (type == "uplink") - { - server.directUp = value; - } - else if (type == "downlink") - { - server.directDown = value; - } - } - } - } - catch (Exception ex) - { - //Utils.SaveLog(ex.Message, ex); - } - } - private int GetFreePort() { - int defaultPort = 28123; try { - // TCP stack please do me a favor - TcpListener l = new(IPAddress.Loopback, 0); - l.Start(); - int port = ((IPEndPoint)l.LocalEndpoint).Port; - l.Stop(); - return port; + int defaultPort = 9090; + if (!Utils.PortInUse(defaultPort)) + { + return defaultPort; + } + for (int i = 0; i < 3; i++) + { + TcpListener l = new(IPAddress.Loopback, 0); + l.Start(); + int port = ((IPEndPoint)l.LocalEndpoint).Port; + l.Stop(); + return port; + } } - catch (Exception ex) + catch { - // in case access denied - Utils.SaveLog(ex.Message, ex); - return defaultPort; } + return 69090; } } } \ No newline at end of file diff --git a/v2rayN/v2rayN/Handler/StatisticsSingbox.cs b/v2rayN/v2rayN/Handler/StatisticsSingbox.cs new file mode 100644 index 00000000..15fea2d9 --- /dev/null +++ b/v2rayN/v2rayN/Handler/StatisticsSingbox.cs @@ -0,0 +1,127 @@ +using System.Net.WebSockets; +using System.Text; +using v2rayN.Mode; + +namespace v2rayN.Handler +{ + internal class StatisticsSingbox + { + private Config _config; + private bool _exitFlag; + private ClientWebSocket? webSocket; + private string url = string.Empty; + private Action _updateFunc; + + public StatisticsSingbox(Config config, Action update) + { + _config = config; + _updateFunc = update; + _exitFlag = false; + + Task.Run(() => Run()); + } + + private async void Init() + { + Thread.Sleep(5000); + + try + { + url = $"ws://{Global.Loopback}:{Global.statePort}/traffic"; + + if (webSocket == null) + { + webSocket = new ClientWebSocket(); + await webSocket.ConnectAsync(new Uri(url), CancellationToken.None); + } + } + catch { } + } + + public void Close() + { + try + { + _exitFlag = true; + if (webSocket != null) + { + webSocket.Abort(); + webSocket = null; + } + } + catch (Exception ex) + { + Utils.SaveLog(ex.Message, ex); + } + } + + private async void Run() + { + Init(); + + while (!_exitFlag) + { + try + { + if (webSocket != null) + { + if (webSocket.State == WebSocketState.Aborted + || webSocket.State == WebSocketState.Closed) + { + webSocket.Abort(); + webSocket = null; + Init(); + } + + if (webSocket.State != WebSocketState.Open) + { + continue; + } + + var buffer = new byte[1024]; + var res = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); + while (!res.CloseStatus.HasValue) + { + var result = Encoding.UTF8.GetString(buffer, 0, res.Count); + if (!string.IsNullOrEmpty(result)) + { + ParseOutput(result, out ulong up, out ulong down); + + _updateFunc(new ServerSpeedItem() + { + proxyUp = (long)(up / 1000), + proxyDown = (long)(down / 1000) + }); + } + res = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); + } + } + } + catch + { + } + finally + { + Thread.Sleep(1000); + } + } + } + + private void ParseOutput(string source, out ulong up, out ulong down) + { + up = 0; down = 0; + try + { + var trafficItem = Utils.FromJson(source); + if (trafficItem != null) + { + up = trafficItem.up; + down = trafficItem.down; + } + } + catch + { + } + } + } +} \ No newline at end of file diff --git a/v2rayN/v2rayN/Handler/StatisticsV2ray.cs b/v2rayN/v2rayN/Handler/StatisticsV2ray.cs new file mode 100644 index 00000000..cc4a065b --- /dev/null +++ b/v2rayN/v2rayN/Handler/StatisticsV2ray.cs @@ -0,0 +1,120 @@ +using Grpc.Core; +using Grpc.Net.Client; +using ProtosLib.Statistics; +using v2rayN.Mode; + +namespace v2rayN.Handler +{ + internal class StatisticsV2ray + { + private Mode.Config _config; + private GrpcChannel _channel; + private StatsService.StatsServiceClient _client; + private bool _exitFlag; + private Action _updateFunc; + + public StatisticsV2ray(Mode.Config config, Action update) + { + _config = config; + _updateFunc = update; + _exitFlag = false; + + GrpcInit(); + + Task.Run(Run); + } + + private void GrpcInit() + { + if (_channel == null) + { + _channel = GrpcChannel.ForAddress($"{Global.httpProtocol}{Global.Loopback}:{Global.statePort}"); + _client = new StatsService.StatsServiceClient(_channel); + } + } + + public void Close() + { + _exitFlag = true; + } + + private async void Run() + { + while (!_exitFlag) + { + try + { + if (_channel.State == ConnectivityState.Ready) + { + QueryStatsResponse? res = null; + try + { + res = await _client.QueryStatsAsync(new QueryStatsRequest() { Pattern = "", Reset = true }); + } + catch + { + } + + if (res != null) + { + ParseOutput(res.Stat, out ServerSpeedItem server); + _updateFunc(server); + } + } + var sleep = _config.guiItem.statisticsFreshRate < 1 ? 1 : _config.guiItem.statisticsFreshRate; + Thread.Sleep(1000 * sleep); + await _channel.ConnectAsync(); + } + catch + { + } + } + } + + private void ParseOutput(Google.Protobuf.Collections.RepeatedField source, out ServerSpeedItem server) + { + server = new(); + try + { + foreach (Stat stat in source) + { + string name = stat.Name; + long value = stat.Value / 1024; //KByte + string[] nStr = name.Split(">>>".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); + string type = ""; + + name = name.Trim(); + + name = nStr[1]; + type = nStr[3]; + + if (name == Global.agentTag) + { + if (type == "uplink") + { + server.proxyUp = value; + } + else if (type == "downlink") + { + server.proxyDown = value; + } + } + else if (name == Global.directTag) + { + if (type == "uplink") + { + server.directUp = value; + } + else if (type == "downlink") + { + server.directDown = value; + } + } + } + } + catch + { + } + } + } +} \ No newline at end of file diff --git a/v2rayN/v2rayN/Mode/ServerSpeedItem.cs b/v2rayN/v2rayN/Mode/ServerSpeedItem.cs index 8f953159..c51f6d11 100644 --- a/v2rayN/v2rayN/Mode/ServerSpeedItem.cs +++ b/v2rayN/v2rayN/Mode/ServerSpeedItem.cs @@ -23,4 +23,18 @@ get; set; } } + + [Serializable] + public class TrafficItem + { + public ulong up + { + get; set; + } + + public ulong down + { + get; set; + } + } } \ No newline at end of file diff --git a/v2rayN/v2rayN/Mode/SingboxConfig.cs b/v2rayN/v2rayN/Mode/SingboxConfig.cs index 64e06798..7c30fa32 100644 --- a/v2rayN/v2rayN/Mode/SingboxConfig.cs +++ b/v2rayN/v2rayN/Mode/SingboxConfig.cs @@ -166,6 +166,7 @@ public class Experimental4Sbox { public V2ray_Api4Sbox v2ray_api { get; set; } + public Clash_Api4Sbox clash_api { get; set; } } public class V2ray_Api4Sbox @@ -174,6 +175,12 @@ public Stats4Sbox stats { get; set; } } + public class Clash_Api4Sbox + { + public string external_controller { get; set; } + public bool store_selected { get; set; } + } + public class Stats4Sbox { public bool enabled { get; set; }