[feat] 新增内网外网网络质量监测,基于到网关和星尘服务器的Ping丢包和延迟来综合平分

This commit is contained in:
大石头 2025-07-19 02:30:19 +08:00
parent c122afb968
commit ab159e3507
9 changed files with 219 additions and 10 deletions

View File

@ -156,6 +156,8 @@
<Column Name="Signal" DataType="Int32" Description="信号。信号强度WiFi/4G" />
<Column Name="UplinkSpeed" DataType="Int64" ItemType="GMK" Description="上行速度。网络发送速度,字节每秒" />
<Column Name="DownlinkSpeed" DataType="Int64" ItemType="GMK" Description="下行速度。网络接收速度,字节每秒" />
<Column Name="IntranetScore" DataType="Double" ItemType="percent" Description="内网质量。综合评估到网关的心跳延迟和丢包率满分1分" />
<Column Name="InternetScore" DataType="Double" ItemType="percent" Description="外网质量。综合评估到DNS和星尘服务器的心跳延迟和丢包率满分1分" />
<Column Name="ProcessCount" DataType="Int32" Description="进程数" />
<Column Name="TcpConnections" DataType="Int32" Description="连接数。传输数据Established的Tcp网络连接数" />
<Column Name="TcpTimeWait" DataType="Int32" Description="主动关闭。主动关闭后TimeWait的Tcp网络连接数下一步Closed" />
@ -221,6 +223,8 @@
<Column Name="TcpConnections" DataType="Int32" Description="连接数。传输数据Established的Tcp网络连接数" />
<Column Name="TcpTimeWait" DataType="Int32" Description="主动关闭。主动关闭后TimeWait的Tcp网络连接数等待2MSL确保四次挥手的最后一个ACK能够发出下一步Closed" />
<Column Name="TcpCloseWait" DataType="Int32" Description="被动关闭。作为客户端收到服务器FIN后进入CloseWait的Tcp网络连接数还没发送自己的FIN主要原因是我方太忙" />
<Column Name="IntranetScore" DataType="Double" ItemType="percent" Description="内网质量。综合评估到网关的心跳延迟和丢包率满分1分" />
<Column Name="InternetScore" DataType="Double" ItemType="percent" Description="外网质量。综合评估到DNS和星尘服务器的心跳延迟和丢包率满分1分" />
<Column Name="Delay" DataType="Int32" Description="延迟。网络延迟客户端最近一次心跳耗时的一半单位ms" />
<Column Name="Offset" DataType="Int32" Description="偏移。客户端UTC时间加上一半延迟再减服务端UTC时间单位ms" />
<Column Name="LocalTime" DataType="DateTime" Description="本地时间" />

View File

@ -1155,6 +1155,28 @@
<td>网络接收速度,字节每秒</td>
</tr>
<tr>
<td>IntranetScore</td>
<td>内网质量</td>
<td>Double</td>
<td></td>
<td></td>
<td></td>
<td>N</td>
<td>综合评估到网关的心跳延迟和丢包率满分1分</td>
</tr>
<tr>
<td>InternetScore</td>
<td>外网质量</td>
<td>Double</td>
<td></td>
<td></td>
<td></td>
<td>N</td>
<td>综合评估到DNS和星尘服务器的心跳延迟和丢包率满分1分</td>
</tr>
<tr>
<td>ProcessCount</td>
<td>进程数</td>
@ -1706,6 +1728,28 @@
<td>作为客户端收到服务器FIN后进入CloseWait的Tcp网络连接数还没发送自己的FIN主要原因是我方太忙</td>
</tr>
<tr>
<td>IntranetScore</td>
<td>内网质量</td>
<td>Double</td>
<td></td>
<td></td>
<td></td>
<td>N</td>
<td>综合评估到网关的心跳延迟和丢包率满分1分</td>
</tr>
<tr>
<td>InternetScore</td>
<td>外网质量</td>
<td>Double</td>
<td></td>
<td></td>
<td></td>
<td>N</td>
<td>综合评估到DNS和星尘服务器的心跳延迟和丢包率满分1分</td>
</tr>
<tr>
<td>Delay</td>
<td>延迟</td>

