如何计算HttpWebRequest花费的出站和入站互联网流量

时间:2014-08-13 21:07:17

标签: c# wpf httpwebrequest webrequest network-traffic

我有以下功能来获取页面。我的问题是我想计算花了多少互联网连接

入站(下载)和出站流量(已发送)

我该怎么做?谢谢

我的功能

 public static string func_fetch_Page(string srUrl, int irTimeOut = 60,
    string srRequestUserAgent = "Mozilla/5.0 (Windows NT 6.3; WOW64; rv:31.0) Gecko/20100101 Firefox/31.0",
    string srProxy = null)
    {
        string srBody = "";
        string srResult = "";
        try
        {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(srUrl);

            request.Timeout = irTimeOut * 1000;
            request.UserAgent = srRequestUserAgent;
            request.KeepAlive = true;
            request.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";

            WebHeaderCollection myWebHeaderCollection = request.Headers;
            myWebHeaderCollection.Add("Accept-Language", "en-gb,en;q=0.5");
            myWebHeaderCollection.Add("Accept-Encoding", "gzip, deflate");

            request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;

            using (WebResponse response = request.GetResponse())
            {
                using (Stream strumien = response.GetResponseStream())
                {
                    using (StreamReader sr = new StreamReader(strumien))
                    {
                        srBody = sr.ReadToEnd();
                        srResult = "success";
                    }
                }
            }
        }
        catch ()
        {

        }

        return srBody;
    }

C#.net 4.5 WPF应用程序

@Simon Mourier如何计算花费的流量

public static long long_GlobalDownload_KByte = 0;
public static long long_GlobalSent_KByte = 0;

Time  _timer_fetch_download_upload = new Timer(getDownload_Upload_Values, null, 0, 100 * 1000);

public static void getDownload_Upload_Values(Object state)
{
    using (Process p = Process.GetCurrentProcess())
    {
        foreach (var cnx in TcpConnection.GetAll().Where(c => c.ProcessId == p.Id))
        {
            Interlocked.Add(ref long_GlobalDownload_KByte, Convert.ToInt64(cnx.DataBytesIn));
            Interlocked.Add(ref long_GlobalSent_KByte, Convert.ToInt64(cnx.DataBytesOut));
        }
    }
}

6 个答案:

答案 0 :(得分:6)

一个Windows API可以为您提供以下信息:GetPerTcpConnectionEStats用于IPV4连接和相关的IPV6 GetPerTcp6ConnectionEStats功能。请注意,您需要先使用SetPerTcpConnectionEStats才能获得任何统计信息,这通常需要管理员权限...

要获取所有连接的列表,可以使用GetExtendedTcpTable功能。它还可以为您提供连接的进程ID,这非常有用。

这些本机API并不容易使用,但我创建了一个包装所有这些的TcpConnection类。它可以在一个名为IPStats的小型WPF应用程序中找到:https://github.com/smourier/IPStats

因此,这里的困难是将.NET HttpWebRequest链接到连接列表中的TcpConnection。 在连接存在之前,您无法获得任何统计信息,但是一旦创建了连接,就可以使用如下代码获取相应的连接:

    static IEnumerable<TcpConnection> GetProcessConnection(IPEndPoint ep)
    {
        var p = Process.GetCurrentProcess();
        return TcpConnection.GetAll().Where(c => ep.Equals(c.RemoteEndPoint) && c.ProcessId == p.Id);
    }

    HttpWebRequest req = ...

    // this is how you can get the enpoint, or you can also built it by yourself manually
    IPEndPoint remoteEndPoint;
    req.ServicePoint.BindIPEndPointDelegate += (sp, rp, rc) =>
        {
            remoteEndPoint = rp;
            return null;
        };
    // TODO: here, you need to connect, so the connection exists
    var cnx = GetProcessConnection(remoteEndPoint).FirstOrDefault();

    // access denied here means you don't have sufficient rights
    cnx.DataStatsEnabled = true;

    // TODO: here, you need to do another request, so the values are incremented
    // now, you should get non-zero values here
    // note TcpConnection also has int/out bandwidth usage, and in/out packet usage.
    Console.WriteLine("DataBytesIn:" + cnx.DataBytesIn);
    Console.WriteLine("DataBytesOut:" + cnx.DataBytesOut);

    // if you need all connections in the current process, just do this
    ulong totalBytesIn = 0;
    ulong totalBytesOut = 0;
    Process p = Process.GetCurrentProcess();
    foreach (var cnx in TcpConnection.GetAll().Where(c => c.ProcessId == p.Id))
    {
        totalBytesIn += cnx.DataBytesIn;
        totalBytesOut += cnx.DataBytesOut;
    }

