【问题标题】:Detecting if internet connection is busy检测互联网连接是否繁忙
【发布时间】:2012-02-22 12:08:03
【问题描述】:

我们正在开发一个将安装在 PC 上的应用程序,它将执行一些后台上传和下载到我们的服务器的操作。其中一项要求是检测 Internet 连接当前是否繁忙(例如,利用率高于 50%),如果是,则需要退出并再次尝试。主要原因是为了确保应用不会干扰用户在玩游戏、看在线电影或大量下载文件时的体验

经过对谷歌的大量思考和研究,当然还有 SO,我仍然没有找到如何实现这一点的好方法,所以决定把它扔在这里。 该应用程序是用 C#、.NET 4.0 实现的,我正在寻找所有形式的响应——无论是用 C# 还是其他语言实现、伪逻辑或如何实现的方法——测量互联网流量利用率具有足够准确度的本地 PC。

为了避免重复劳动,到目前为止我已经尝试了这些(以及为什么它们不适合)

  • 使用 WMI 获取网络统计信息。大多数 SO 帖子和解决方案都将其称为方法,但它不符合我们的要求,因为根据网络接口容量(例如 1GB 以太网卡)测量发送/接收的字节以供使用将产生一个很好的 LAN 流量测量但不适用于互联网流量(实际互联网带宽可能只有 8Mbps)
  • 使用 .NET 网络信息统计或性能计数器 - 产生与上述相似的读数,因此具有相同的缺点
  • 使用 ICMP (Ping) 并测量 RTT。有人建议 400 毫秒 RTT 被认为是网络繁忙的慢速和良好指示,但是我被告知使用调制解调器的用户(是的,我们必须支持),使用反向代理或微波链路通常会得到 ping 以上,因此不是一个好衡量
  • 开始下载已知文件并测量速度 - 这本身会产生我们试图避免的流量,而且如果此检查执行得足够频繁,我们的应用程序最终会产生大量互联网流量 - 这同样不理想
  • MOD:使用 BITS - 可以在用户 PC 上禁用此服务,需要更改组策略并假设服务器是 IIS(具有自定义配置),在我们的例子中,我们的服务器不是 IIS

就是这样,我很困惑并寻求一些建议。我突出显示了问题文本,这样你们就不会迷失阅读本文并想知道问题是什么。谢谢。