View File

@ -268,6 +268,8 @@ public partial class NodeOnline : Entity<NodeOnline>
if (inf.TcpConnections > 0) online.TcpConnections = inf.TcpConnections;
if (inf.TcpTimeWait > 0) online.TcpTimeWait = inf.TcpTimeWait;
if (inf.TcpCloseWait > 0) online.TcpCloseWait = inf.TcpCloseWait;
if (inf.IntranetScore > 0) online.IntranetScore = inf.IntranetScore;
if (inf.InternetScore > 0) online.InternetScore = inf.InternetScore;
if (inf.Uptime > 0) online.Uptime = inf.Uptime;
if (inf.Delay > 0) online.Delay = inf.Delay;
@ -314,6 +316,8 @@ public partial class NodeOnline : Entity<NodeOnline>
TcpConnections = inf.TcpConnections,
TcpTimeWait = inf.TcpTimeWait,
TcpCloseWait = inf.TcpCloseWait,
IntranetScore = inf.IntranetScore,
InternetScore = inf.InternetScore,
Uptime = inf.Uptime,
Delay = inf.Delay,
LocalTime = dt,

View File

@ -275,6 +275,22 @@ public partial class NodeOnline
[BindColumn("DownlinkSpeed", "下行速度。网络接收速度,字节每秒", "", ItemType = "GMK")]
public Int64 DownlinkSpeed { get => _DownlinkSpeed; set { if (OnPropertyChanging("DownlinkSpeed", value)) { _DownlinkSpeed = value; OnPropertyChanged("DownlinkSpeed"); } } }
private Double _IntranetScore;
/// <summary>内网质量。综合评估到网关的心跳延迟和丢包率满分1分</summary>
[DisplayName("内网质量")]
[Description("内网质量。综合评估到网关的心跳延迟和丢包率满分1分")]
[DataObjectField(false, false, false, 0)]
[BindColumn("IntranetScore", "内网质量。综合评估到网关的心跳延迟和丢包率满分1分", "", ItemType = "percent")]
public Double IntranetScore { get => _IntranetScore; set { if (OnPropertyChanging("IntranetScore", value)) { _IntranetScore = value; OnPropertyChanged("IntranetScore"); } } }
private Double _InternetScore;
/// <summary>外网质量。综合评估到DNS和星尘服务器的心跳延迟和丢包率满分1分</summary>
[DisplayName("外网质量")]
[Description("外网质量。综合评估到DNS和星尘服务器的心跳延迟和丢包率满分1分")]
[DataObjectField(false, false, false, 0)]
[BindColumn("InternetScore", "外网质量。综合评估到DNS和星尘服务器的心跳延迟和丢包率满分1分", "", ItemType = "percent")]
public Double InternetScore { get => _InternetScore; set { if (OnPropertyChanging("InternetScore", value)) { _InternetScore = value; OnPropertyChanged("InternetScore"); } } }
private Int32 _ProcessCount;
/// <summary>进程数</summary>
[DisplayName("进程数")]
@ -465,6 +481,8 @@ public partial class NodeOnline
"Signal" => _Signal,
"UplinkSpeed" => _UplinkSpeed,
"DownlinkSpeed" => _DownlinkSpeed,
"IntranetScore" => _IntranetScore,
"InternetScore" => _InternetScore,
"ProcessCount" => _ProcessCount,
"TcpConnections" => _TcpConnections,
"TcpTimeWait" => _TcpTimeWait,
@ -520,6 +538,8 @@ public partial class NodeOnline
case "Signal": _Signal = value.ToInt(); break;
case "UplinkSpeed": _UplinkSpeed = value.ToLong(); break;
case "DownlinkSpeed": _DownlinkSpeed = value.ToLong(); break;
case "IntranetScore": _IntranetScore = value.ToDouble(); break;
case "InternetScore": _InternetScore = value.ToDouble(); break;
case "ProcessCount": _ProcessCount = value.ToInt(); break;
case "TcpConnections": _TcpConnections = value.ToInt(); break;
case "TcpTimeWait": _TcpTimeWait = value.ToInt(); break;
@ -674,6 +694,12 @@ public partial class NodeOnline
/// <summary>下行速度。网络接收速度,字节每秒</summary>
public static readonly Field DownlinkSpeed = FindByName("DownlinkSpeed");
/// <summary>内网质量。综合评估到网关的心跳延迟和丢包率满分1分</summary>
public static readonly Field IntranetScore = FindByName("IntranetScore");
/// <summary>外网质量。综合评估到DNS和星尘服务器的心跳延迟和丢包率满分1分</summary>
public static readonly Field InternetScore = FindByName("InternetScore");
/// <summary>进程数</summary>
public static readonly Field ProcessCount = FindByName("ProcessCount");
@ -827,6 +853,12 @@ public partial class NodeOnline
/// <summary>下行速度。网络接收速度,字节每秒</summary>
public const String DownlinkSpeed = "DownlinkSpeed";
/// <summary>内网质量。综合评估到网关的心跳延迟和丢包率满分1分</summary>
public const String IntranetScore = "IntranetScore";
/// <summary>外网质量。综合评估到DNS和星尘服务器的心跳延迟和丢包率满分1分</summary>
public const String InternetScore = "InternetScore";
/// <summary>进程数</summary>
public const String ProcessCount = "ProcessCount";