有3个缺点:

  • 您需要成为管理员以启用数据统计信息;
  • 您无法获得第一个请求的统计信息。这可能不是问题,具体取决于您的问题 上下文;
  • HttpWebRequest的连接与TCP连接之间的匹配可能比较难以确定,因为即使在同一进程中,您也可以与远程端点建立多个连接。区分的唯一方法是确定本地端点(尤其是端口)并使用此本地端点扩展GetProcessConnection。不幸的是,没有简单的方法可以做到这一点。以下是相关答案:How do I get the local port number of an HttpWebRequest?

更新:如果你想连续监控给定进程的所有连接,我编写了一个ProcessTcpConnections实用程序类来记住所有连接并总结它们的用法。它会像这样使用(在控制台应用程序示例中):

class Program
{
    static void Main(string[] args)
    {
        ProcessTcpConnections p = new ProcessTcpConnections(Process.GetCurrentProcess().Id);
        Timer timer = new Timer(UpdateStats, p, 0, 100);

        do
        {
            // let's activate the network so we measure something...
            using (WebClient client = new WebClient())
            {
                client.DownloadString("http://www.example.com");
            }
            Console.ReadKey(true); // press any key to download again
        }
        while (true);
    }

    private static void UpdateStats(object state)
    {
        ProcessTcpConnections p = (ProcessTcpConnections)state;
        p.Update();
        Console.WriteLine("DataBytesIn:" + p.DataBytesIn + " DataBytesOut:" + p.DataBytesOut);
    }
}

public class ProcessTcpConnections : TcpConnectionGroup
{
    public ProcessTcpConnections(int processId)
        : base(c => c.ProcessId == processId)
    {
        ProcessId = processId;
    }

    public int ProcessId { get; private set; }
}

public class TcpConnectionGroup
{
    private List<TcpConnectionStats> _states = new List<TcpConnectionStats>();
    private Func<TcpConnection, bool> _groupFunc;

    public TcpConnectionGroup(Func<TcpConnection, bool> groupFunc)
    {
        if (groupFunc == null)
            throw new ArgumentNullException("groupFunc");

        _groupFunc = groupFunc;
    }

    public void Update()
    {
        foreach (var conn in TcpConnection.GetAll().Where(_groupFunc))
        {
            if (!conn.DataStatsEnabled)
            {
                conn.DataStatsEnabled = true;
            }

            TcpConnectionStats existing = _states.Find(s => s.Equals(conn));
            if (existing == null)
            {
                existing = new TcpConnectionStats();
                _states.Add(existing);
            }
            existing.DataBytesIn = conn.DataBytesIn;
            existing.DataBytesOut = conn.DataBytesOut;
            existing.LocalEndPoint = conn.LocalEndPoint;
            existing.RemoteEndPoint = conn.RemoteEndPoint;
            existing.State = conn.State;
            existing.LastUpdateTime = DateTime.Now;
        }
    }

    public ulong DataBytesIn
    {
        get
        {
            ulong count = 0; foreach (var state in _states) count += state.DataBytesIn; return count;
        }
    }

    public ulong DataBytesOut
    {
        get
        {
            ulong count = 0; foreach (var state in _states) count += state.DataBytesOut; return count;
        }
    }

    private class TcpConnectionStats
    {
        public ulong DataBytesIn { get; set; }
        public ulong DataBytesOut { get; set; }
        public IPEndPoint LocalEndPoint { get; set; }
        public IPEndPoint RemoteEndPoint { get; set; }
        public TcpState State { get; set; }
        public DateTime LastUpdateTime { get;  set; }

        public bool Equals(TcpConnection connection)
        {
            return LocalEndPoint.Equals(connection.LocalEndPoint) && RemoteEndPoint.Equals(connection.RemoteEndPoint);
        }
    }
}

答案 1 :(得分:6)

您可以使用FiddlerCore创建代理(只需要一个dll来引用),并将 WebProxy 设置为HttpWebRequest来计算发送和接收的字节数

public class WebConnectionStats
{
    static int _Read = 0;
    static int _Written = 0;

