Add speed display for sing-box , using clash api

This commit is contained in:
2dust 2023-05-09 15:51:32 +08:00
parent db1b3fdaad
commit 1dce0f0366
6 changed files with 335 additions and 148 deletions

View file

@ -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
}
};
}

View file

@ -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<ServerStatItem> _lstServerStat;
public List<ServerStatItem> ServerStat => _lstServerStat;
private Action<ServerSpeedItem> _updateFunc;
private StatisticsV2ray? _statisticsV2Ray;
private StatisticsSingbox? _statisticsSingbox;
public bool Enable
{
get; set;
}
public List<ServerStatItem> ServerStat => _lstServerStat;
public bool Enable { get; set; }
public StatisticsHandler(Mode.Config config, Action<ServerSpeedItem> update)
public StatisticsHandler(Config config, Action<ServerSpeedItem> 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<ServerStatItem>().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<Stat> 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;
}
}
}

View file

@ -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<ServerSpeedItem> _updateFunc;
public StatisticsSingbox(Config config, Action<ServerSpeedItem> 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<byte>(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<byte>(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<TrafficItem>(source);
if (trafficItem != null)
{
up = trafficItem.up;
down = trafficItem.down;
}
}
catch
{
}
}
}
}

View file

@ -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<ServerSpeedItem> _updateFunc;
public StatisticsV2ray(Mode.Config config, Action<ServerSpeedItem> 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<Stat> 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
{
}
}
}
}

View file

@ -23,4 +23,18 @@
get; set;
}
}
[Serializable]
public class TrafficItem
{
public ulong up
{
get; set;
}
public ulong down
{
get; set;
}
}
}

View file

@ -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; }