View File

@ -142,6 +142,22 @@ public partial class NodeData
[BindColumn("TcpCloseWait", "被动关闭。作为客户端收到服务器FIN后进入CloseWait的Tcp网络连接数还没发送自己的FIN主要原因是我方太忙", "")]
public Int32 TcpCloseWait { get => _TcpCloseWait; set { if (OnPropertyChanging("TcpCloseWait", value)) { _TcpCloseWait = value; OnPropertyChanged("TcpCloseWait"); } } }
private Double _IntranetScore;
/// <summary>内网质量。综合评估到网关的心跳延迟和丢包率满分1分</summary>
[DisplayName("内网质量")]
[Description("内网质量。综合评估到网关的心跳延迟和丢包率满分1分")]
[DataObjectField(false, false, false, 0)]
[BindColumn("IntranetScore", "内网质量。综合评估到网关的心跳延迟和丢包率满分1分", "", ItemType = "percent")]
public Double IntranetScore { get => _IntranetScore; set { if (OnPropertyChanging("IntranetScore", value)) { _IntranetScore = value; OnPropertyChanged("IntranetScore"); } } }
private Double _InternetScore;
/// <summary>外网质量。综合评估到DNS和星尘服务器的心跳延迟和丢包率满分1分</summary>
[DisplayName("外网质量")]
[Description("外网质量。综合评估到DNS和星尘服务器的心跳延迟和丢包率满分1分")]
[DataObjectField(false, false, false, 0)]
[BindColumn("InternetScore", "外网质量。综合评估到DNS和星尘服务器的心跳延迟和丢包率满分1分", "", ItemType = "percent")]
public Double InternetScore { get => _InternetScore; set { if (OnPropertyChanging("InternetScore", value)) { _InternetScore = value; OnPropertyChanged("InternetScore"); } } }
private Int32 _Delay;
/// <summary>延迟。网络延迟客户端最近一次心跳耗时的一半单位ms</summary>
[DisplayName("延迟")]
@ -225,6 +241,8 @@ public partial class NodeData
"TcpConnections" => _TcpConnections,
"TcpTimeWait" => _TcpTimeWait,
"TcpCloseWait" => _TcpCloseWait,
"IntranetScore" => _IntranetScore,
"InternetScore" => _InternetScore,
"Delay" => _Delay,
"Offset" => _Offset,
"LocalTime" => _LocalTime,
@ -253,6 +271,8 @@ public partial class NodeData
case "TcpConnections": _TcpConnections = value.ToInt(); break;
case "TcpTimeWait": _TcpTimeWait = value.ToInt(); break;
case "TcpCloseWait": _TcpCloseWait = value.ToInt(); break;
case "IntranetScore": _IntranetScore = value.ToDouble(); break;
case "InternetScore": _InternetScore = value.ToDouble(); break;
case "Delay": _Delay = value.ToInt(); break;
case "Offset": _Offset = value.ToInt(); break;
case "LocalTime": _LocalTime = value.ToDateTime(); break;
@ -341,6 +361,12 @@ public partial class NodeData
/// <summary>被动关闭。作为客户端收到服务器FIN后进入CloseWait的Tcp网络连接数还没发送自己的FIN主要原因是我方太忙</summary>
public static readonly Field TcpCloseWait = FindByName("TcpCloseWait");
/// <summary>内网质量。综合评估到网关的心跳延迟和丢包率满分1分</summary>
public static readonly Field IntranetScore = FindByName("IntranetScore");
/// <summary>外网质量。综合评估到DNS和星尘服务器的心跳延迟和丢包率满分1分</summary>
public static readonly Field InternetScore = FindByName("InternetScore");
/// <summary>延迟。网络延迟客户端最近一次心跳耗时的一半单位ms</summary>
public static readonly Field Delay = FindByName("Delay");
@ -413,6 +439,12 @@ public partial class NodeData
/// <summary>被动关闭。作为客户端收到服务器FIN后进入CloseWait的Tcp网络连接数还没发送自己的FIN主要原因是我方太忙</summary>
public const String TcpCloseWait = "TcpCloseWait";
/// <summary>内网质量。综合评估到网关的心跳延迟和丢包率满分1分</summary>
public const String IntranetScore = "IntranetScore";
/// <summary>外网质量。综合评估到DNS和星尘服务器的心跳延迟和丢包率满分1分</summary>
public const String InternetScore = "InternetScore";
/// <summary>延迟。网络延迟客户端最近一次心跳耗时的一半单位ms</summary>
public const String Delay = "Delay";