    public static void Init(bool registerAsSystemProxy = false)
    {
        Fiddler.FiddlerApplication.OnReadRequestBuffer += (s, e) => Interlocked.Add(ref _Written, e.iCountOfBytes);
        Fiddler.FiddlerApplication.OnReadResponseBuffer += (s, e) => Interlocked.Add(ref _Read, e.iCountOfBytes);
        Fiddler.FiddlerApplication.Startup(8088, registerAsSystemProxy, true);
    }

    public static int Read
    {
        get { return _Read; }
    }

    public static int Written
    {
        get { return _Written; }
    }
}

WebConnectionStats.Init(); //call this only once

var client = HttpWebRequest.Create("http://stackoverflow.com") as HttpWebRequest;
client.Proxy = new WebProxy("127.0.0.1", 8088);
var resp = client.GetResponse();
var html = new StreamReader(resp.GetResponseStream()).ReadToEnd();

Console.WriteLine("Read: {0}   Write: {1}", WebConnectionStats.Read, 
                                            WebConnectionStats.Written);

PS1:此计数不包括 Tcp标题长度

PS2 :您可以获得有关Fiddler核心的更多信息here

答案 2 :(得分:4)

由于一些不可预测的以太网流量等等,很难知道完全正在发生多少网络流量,但基本上任何HTTP请求的要点都是:

method request-uri version
* (header : value)
CRLF
body

鉴于此,您可以计算出请求字符串的长度:

HttpWebRequest req = ...;
StringBuilder requestText = new StringBuilder();

requestText.AppendFormat("{0} {1} HTTP/{2}.{3}", req.Method, req.RequestUri, req.ProtocolVersion.Major, req.ProtocolVersion.Minor);

requestText.AppendLine();

foreach (var header in req.Headers)
{
    requestText.AppendFormat("{0}: {1}", v, webReq.Headers[v]);
    requestText.AppendLine();
}

requestText.AppendLine();

// somehow add on the contents of the request stream, or just add that length later. I won't put that in this code because of stream positioning and all that

return System.Text.Encoding.UTF8.GetByteCount(requestText.ToString());

然后它在响应方面非常相似。 HTTP响应的格式为:

version status-code status-description
* (header : value)
CRLF
body

所以,

HttpWebResponse resp = ...;
StringBuilder responseText = new StringBuilder();

responseText .AppendFormat("HTTP/{0}.{1} {2} {3}", resp.ProtocolVersion.Major, resp.ProtocolVersion.Minor, (int)resp.StatusCode, resp.StatusDescription);
responseText .AppendLine();

foreach (var header in resp.Headers)
{
    responseText .AppendFormat("{0}: {1}", v, resp.Headers[v]);
    responseText .AppendLine();
}

responseText.AppendLine();

这里有一点决定。您需要获取响应正文的长度。可能有更多的选择,但我现在想的是你可以:

  1. 编写一个Stream包装器,用于获取复制的字节数。这样你就不必担心分块转移了,写起来可能会很有趣。
  2. 使用content-length标题,但如果该标题,则不会得到它,如transfer-encoding: chunked的情况。
  3. 使用您想要的任何方法读取流,然后在其中添加长度。
  4. 将流复制到MemoryStream,然后将其作为新的响应流传递,同时抓住路上的长度。
  5. 所有人都说,你对额外担心内容压缩感到沮丧。事实上,我看到你在你的身上使用它。鉴于简单性,我将假设GZip并采用第四种选择。您可能希望扩展我在此处所写的内容,以使其更全面。

    // webReq.AutomaticDecompression = DecompressionMethods.None; is required for this, since we're handling that decompression ourselves.
    
    using (var respStream = resp.GetResponseStream())
    using (var memStream = new MemoryStream())
    {
        respStream.CopyTo(memStream);
    
        using (var gzip = new System.IO.Compression.GZipStream(respStream, System.IO.Compression.CompressionMode.Decompress))
        using (var reader = new StreamReader(gzip))
        {
            var content = reader.ReadToEnd();
    
            // you may or may not actually care about this, depending on whether this is just light testing or if you'll actually have these metrics in production
        }
    
        return System.Text.Encoding.UTF8.GetByteCount(responseText.ToString()) + memStream.Length;
    }
    

    现在,我现在看到的一些警告。其他人可能会注意到更多,如果人们发表评论我会添加它们。

    • 正如我在开头提到的那样,一个请求可能会有更多的网络流量,而不是告诉你的。
    • 根据客户端和服务器的不同,您可能会发现可以使用不同数量的空格列出实际标头。我不相信这超出了HTTP规范,即使是人们也会这样做。因此,对于随机示例,您可能会看到一个服务器集content-type: text/html和另一个集content-type : text/html至少(确实,因为我们使用的是UTF-8)一个字节的区别。再说一点,但这可能存在差异。

    这只是一个估计。这是一个很好的,但它只是一个估计。

    如果您想以一些简单的方式寻找额外的准确性,您也可以自己编写代理。 HTTP代理的想法是它只是获得逐字的请求,因此获得长度“非常容易”。当然,最大的问题是你必须编写一个功能齐全的代理,它很可能解析和重新请求你的传入请求,并解析和转发他们各自的响应。当然可行,但这不是微不足道的。特别是如果您不得不担心SSL。

    在最基本的层面上,当然,你可以写一个像WireShark这样的程序的替代品。我不知道该怎么做,除非你真的需要它,否则我不建议你这么做。这仍然不会给你一个完美的想法。更接近。

    现在,所有人都说,而且男孩,这是相当多的说法,如果你为了剖析的目的这样做,那么Visual Studio中内置的工具很可能会让你这样做。不可否认,我对他们的了解很远 - 我从来没有打开它们 - 但我相信有网络流量分析器可用。当然,我怀疑他们会像这样在每个平台上工作。但这肯定是一种更容易的方法。

    此外,如果有人碰巧在这里注意到任何拼写错误,我会复制粘贴几次,我想我已经完成了所有这些,但请随时告诉我或者修复它们。

