想象一下这个场景:你正在开发一个关键项目,突然网络变慢,但你不知道是哪个应用在偷偷占用带宽。或者你是运维工程师,需要实时监控服务器的网络流量状况。市面上的网络监控工具要么功能过于复杂,要么界面丑陋过时。
今天,我将带你用C#从零构建一个专业级网络流量监控工具,不仅界面美观,功能强大,而且完全可定制。这个项目将帮你掌握网络编程、数据可视化、性能优化等多个技术要点,突然发现正式版的ScottPlot 5.0又变了。。。晕死。
我们要实现的功能包括:
XML<!-- ScottPlot:强大的数据可视化库 -->
<PackageReference Include="ScottPlot.WinForms" Version="5.0.0" />
C#using ScottPlot; // 图表核心功能
using ScottPlot.WinForms; // WinForms集成
using System.Net.NetworkInformation; // 网络接口操作
using Timer = System.Windows.Forms.Timer; // 定时器
C#public partial class Form1 : Form
{
// 定时器和网络接口
private Timer updateTimer;
private NetworkInterface selectedInterface;
private List<NetworkInterface> networkInterfaces;
// 历史数据存储
private List<double> downloadHistory;
private List<double> uploadHistory;
private List<DateTime> timeHistory;
// 基准数据用于计算差值
private long lastBytesReceived;
private long lastBytesSent;
private DateTime lastUpdateTime;
// 数据点控制
private int maxHistoryPoints = 60; // 保留60个数据点(1分钟历史)
// ScottPlot 图表对象
private ScottPlot.Plottables.Scatter downloadPlot;
private ScottPlot.Plottables.Scatter uploadPlot;
}
💡 设计亮点: 使用差值计算法精确测量网络速度,避免累积误差。
C#private void LoadNetworkInterfaces()
{
// 获取所有活跃的非回环网络接口
networkInterfaces = NetworkInterface.GetAllNetworkInterfaces()
.Where(ni => ni.OperationalStatus == OperationalStatus.Up &&
ni.NetworkInterfaceType != NetworkInterfaceType.Loopback)
.ToList();
comboBoxInterfaces.Items.Clear();
foreach (var ni in networkInterfaces)
{
// 显示友好的接口名称
comboBoxInterfaces.Items.Add($"{ni.Name} ({ni.NetworkInterfaceType})");
}
if (comboBoxInterfaces.Items.Count > 0)
{
comboBoxInterfaces.SelectedIndex = 0;
selectedInterface = networkInterfaces[0];
// 🔥 关键:初始化基准值,确保第一次计算的准确性
var stats = selectedInterface.GetIPv4Statistics();
lastBytesReceived = stats.BytesReceived;
lastBytesSent = stats.BytesSent;
}
}
⚠️ 注意事项:
C#private void SetupChart()
{
// 清除所有绘图对象
formsPlot.Plot.Clear();
formsPlot.Plot.Legend.FontName = "SimSun"; // 中文字体支持
// 🎨 坐标轴美化
formsPlot.Plot.Axes.Left.Label.Text = "速度 (KB/s)";
formsPlot.Plot.Axes.Bottom.Label.Text = "时间";
formsPlot.Plot.Axes.Left.Label.FontSize = 12;
formsPlot.Plot.Axes.Bottom.Label.FontSize = 12;
formsPlot.Plot.Axes.Bottom.Label.FontName = "SimSun";
formsPlot.Plot.Axes.Left.Label.FontName = "SimSun";
// 📊 创建下载速度线条
downloadPlot = formsPlot.Plot.Add.Scatter(new double[0], new double[0]);
downloadPlot.Color = ScottPlot.Color.FromHtml("#007BFF"); // 专业蓝色
downloadPlot.LineWidth = 2;
downloadPlot.MarkerSize = 0; // 不显示数据点,保持线条流畅
downloadPlot.LegendText = "下载速度";
// 📤 创建上传速度线条
uploadPlot = formsPlot.Plot.Add.Scatter(new double[0], new double[0]);
uploadPlot.Color = ScottPlot.Color.FromHtml("#DC3545"); // 警示红色
uploadPlot.LineWidth = 2;
uploadPlot.MarkerSize = 0;
uploadPlot.LegendText = "上传速度";
// 显示图例并设置时间轴
formsPlot.Plot.ShowLegend(Alignment.UpperRight);
formsPlot.Plot.Axes.DateTimeTicksBottom(); // 时间轴格式化
formsPlot.Refresh();
}
🎨 设计技巧:
C#private void UpdateTimer_Tick(object sender, EventArgs e)
{
if (selectedInterface == null) return;
try
{
var stats = selectedInterface.GetIPv4Statistics();
var currentTime = DateTime.Now;
var timeSpan = (currentTime - lastUpdateTime).TotalSeconds;
// 🔥 核心算法:差值法计算实时速度
if (timeSpan > 0 && lastBytesReceived > 0 && lastBytesSent > 0)
{
// 计算速度 (KB/s)
double downloadSpeed = (stats.BytesReceived - lastBytesReceived) / timeSpan / 1024;
double uploadSpeed = (stats.BytesSent - lastBytesSent) / timeSpan / 1024;
// 🛡️ 防御性编程:确保速度不为负数
downloadSpeed = Math.Max(0, downloadSpeed);
uploadSpeed = Math.Max(0, uploadSpeed);
// 更新各个显示组件
UpdateRealTimeDisplay(downloadSpeed, uploadSpeed, stats);
UpdateHistory(downloadSpeed, uploadSpeed, currentTime);
UpdateChart();
}
// 更新基准值
lastBytesReceived = stats.BytesReceived;
lastBytesSent = stats.BytesSent;
lastUpdateTime = currentTime;
}
catch (Exception ex)
{
// 🚨 优雅的错误处理
labelStatus.Text = $"监控错误: {ex.Message}";
labelStatus.ForeColor = System.Drawing.Color.Red;
}
}
💡 算法亮点:
C#private void UpdateChart()
{
if (timeHistory.Count == 0) return;
try
{
// 🕒 时间格式转换:DateTime转OADate用于ScottPlot
double[] timeArray = timeHistory.Select(t => t.ToOADate()).ToArray();
double[] downloadArray = downloadHistory.ToArray();
double[] uploadArray = uploadHistory.ToArray();
// 🔄 动态更新策略:移除旧对象,添加新对象
if (downloadPlot != null)
formsPlot.Plot.Remove(downloadPlot);
if (uploadPlot != null)
formsPlot.Plot.Remove(uploadPlot);
// 重新创建图表对象
downloadPlot = formsPlot.Plot.Add.Scatter(timeArray, downloadArray);
downloadPlot.Color = ScottPlot.Color.FromHtml("#007BFF");
downloadPlot.LineWidth = 2;
downloadPlot.MarkerSize = 0;
downloadPlot.LegendText = "下载速度";
uploadPlot = formsPlot.Plot.Add.Scatter(timeArray, uploadArray);
uploadPlot.Color = ScottPlot.Color.FromHtml("#DC3545");
uploadPlot.LineWidth = 2;
uploadPlot.MarkerSize = 0;
uploadPlot.LegendText = "上传速度";
formsPlot.Plot.ShowLegend(Alignment.UpperRight);
// 🎯 智能坐标轴调整
formsPlot.Plot.Axes.AutoScale();
// 设置X轴显示最近的时间窗口
if (timeArray.Length > 0)
{
var latestTime = timeArray[timeArray.Length - 1];
var earliestTime = DateTime.Now.AddSeconds(-maxHistoryPoints).ToOADate();
formsPlot.Plot.Axes.SetLimitsX(earliestTime, latestTime);
}
formsPlot.Refresh();
}
catch (Exception ex)
{
// 🛡️ 图表更新失败不影响主程序
System.Diagnostics.Debug.WriteLine($"Chart update error: {ex.Message}");
}
}
C#private void comboBoxInterfaces_SelectedIndexChanged(object sender, EventArgs e)
{
if (comboBoxInterfaces.SelectedIndex >= 0)
{
selectedInterface = networkInterfaces[comboBoxInterfaces.SelectedIndex];
// 🔄 重置统计基准
var stats = selectedInterface.GetIPv4Statistics();
lastBytesReceived = stats.BytesReceived;
lastBytesSent = stats.BytesSent;
lastUpdateTime = DateTime.Now;
// 清空历史数据重新开始
downloadHistory.Clear();
uploadHistory.Clear();
timeHistory.Clear();
// 重新初始化图表
formsPlot.Plot.Clear();
SetupChart();
labelStatus.Text = $"已切换到: {selectedInterface.Name}";
labelStatus.ForeColor = System.Drawing.Color.Blue;
}
}
private void buttonStartStop_Click(object sender, EventArgs e)
{
if (updateTimer.Enabled)
{
// 🛑 停止监控
updateTimer.Stop();
buttonStartStop.Text = "开始监控";
buttonStartStop.BackColor = System.Drawing.Color.FromArgb(40, 167, 69);
labelStatus.Text = "监控已停止";
labelStatus.ForeColor = System.Drawing.Color.Red;
}
else
{
// ▶️ 开始监控
updateTimer.Start();
buttonStartStop.Text = "停止监控";
buttonStartStop.BackColor = System.Drawing.Color.FromArgb(220, 53, 69);
labelStatus.Text = "监控已开始";
labelStatus.ForeColor = System.Drawing.Color.Green;
}
}
C#private string FormatSpeed(double bytesPerSecond)
{
// 🎯 智能单位转换
if (bytesPerSecond < 1024)
return $"{bytesPerSecond:F1} B/s";
else if (bytesPerSecond < 1024 * 1024)
return $"{bytesPerSecond / 1024:F1} KB/s";
else if (bytesPerSecond < 1024L * 1024 * 1024)
return $"{bytesPerSecond / (1024 * 1024):F1} MB/s";
else
return $"{bytesPerSecond / (1024L * 1024 * 1024):F1} GB/s";
}
private string FormatBytes(long bytes)
{
// 📊 流量统计格式化
if (bytes < 1024)
return $"{bytes} B";
else if (bytes < 1024 * 1024)
return $"{bytes / 1024.0:F1} KB";
else if (bytes < 1024L * 1024 * 1024)
return $"{bytes / (1024.0 * 1024):F1} MB";
else if (bytes < 1024L * 1024 * 1024 * 1024)
return $"{bytes / (1024.0 * 1024 * 1024):F1} GB";
else
return $"{bytes / (1024.0 * 1024 * 1024 * 1024):F1} TB";
}
C#private void UpdateHistory(double downloadSpeed, double uploadSpeed, DateTime currentTime)
{
downloadHistory.Add(downloadSpeed);
uploadHistory.Add(uploadSpeed);
timeHistory.Add(currentTime);
// 🔄 滑动窗口:自动清理过期数据
while (downloadHistory.Count > maxHistoryPoints)
{
downloadHistory.RemoveAt(0);
uploadHistory.RemoveAt(0);
timeHistory.RemoveAt(0);
}
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
// 🧹 优雅关闭:清理资源
updateTimer?.Stop();
updateTimer?.Dispose();
base.OnFormClosing(e);
}
C#using ScottPlot;
using ScottPlot.WinForms;
using System.Net.NetworkInformation;
using Timer = System.Windows.Forms.Timer;
namespace AppNetworkTrafficMonitor
{
public partial class Form1 : Form
{
private Timer updateTimer;
private NetworkInterface selectedInterface;
private List<NetworkInterface> networkInterfaces;
private List<double> downloadHistory;
private List<double> uploadHistory;
private List<DateTime> timeHistory;
private long lastBytesReceived;
private long lastBytesSent;
private DateTime lastUpdateTime;
private int maxHistoryPoints = 60; // 保留60个数据点
// ScottPlot 相关
private ScottPlot.Plottables.Scatter downloadPlot;
private ScottPlot.Plottables.Scatter uploadPlot;
public Form1()
{
InitializeComponent();
InitializeMonitor();
}
private void InitializeMonitor()
{
downloadHistory = new List<double>();
uploadHistory = new List<double>();
timeHistory = new List<DateTime>();
LoadNetworkInterfaces();
SetupChart();
updateTimer = new Timer();
updateTimer.Interval = 1000; // 每秒更新一次
updateTimer.Tick += UpdateTimer_Tick;
updateTimer.Start();
lastUpdateTime = DateTime.Now;
}
private void LoadNetworkInterfaces()
{
networkInterfaces = NetworkInterface.GetAllNetworkInterfaces()
.Where(ni => ni.OperationalStatus == OperationalStatus.Up &&
ni.NetworkInterfaceType != NetworkInterfaceType.Loopback)
.ToList();
comboBoxInterfaces.Items.Clear();
foreach (var ni in networkInterfaces)
{
comboBoxInterfaces.Items.Add($"{ni.Name} ({ni.NetworkInterfaceType})");
}
if (comboBoxInterfaces.Items.Count > 0)
{
comboBoxInterfaces.SelectedIndex = 0;
selectedInterface = networkInterfaces[0];
// 初始化基准值
var stats = selectedInterface.GetIPv4Statistics();
lastBytesReceived = stats.BytesReceived;
lastBytesSent = stats.BytesSent;
}
}
private void SetupChart()
{
// 清除所有绘图对象
formsPlot.Plot.Clear();
formsPlot.Plot.Legend.FontName = "SimSun";
// 设置坐标轴
formsPlot.Plot.Axes.Left.Label.Text = "速度 (KB/s)";
formsPlot.Plot.Axes.Bottom.Label.Text = "时间";
formsPlot.Plot.Axes.Left.Label.FontSize = 12;
formsPlot.Plot.Axes.Bottom.Label.FontSize = 12;
formsPlot.Plot.Axes.Bottom.Label.FontName = "SimSun";
formsPlot.Plot.Axes.Left.Label.FontName = "SimSun";
// 创建下载速度线条
downloadPlot = formsPlot.Plot.Add.Scatter(new double[0], new double[0]);
downloadPlot.Color = ScottPlot.Color.FromHtml("#007BFF"); // 蓝色
downloadPlot.LineWidth = 2;
downloadPlot.MarkerSize = 0;
downloadPlot.LegendText = "下载速度";
// 创建上传速度线条
uploadPlot = formsPlot.Plot.Add.Scatter(new double[0], new double[0]);
uploadPlot.Color = ScottPlot.Color.FromHtml("#DC3545"); // 红色
uploadPlot.LineWidth = 2;
uploadPlot.MarkerSize = 0;
uploadPlot.LegendText = "上传速度";
// 显示图例
formsPlot.Plot.ShowLegend(Alignment.UpperRight);
// 设置时间轴格式
formsPlot.Plot.Axes.DateTimeTicksBottom();
// 初始刷新
formsPlot.Refresh();
}
private void UpdateTimer_Tick(object sender, EventArgs e)
{
if (selectedInterface == null) return;
try
{
var stats = selectedInterface.GetIPv4Statistics();
var currentTime = DateTime.Now;
var timeSpan = (currentTime - lastUpdateTime).TotalSeconds;
if (timeSpan > 0 && lastBytesReceived > 0 && lastBytesSent > 0)
{
// 计算速度 (KB/s)
double downloadSpeed = (stats.BytesReceived - lastBytesReceived) / timeSpan / 1024;
double uploadSpeed = (stats.BytesSent - lastBytesSent) / timeSpan / 1024;
// 确保速度不为负数
downloadSpeed = Math.Max(0, downloadSpeed);
uploadSpeed = Math.Max(0, uploadSpeed);
// 更新实时显示
UpdateRealTimeDisplay(downloadSpeed, uploadSpeed, stats);
// 更新历史数据
UpdateHistory(downloadSpeed, uploadSpeed, currentTime);
// 更新图表
UpdateChart();
}
lastBytesReceived = stats.BytesReceived;
lastBytesSent = stats.BytesSent;
lastUpdateTime = currentTime;
}
catch (Exception ex)
{
labelStatus.Text = $"监控错误: {ex.Message}";
labelStatus.ForeColor = System.Drawing.Color.Red;
}
}
private void UpdateRealTimeDisplay(double downloadSpeed, double uploadSpeed, IPv4InterfaceStatistics stats)
{
// 使用Invoke确保在UI线程中更新
if (InvokeRequired)
{
Invoke(new Action(() => UpdateRealTimeDisplay(downloadSpeed, uploadSpeed, stats)));
return;
}
labelDownloadSpeed.Text = FormatSpeed(downloadSpeed * 1024); // 转换回 bytes/s 用于格式化
labelUploadSpeed.Text = FormatSpeed(uploadSpeed * 1024);
labelTotalDownload.Text = $"总下载: {FormatBytes(stats.BytesReceived)}";
labelTotalUpload.Text = $"总上传: {FormatBytes(stats.BytesSent)}";
// 更新进度条 (最大100 KB/s)
progressBarDownload.Value = Math.Min((int)downloadSpeed, 100);
progressBarUpload.Value = Math.Min((int)uploadSpeed, 100);
labelStatus.Text = $"监控中... | 下载: {FormatSpeed(downloadSpeed * 1024)} | 上传: {FormatSpeed(uploadSpeed * 1024)}";
labelStatus.ForeColor = System.Drawing.Color.Green;
}
private void UpdateHistory(double downloadSpeed, double uploadSpeed, DateTime currentTime)
{
downloadHistory.Add(downloadSpeed);
uploadHistory.Add(uploadSpeed);
timeHistory.Add(currentTime);
// 保持历史数据在指定范围内
while (downloadHistory.Count > maxHistoryPoints)
{
downloadHistory.RemoveAt(0);
uploadHistory.RemoveAt(0);
timeHistory.RemoveAt(0);
}
}
private void UpdateChart()
{
if (timeHistory.Count == 0) return;
try
{
// 转换时间为OADate格式用于ScottPlot
double[] timeArray = timeHistory.Select(t => t.ToOADate()).ToArray();
double[] downloadArray = downloadHistory.ToArray();
double[] uploadArray = uploadHistory.ToArray();
// 移除旧的图表对象
if (downloadPlot != null)
formsPlot.Plot.Remove(downloadPlot);
if (uploadPlot != null)
formsPlot.Plot.Remove(uploadPlot);
// 创建新的图表对象
downloadPlot = formsPlot.Plot.Add.Scatter(timeArray, downloadArray);
downloadPlot.Color = ScottPlot.Color.FromHtml("#007BFF"); // 蓝色
downloadPlot.LineWidth = 2;
downloadPlot.MarkerSize = 0;
downloadPlot.LegendText = "下载速度";
uploadPlot = formsPlot.Plot.Add.Scatter(timeArray, uploadArray);
uploadPlot.Color = ScottPlot.Color.FromHtml("#DC3545"); // 红色
uploadPlot.LineWidth = 2;
uploadPlot.MarkerSize = 0;
uploadPlot.LegendText = "上传速度";
// 显示图例
formsPlot.Plot.ShowLegend(Alignment.UpperRight);
// 自动调整坐标轴范围
formsPlot.Plot.Axes.AutoScale();
// 设置X轴范围为最近的时间窗口
if (timeArray.Length > 0)
{
var latestTime = timeArray[timeArray.Length - 1];
var earliestTime = DateTime.Now.AddSeconds(-maxHistoryPoints).ToOADate();
formsPlot.Plot.Axes.SetLimitsX(earliestTime, latestTime);
}
// 刷新图表
formsPlot.Refresh();
}
catch (Exception ex)
{
// 图表更新出错时不影响主程序运行
System.Diagnostics.Debug.WriteLine($"Chart update error: {ex.Message}");
}
}
private string FormatSpeed(double bytesPerSecond)
{
if (bytesPerSecond < 1024)
return $"{bytesPerSecond:F1} B/s";
else if (bytesPerSecond < 1024 * 1024)
return $"{bytesPerSecond / 1024:F1} KB/s";
else if (bytesPerSecond < 1024L * 1024 * 1024)
return $"{bytesPerSecond / (1024 * 1024):F1} MB/s";
else
return $"{bytesPerSecond / (1024L * 1024 * 1024):F1} GB/s";
}
private string FormatBytes(long bytes)
{
if (bytes < 1024)
return $"{bytes} B";
else if (bytes < 1024 * 1024)
return $"{bytes / 1024.0:F1} KB";
else if (bytes < 1024L * 1024 * 1024)
return $"{bytes / (1024.0 * 1024):F1} MB";
else if (bytes < 1024L * 1024 * 1024 * 1024)
return $"{bytes / (1024.0 * 1024 * 1024):F1} GB";
else
return $"{bytes / (1024.0 * 1024 * 1024 * 1024):F1} TB";
}
private void comboBoxInterfaces_SelectedIndexChanged(object sender, EventArgs e)
{
if (comboBoxInterfaces.SelectedIndex >= 0)
{
selectedInterface = networkInterfaces[comboBoxInterfaces.SelectedIndex];
// 重置统计
var stats = selectedInterface.GetIPv4Statistics();
lastBytesReceived = stats.BytesReceived;
lastBytesSent = stats.BytesSent;
lastUpdateTime = DateTime.Now;
// 清空历史数据
downloadHistory.Clear();
uploadHistory.Clear();
timeHistory.Clear();
// 清空图表
formsPlot.Plot.Clear();
SetupChart();
labelStatus.Text = $"已切换到: {selectedInterface.Name}";
labelStatus.ForeColor = System.Drawing.Color.Blue;
}
}
private void buttonRefresh_Click(object sender, EventArgs e)
{
LoadNetworkInterfaces();
labelStatus.Text = "网络接口已刷新";
labelStatus.ForeColor = System.Drawing.Color.Blue;
}
private void buttonStartStop_Click(object sender, EventArgs e)
{
if (updateTimer.Enabled)
{
updateTimer.Stop();
buttonStartStop.Text = "开始监控";
buttonStartStop.BackColor = System.Drawing.Color.FromArgb(40, 167, 69);
labelStatus.Text = "监控已停止";
labelStatus.ForeColor = System.Drawing.Color.Red;
}
else
{
updateTimer.Start();
buttonStartStop.Text = "停止监控";
buttonStartStop.BackColor = System.Drawing.Color.FromArgb(220, 53, 69);
labelStatus.Text = "监控已开始";
labelStatus.ForeColor = System.Drawing.Color.Green;
}
}
private void buttonClearHistory_Click(object sender, EventArgs e)
{
downloadHistory.Clear();
uploadHistory.Clear();
timeHistory.Clear();
formsPlot.Plot.Clear();
SetupChart();
labelStatus.Text = "历史数据已清空";
labelStatus.ForeColor = System.Drawing.Color.Blue;
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
updateTimer?.Stop();
updateTimer?.Dispose();
base.OnFormClosing(e);
}
}
}

想要让你的监控工具更强大?试试这些扩展:
通过这个项目,我们掌握了:
这不仅是一个实用工具,更是一个完整的技术综合体验。无论你是想提升C#技能,还是需要解决实际的网络监控需求,这个项目都能给你带来价值。
你在网络监控方面遇到过哪些挑战? 欢迎在评论区分享你的经验,或者说说你想要哪些额外功能!
如果这篇文章对你有帮助,请转发给更多需要的同行,让我们一起构建更强大的开发者社区!🚀
相关信息
通过网盘分享的文件:AppNetworkTrafficMonitor.zip 链接: https://pan.baidu.com/s/1Mlqn7HjHTeRLK5ziyxSQ6w?pwd=bd3w 提取码: bd3w --来自百度网盘超级会员v9的分享
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!