View File

@ -104,16 +104,20 @@ public class NodeDataController : ReadOnlyEntityController<NodeData>
var line = chart.Add(list2, _.TcpConnections);
line.YAxisIndex = 1;
}
if (list2.Any(e => e.TcpTimeWait > 0))
{
var line = chart.Add(list2, _.TcpTimeWait);
line.YAxisIndex = 1;
}
if (list2.Any(e => e.TcpCloseWait > 0))
{
var line = chart.Add(list2, _.TcpCloseWait);
line.YAxisIndex = 1;
}
//if (list2.Any(e => e.TcpTimeWait > 0))
//{
// var line = chart.Add(list2, _.TcpTimeWait);
// line.YAxisIndex = 1;
//}
//if (list2.Any(e => e.TcpCloseWait > 0))
//{
// var line = chart.Add(list2, _.TcpCloseWait);
// line.YAxisIndex = 1;
//}
chart.AddLine(list2, _.IntranetScore, e => Math.Round(e.IntranetScore * 100));
chart.AddLine(list2, _.InternetScore, e => Math.Round(e.InternetScore * 100));
//chart.Add(list2, _.Offset);
chart.SetTooltip();
ViewBag.Charts = new[] { chart };

View File

@ -67,6 +67,12 @@ public class PingInfo : PingRequest
/// <summary>被动关闭的Tcp连接数</summary>
public Int32 TcpCloseWait { get; set; }
/// <summary>内网质量。综合评估到网关的心跳延迟和丢包率满分1分</summary>
public Double IntranetScore { get; set; }
/// <summary>外网质量。综合评估到DNS和星尘服务器的心跳延迟和丢包率满分1分</summary>
public Double InternetScore { get; set; }
///// <summary>开机时间单位s</summary>
//public Int32 Uptime { get; set; }