答案 3 :(得分:3)

它有点难看但你可以使用网络跟踪然后解析日志。 有关启用网络跟踪的信息,请参阅http://msdn.microsoft.com/en-us/library/ty48b824(v=vs.110).aspx

我添加了基本代码(我知道我在解析日志时遇到了错误但想法应该清楚了)

<强> NetworkListner.cs

using System;
using System.Diagnostics;
using System.Globalization;
using System.Text.RegularExpressions;

namespace NetworkTracing
{
    /// <summary>
    /// Description of NetworkListner.
    /// </summary>
    public class NetworkListner : TraceListener
    {
        public static int BytesSent { get; private set;} 
        public static int BytesReceived { get; private set;}
        private bool _inSend = false;
        private bool _inReceived = false;
        private string _lastMessage = "";
        private Regex _lengthRegex = new Regex(@"(\[\d*\]) ([\dA-F]*)");
        public NetworkListner()
        {
            BytesSent = 0;
            BytesReceived = 0;          
        }

        private int ExtractNumOfBytes(string message){
            string lengthUntilThisLineStr = null;
            try {           
                var match = _lengthRegex.Match(message);                
                lengthUntilThisLineStr = match.Groups[2].Value;
            } catch (ArgumentException ex) {
                // Syntax error in the regular expression
            }
            if (String.IsNullOrEmpty(lengthUntilThisLineStr)) {
                return 0;
            }
            var lengthUntilThisLine = int.Parse(lengthUntilThisLineStr,NumberStyles.HexNumber);
            return lengthUntilThisLine;
        }

        public override void Write(string message) {
            if (message.Equals("System.Net.Sockets Verbose: 0 : ")) {
                return;
            }
            if (message.Contains("Exiting Socket#")) {
                int bytes = ExtractNumOfBytes(_lastMessage);
                if (_inSend) {
                    _inSend = false;
                    BytesSent += bytes;
                }else if (_inReceived) {
                    _inReceived = false;
                    BytesReceived += bytes;
                }   
            }           
            else if (message.Contains("Data from Socket")){
                if (message.Contains("Send")) {
                    _inSend = true;
                }
                else if (message.Contains("Receive")) {
                    _inReceived = true;
                }
            }
            _lastMessage = message;
        }

        public override void WriteLine(string message) {
            Write(message + Environment.NewLine);
        }
    }
}

<强> Program.cs的

using System;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Net;