【问题讨论】:

  • 正确的解决方案是路由器上的 QoS。可惜大多数消费级路由器都很糟糕。
  • 似乎是一个常见问题,但似乎没有通用解决方案:(
  • 因为这个问题在PC层面是不可能解决的。您的软件无法知道使用同一路由器的其他人正在产生多少流量。
  • 我非常同意你的看法。在我放弃之前,我想,我应该看看其他人是否有同样的想法,或者过去可能已经用一些聪明的想法解决了这个问题并愿意分享他们的经验。
  • 如果您的上传或下载开始时间超过 N 秒,您可以限制它,然后在 N 后全速重试秒数?

标签: c# .net language-agnostic network-utilization


【解决方案1】:

您可以使用 UPnP 查询路由器,并检索通过网络发送和接收的字节数。您可以继续在路由器上检查此值以确定活动是什么。不幸的是,这个功能似乎没有很好的文档记录,但可以在 C# 应用程序中实现 UPnP 通信功能。您将需要使用 UDP 查询路由器(UPnP 发现),一旦您找到设备,查询其功能,然后使用 WebClient (TCP) 查询 Internet 网关设备发送和接收的数据包数。

UPnP 库的代码: using System; using System.Collections.Generic; using System.Text; using System.Net.Sockets; using System.Net; using System.Xml; using System.IO; namespace UPNPLib { public class RouterElement { public RouterElement() { } public override string ToString() { return Name; } public List children = new List(); public RouterElement parent; public string Name; public string Value; public RouterElement this[string name] { get { foreach (RouterElement et in children) { if (et.Name.ToLower().Contains(name.ToLower())) { return et; } } foreach (RouterElement et in children) { Console.WriteLine(et.Name); } throw new KeyNotFoundException("Unable to find the specified entry"); } } public RouterElement(XmlNode node, RouterElement _parent) { Name = node.Name; if (node.ChildNodes.Count /// Gets the root URL of the device /// /// public static string GetRootUrl() { StringBuilder mbuilder = new StringBuilder(); mbuilder.Append("M-SEARCH * HTTP/1.1\r\n"); mbuilder.Append("HOST: 239.255.255.250:1900\r\n"); mbuilder.Append("ST:upnp:rootdevice\r\n"); mbuilder.Append("MAN:\"ssdp:discover\"\r\n"); mbuilder.Append("MX:3\r\n\r\n"); UdpClient mclient = new UdpClient(); byte[] dgram = Encoding.ASCII.GetBytes(mbuilder.ToString()); mclient.Send(dgram,dgram.Length,new IPEndPoint(IPAddress.Broadcast,1900)); IPEndPoint mpoint = new IPEndPoint(IPAddress.Any, 0); rootsearch: dgram = mclient.Receive(ref mpoint); string mret = Encoding.ASCII.GetString(dgram); string orig = mret; mret = mret.ToLower(); string url = orig.Substring(mret.IndexOf("location:") + "location:".Length, mret.IndexOf("\r", mret.IndexOf("location:")) - (mret.IndexOf("location:") + "location:".Length)); WebClient wclient = new WebClient(); try { Console.WriteLine("POLL:" + url); string reply = wclient.DownloadString(url); if (!reply.ToLower().Contains("router")) { goto rootsearch; } } catch (Exception) { goto rootsearch; } return url; } public static RouterElement enumRouterFunctions(string url) { XmlReader mreader = XmlReader.Create(url); XmlDocument md = new XmlDocument(); md.Load(mreader); XmlNodeList rootnodes = md.GetElementsByTagName("serviceList"); RouterElement elem = new RouterElement(); foreach (XmlNode et in rootnodes) { RouterElement el = new RouterElement(et, null); elem.children.Add(el); } return elem; } public static RouterElement getRouterInformation(string url) { XmlReader mreader = XmlReader.Create(url); XmlDocument md = new XmlDocument(); md.Load(mreader); XmlNodeList rootnodes = md.GetElementsByTagName("device"); return new RouterElement(rootnodes[0], null); } } public class RouterMethod { string url; public string MethodName; string parentname; string MakeRequest(string URL, byte[] data, string[] headers) { Uri mri = new Uri(URL); TcpClient mclient = new TcpClient(); mclient.Connect(mri.Host, mri.Port); Stream mstream = mclient.GetStream(); StreamWriter textwriter = new StreamWriter(mstream); textwriter.Write("POST "+mri.PathAndQuery+" HTTP/1.1\r\n"); textwriter.Write("Connection: Close\r\n"); textwriter.Write("Content-Type: text/xml; charset=\"utf-8\"\r\n"); foreach (string et in headers) { textwriter.Write(et + "\r\n"); } textwriter.Write("Content-Length: " + (data.Length).ToString()+"\r\n"); textwriter.Write("Host: " + mri.Host+":"+mri.Port+"\r\n"); textwriter.Write("\r\n"); textwriter.Flush(); Stream reqstream = mstream; reqstream.Write(data, 0, data.Length); reqstream.Flush(); StreamReader reader = new StreamReader(mstream); while (reader.ReadLine().Length > 2) { } return reader.ReadToEnd(); } public RouterElement Invoke(string[] args) { MemoryStream mstream = new MemoryStream(); StreamWriter mwriter = new StreamWriter(mstream); //TODO: Implement argument list string arglist = ""; mwriter.Write("" + "" + ""); mwriter.Write("");//" + arglist + ""); mwriter.Write(""); mwriter.Flush(); List headers = new List(); headers.Add("SOAPAction: \"" + parentschema + "#" + MethodName + "\""); mstream.Position = 0; byte[] dgram = new byte[mstream.Length]; mstream.Read(dgram, 0, dgram.Length); XmlDocument mdoc = new XmlDocument(); string txt = MakeRequest(url, dgram, headers.ToArray()); mdoc.LoadXml(txt); try { RouterElement elem = new RouterElement(mdoc.ChildNodes[0], null); return elem["Body"].children[0]; } catch (Exception er) { RouterElement elem = new RouterElement(mdoc.ChildNodes[1], null); return elem["Body"].children[0]; } } public List parameters = new List(); string baseurl; string parentschema; public RouterMethod(string svcurl, RouterElement element,string pname, string baseURL, string svcpdsc) { parentschema = svcpdsc; baseurl = baseURL; parentname = pname; url = svcurl; MethodName = element["name"].Value; try { foreach (RouterElement et in element["argumentList"].children) { parameters.Add(et.children[0].Value); } } catch (KeyNotFoundException) { } } } public class RouterService { string url; public string ServiceName; public List methods = new List(); public RouterMethod GetMethodByNonCaseSensitiveName(string name) { foreach (RouterMethod et in methods) { if (et.MethodName.ToLower() == name.ToLower()) { return et; } } throw new KeyNotFoundException(); } public RouterService(RouterElement element, string baseurl) { ServiceName = element["serviceId"].Value; url = element["controlURL"].Value; WebClient mclient = new WebClient(); string turtle = element["SCPDURL"].Value; if (!turtle.ToLower().Contains("http")) { turtle = baseurl + turtle; } Console.WriteLine("service URL " + turtle); string axml = mclient.DownloadString(turtle); XmlDocument mdoc = new XmlDocument(); if (!url.ToLower().Contains("http")) { url = baseurl + url; } mdoc.LoadXml(axml); XmlNode mainnode = mdoc.GetElementsByTagName("actionList")[0]; RouterElement actions = new RouterElement(mainnode, null); foreach (RouterElement et in actions.children) { RouterMethod method = new RouterMethod(url, et,ServiceName,baseurl,element["serviceType"].Value); methods.Add(method); } } } } Code for a bandwidth meter: using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using UPNPLib; using System.IO; namespace bandwidthmeter { public partial class Form1 : Form { public Form1() { InitializeComponent(); BinaryReader mreader = new BinaryReader(File.Open("bandwidthlog.txt", FileMode.OpenOrCreate)); if (mreader.BaseStream.Length > 0) { prevsent = mreader.ReadInt64(); prevrecv = mreader.ReadInt64(); } mreader.Close(); List services = new List(); string fullurl = UPNP.GetRootUrl(); RouterElement router = UPNP.enumRouterFunctions(fullurl); Console.WriteLine("Router feature enumeration complete"); foreach (RouterElement et in router.children) { services.Add(new RouterService(et.children[0], fullurl.Substring(0, fullurl.IndexOf("/", "http://".Length+1)))); } getReceiveDelegate = services[1].GetMethodByNonCaseSensitiveName("GetTotalBytesReceived"); getSentDelegate = services[1].GetMethodByNonCaseSensitiveName("GetTotalBytesSent"); Console.WriteLine("Invoking " + getReceiveDelegate.MethodName); //Console.WriteLine(services[1].GetMethodByNonCaseSensitiveName("GetTotalPacketsSent").Invoke(null)); Timer mymer = new Timer(); mymer.Tick += new EventHandler(mymer_Tick); mymer.Interval = 1000; mymer.Start(); FormClosed += new FormClosedEventHandler(Form1_FormClosed); } long prevsent = 0; long prevrecv = 0; void Form1_FormClosed(object sender, FormClosedEventArgs e) { BinaryWriter mwriter = new BinaryWriter(File.Open("bandwidthlog.txt", FileMode.OpenOrCreate)); mwriter.Write(getsent()); mwriter.Write(getreceived()); mwriter.Flush(); mwriter.Close(); } long getsent() { long retval = Convert.ToInt64(getSentDelegate.Invoke(null).children[0].Value); if (prevsent > retval) { retval = prevsent + retval; } return retval; } long getreceived() { long retval = Convert.ToInt64(getReceiveDelegate.Invoke(null).children[0].Value); if (prevrecv > retval) { retval = prevrecv + retval; } return retval; } void mymer_Tick(object sender, EventArgs e) { label1.Text = "Sent: "+(getsent()/1024/1024).ToString()+"MB\nReceived: "+(getreceived()/1024/1024).ToString()+"MB"; } RouterMethod getSentDelegate; RouterMethod getReceiveDelegate; } }

【讨论】:

  • 感谢您的回复。这种方法是否适用于所有受支持的 Win OS 和大多数路由器?此外,一旦我收到发送和接收的数据包,我该如何确定利用率(例如在我的示例中 > 50% 被认为是忙)
  • 是的,这种方法适用于大多数消费者路由器,但不适用于大多数商业路由器(企业路由器默认禁用 UPnP)。
  • 请阅读这篇文章 - codeproject.com/Articles/27992/NAT-Traversal-with-UPnP-in-C 了解 UPnP 的介绍。此示例旨在用于端口转发,但您可以使用路由器提供的 URL 来查找其其他功能(带宽功能通常在大多数路由器中是相同的)。
  • +1 感谢您提供的示例和信息。一旦我们在您的代码示例中发送/接收字节,我仍然不清楚您如何确定利用率百分比?
  • @FadrianSudaman 您需要让用户选择他们的带宽量,类似于免费下载管理器的方式(他们询问用户有多少可用带宽),或者您可以使用第三个派对网站 API 运行速度测试。
【解决方案2】:

您是否考虑过使用Background Intelligent Transfer Service (BITS)。它的设计初衷就是为了完成这项工作:

后台智能传输服务 (BITS) 在客户端和服务器之间传输文件(下载或上传),并提供与传输相关的进度信息。您还可以从对等点下载文件。

和,

保持其他网络应用程序的响应能力。

我不确定它是否有托管接口(我可以看到对 Powershell Cmdlets 的引用),因此您可能必须使用 COM 互操作才能使用它。

【讨论】:

  • 是的,但很遗憾,由于多种原因,它不能满足我们的需求:可以禁用 BITS 服务,它需要更改组策略和服务器端点(假设为 IIS),并且需要配置- 在我们的例子中,服务器不是 IIS。 +1 建议。
【解决方案3】:

假设您的目标是 Windows PC(正如您所说您使用 C# 进行开发),您是否查看过后台智能传输服务 BITS

在 MSDN 和其他地方有如何使用 C# 连接它的示例,例如http://msdn.microsoft.com/en-us/magazine/cc188766.aspx

【讨论】:

  • 感谢您的反馈。是的,我有,请参阅我对 Damien 的回答,其中涉及到同样的事情。
猜你喜欢
  • 1970-01-01
  • 2018-12-27
  • 2022-01-14
  • 1970-01-01
  • 1970-01-01
  • 2012-01-17
  • 2023-03-30
相关资源
最近更新 更多