Как рассчитать HttpWebRequest потраченный исходящий и входящий интернет-трафик

У меня есть функция ниже, чтобы получить страницу. У меня вопрос, я хочу посчитать, сколько потрачено интернет-соединение

Как входящий (загрузка), так и исходящий трафик (отправлено)

Как я могу это сделать? Спасибо

Моя функция

 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

@ Симон Мурье, как рассчитать потраченный трафик

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 ответов

Решение

Вы можете создать прокси, используя 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://stackru.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 здесь

Один Windows API может предоставить вам эту информацию: GetPerTcpConnectionEStats для соединений IPV4 и связанной функции IPV6 GetPerTcp6ConnectionEStats. Обратите внимание, что вам нужно использовать SetPerTcpConnectionEStats, прежде чем вы сможете получить какую-либо статистику, а для этого обычно нужны права администратора...

Чтобы получить список всех соединений, вы можете использовать функцию GetExtendedTcpTable. Он также может дать вам идентификатор процесса соединения, что очень полезно.

Это нативные API, которые не так просты в использовании, но я создал класс TcpConnection, который оборачивает все это. Он доступен здесь в небольшом приложении WPF под названием IPStats: 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 с этой локальной конечной точкой. К сожалению, нет простого способа сделать это. Вот связанный ответ об этом: Как я могу получить номер локального порта 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);
        }
    }
}

Трудно точно знать , сколько сетевого трафика происходит из-за непредсказуемого трафика Ethernet и подобных вещей, но по сути суть любого 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, затем передайте это как новый поток ответов, одновременно получая длину в пути.

Тем не менее, вы испытываете дополнительное беспокойство по поводу сжатия контента. Я вижу, что вы используете это на самом деле. Учитывая простоту, я собираюсь взять 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, позволят вам это сделать. По общему признанию я далеко не осведомлен о них - я даже никогда не открывал их - но я полагаю, что доступны профилировщики сетевого трафика. Конечно, я сомневаюсь, что они работают на каждой платформе, как это было бы. Но это, безусловно, будет проще.

Кроме того, если кто-то заметит какие-либо опечатки здесь, я пару раз скопировал и вставил, и думаю, что получил их все, но не стесняйтесь, дайте мне знать или просто исправьте их.

Это немного уродливо, но вы можете использовать трассировку сети, а затем проанализировать журнал. см. 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 и нажмите кнопку ОК.

Если ваше клиентское приложение обменивается данными с вашими известными серверами IIS, я бы попытался получить эти данные из журналов IIS. Проверьте cs-байты (байты от клиента к серверу, также известные как байты запроса) и sc-байты (байты от сервера к клиенту, также известные как ответ). http://technet.microsoft.com/en-us/library/cc754702(v=ws.10).aspx

Я думаю, что вы можете использовать .NET CLR Network счетчик производительности, так как он может дать вам отправленные и полученные байты на 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();
        }
Другие вопросы по тегам