namespace NetworkTracing
{
    class Program
    {
        public static void Main(string[] args)
        {           
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://www.google.com");
            request.UserAgent = "Mozilla/5.0 (Windows NT 6.3; WOW64; rv:31.0) Gecko/20100101 Firefox/31.0";
            using (WebResponse response = request.GetResponse())
            {
                using (Stream strumien = response.GetResponseStream())
                {
                    using (StreamReader sr = new StreamReader(strumien))
                    {
                        var res = sr.ReadToEnd();
                        Console.WriteLine("Send -> {0}",NetworkListner.BytesSent);
                        Console.WriteLine("Recieve -> {0}",NetworkListner.BytesReceived);
                        Console.ReadLine();
                    }
                }
            }
        }
    }

<强>的App.config

<configuration>
<system.diagnostics>
    <sources>
      <source name="System.Net.Sockets" tracemode="includehex" maxdatasize="1024">
        <listeners>
          <add name="network"/>
        </listeners>
      </source>
    </sources>
     <switches>
      <add name="System.Net.Sockets" value="Verbose"/>      
    </switches>
    <sharedListeners>
      <add name="network"
        type="NetworkTracing.NetworkListner, NetworkTracing"
      />
    </sharedListeners>
    <trace autoflush="true"/>
  </system.diagnostics>
</configuration>

您还需要通过以下方式编译带有跟踪标志的项目(此标志在调试中是默认的):

  1. 在“解决方案资源管理器”中选择项目,在“项目”菜单上
  2. 点击“属性”。
  3. 单击“编译”选项卡。
  4. 单击“高级编译选项”按钮以打开“高级” “编译器设置”对话框。
  5. 选中“定义TRACE常量”复选框,然后单击“确定”。

答案 4 :(得分:1)

如果您的客户端应用程序正在与您的已知IIS服务器进行通信,那么我将尝试从IIS日志中获取此数据。检查cs-bytes(客户端到服务器字节,即请求字节)和sc-bytes(服务器到客户端字节,即响应)。 http://technet.microsoft.com/en-us/library/cc754702(v=ws.10).aspx

答案 5 :(得分:1)

我认为你可以使用 .NET CLR Networking 性能计数器,因为它可以为每个AppDomain提供发送和接收的字节

http://msdn.microsoft.com/en-us/library/70xadeyt%28v=vs.110%29.aspx

我写了一个Helper类来验证准确性,结果与Windows ResourceMonitor类似,所以我认为准确性应该是可以接受的。

使用方法:

这是Helper类:

using System.Diagnostics;
using System.Linq;

public class NetworkMonitor
{
    private PerformanceCounter _bytesSent;
    private PerformanceCounter _bytesReceived;
    private readonly int _processId;
    private bool _initialized;

    public NetworkMonitor(int processID)
    {
        _processId = processID;
        Initialize();
    }

    public NetworkMonitor()
        : this(Process.GetCurrentProcess().Id)
    {

    }
    private void Initialize()
    {
        if (_initialized)
            return;

        var category = new PerformanceCounterCategory(".NET CLR Networking 4.0.0.0");
        var instanceNames = category.GetInstanceNames().Where(i => i.Contains(string.Format("p{0}", _processId)));
        if (!instanceNames.Any()) return;

        _bytesSent = new PerformanceCounter
        {
            CategoryName = ".NET CLR Networking 4.0.0.0",
            CounterName = "Bytes Sent",
            InstanceName = instanceNames.First(),
            ReadOnly = true
        };

        _bytesReceived = new PerformanceCounter
        {
            CategoryName = ".NET CLR Networking 4.0.0.0",
            CounterName = "Bytes Received",
            InstanceName = instanceNames.First(),
            ReadOnly = true
        };

        _initialized = true;
    }

    public float GetSentBytes()
    {
        Initialize(); //in Net4.0 performance counter will get activated after first request
        return _initialized ? _bytesSent.RawValue : 0;
    }
    enter code here
    public float GetReceivedBytes()
    {
        Initialize(); //in Net4.0 performance counter will get activated after first request
        return _initialized ? _bytesReceived.RawValue : 0;
    }
} 

您应该将此部分添加到您的app.config

  <system.net>
    <settings>
      <performanceCounters enabled="true" />
    </settings>
  </system.net>

以下是我用于根据您自己的方法验证准确性的示例:

   private static void Main(string[] args)
        {
            var netMonitor = new NetworkMonitor();

            var received = netMonitor.GetReceivedBytes();
            var sent = netMonitor.GetSentBytes();

            Console.WriteLine("received:{0}, sent:{1}", received, sent);
            func_fetch_Page("http://www.google.com");

            received = netMonitor.GetReceivedBytes();
            sent = netMonitor.GetSentBytes();

            Console.WriteLine("received:{0}, sent:{1}", received, sent);
            Console.ReadKey();
        }