View File

@ -0,0 +1,67 @@
using System.Net.NetworkInformation;
using System.Text.RegularExpressions;
using NewLife;
using NewLife.Log;
namespace Stardust.Monitors;
/// <summary>心跳监控。通过发往目标IP的Ping命令来计算延迟和丢包率进一步得到分数</summary>
public class PingMonitor
{
#region
/// <summary>心跳次数</summary>
public Int32 Times { get; set; } = 5;
/// <summary>多次心跳的间隔</summary>
public Int32 Interval { get; set; } = 1_000;
#endregion
#region
/// <summary>执行多次Ping请求获取网络质量评分</summary>
/// <param name="host"></param>
/// <returns></returns>
public async Task<Double> GetScoreAsync(String? host)
{
//XTrace.WriteLine("GetScoreAsync{0}", host);
if (host.IsNullOrEmpty()) return 0;
var rtTimes = new List<Double>();
using var ping = new Ping();
for (var i = 0; i < Times; i++)
{
try
{
var reply = await ping.SendPingAsync(host, 3_000).ConfigureAwait(false);
if (reply.Status == IPStatus.Success)
rtTimes.Add(reply.RoundtripTime);
}
catch { }
if (i + 1 < Times) await Task.Delay(Interval).ConfigureAwait(false);
}
// 综合评估分数
if (rtTimes.Count > 0)
{
// 延迟阈值50ms
var threshold = 1f;
var successRate = rtTimes.Count / Times;
var latency = rtTimes.Average();
var latencyScore = 0d;
if (latency <= threshold)
latencyScore = 1f;
else
latencyScore = Math.Exp(-0.01 * (latency - threshold)); // 衰减系数λ=0.2
// 确保得分在0-1之间
var score = successRate * latencyScore;
//XTrace.WriteLine($"Score: {score} successRate:{successRate} latency:{latency} latencyScore:{latencyScore}");
return Math.Round(score, 3);
}
return 0;
}
#endregion
}

View File

@ -9,11 +9,13 @@ using NewLife.Data;
using NewLife.Log;
using NewLife.Model;
using NewLife.Reflection;
using NewLife.Remoting;
using NewLife.Remoting.Clients;
using NewLife.Remoting.Models;
using NewLife.Security;
using Stardust.Managers;
using Stardust.Models;
using Stardust.Monitors;
namespace Stardust;
@ -289,6 +291,16 @@ public class StarClient : ClientBase, ICommandClient, IEventProvider
var request = new PingInfo();
FillPingRequest(request);
// 获取网络质量
var monitor = new PingMonitor();
var gw = AgentInfo.GetGateway();
if (gw != null && gw.Contains('/')) gw = gw.Substring(0, gw.IndexOf("/"));
var gwtTask = Task.Run(() => monitor.GetScoreAsync(gw));
var dns = AgentInfo.GetDns();
var dnsTask = Task.Run(() => monitor.GetScoreAsync(dns));
var svr = (Client as ApiHttpClient)?.Current?.Address.Host;
var svrTask = Task.Run(() => monitor.GetScoreAsync(svr));
var exs = _excludes.Where(e => e.Contains('*')).ToArray();
var ps = Process.GetProcesses();
@ -336,6 +348,10 @@ public class StarClient : ClientBase, ICommandClient, IEventProvider
}
catch { }
// 获取网络质量
request.IntranetScore = gwtTask?.Result ?? 0;
request.InternetScore = (dnsTask?.Result ?? 0) * 0.3 + (svrTask?.Result ?? 0) * 0.7;
if (mi is IExtend ext)
{
// 读取无线信号强度