Click here to Skip to main content
15,891,375 members
Articles / Web Development / ASP.NET
Article

Bandwidth throttling

Rate me:
Please Sign up or sign in to vote.
4.75/5 (59 votes)
2 Apr 2007CPOL2 min read 274.7K   8.2K   139   61
Save bandwidth and get QoS with bandwidth throttling.

Screenshot - screen.jpg

Introduction

Hosting a website can be fun. But, when you offer big downloads or streaming media, you do not want a visitor to eat up the full bandwidth. Or, maybe, you want to offer a premium account from where users can download without limitation, and a free account where users cannot download faster than 50 kb/s. Here is where throttling comes in.

Bandwidth throttling helps provide quality of service (QoS) by limiting network congestion and server crashes. For example, you make sure a connection does not get more than an X number of bytes per second.

The purpose of this article is to show how to use bandwidth throttling, with a small helper class.

Using the code

Within the source code, you will find a class named ThrottledStream. This class is derived from the abstract Stream that can be found in the System.IO namespace. In the constructor, it accepts a base stream to throttle. Here is a small line of code that shows how to instantiate a ThrottledStream:

C#
Stream throttledStream = new ThrottledStream(Response.OutputStream, 1024);

Now, everything you do with the throttledStream will be limited to 1024 bytes per second, and will be send to or read from the OutputStream that is a member of the Response property that exists within an ASP.NET page.

Because the ThrottledStream is derived from the abstract Stream class, it is easy to add throttling to an existing application or website. For example, when you have a process that sends file content over a Stream and you want to enable bandwidth throttling, you only have to change the initialization of the destination stream.

The old code can look like this:

C#
Stream sourceStream;
Stream destinationStream;

try
{
    sourceStream = new FileStream(@"c:\myfile.bin", 
                       FileMode.Open, FileAccess.Read, FileShare.Read);
    destinationStream = new NetworkStream(mySocket, false);

    byte[] buffer = new byte[1024];
    int readCount = sourceStream.Read(buffer, 0, BufferSize);

    while (readCount > 0)
    {
        destinationStream.Write(buffer, 0, readCount);
        readCount = sourceStream.Read(buffer, 0, BufferSize);
    }
}
finally
{
    if (destinationStream != null)
    {
        destinationStream.Close();
    }
 
    if (sourceStream != null)
    {
        sourceStream.Close();
    }
}

Now, we can easily add throttling support to this process. We only need to change the initialization:

C#
...

Stream originalDestinationStream = new NetworkStream(mySocket, false);
destinationStream = new ThrottledStream(originalDestinationStream, 51200);

...

By adding only one line of code, this full process is throttled to 50 kb/s (51200 b/s). Now, we go even a step further and add throttling that is based on a membership:

C#
...

long bps;

switch( user.Membership )
{
    case MembershipLevel.Bronze:
        bps = 51200;
        break;

    case MembershipLevel.Silver:
        bps = 102400;
        break;

    case MembershipLevel.Gold:
        bps = 153600;
        break;

    case MembershipLevel.Platina:
        bps = ThrottledStream.Infinite;
        break;
}

Stream originalDestinationStream = new NetworkStream(mySocket, false);
destinationStream = new ThrottledStream(originalDestinationStream, bps);

...

Here, we have a situation where a Bronze membership will give you 50 kb/s, Silver 100 kb/s, Gold 150 kb/s, and Platina infinitive - no throttling.

Points of interest

Bandwidth throttling can improve the QoS of your server, and allows you to control the bandwidth for a specified connection. The helper class named ThrottledStream is very easy to use, and can be used in existing scenarios.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Sogyo
Netherlands Netherlands
Pieter Joost is a sr. IT consultant at Sogyo. He is recently awarded as Microsoft Visual C# MVP. He's active in the software development community as a speaker and boardmember of devnology.nl and dotned.nl

Comments and Discussions

 
QuestionWhy inherit from Stream? Pin
copa01730-Nov-18 2:57
copa01730-Nov-18 2:57 
QuestionWorking but sometimes its showing more speed Pin
Member 116092747-Sep-17 20:43
Member 116092747-Sep-17 20:43 
GeneralRe: Working but sometimes its showing more speed Pin
Muhammad Owais4-Oct-21 21:37
Muhammad Owais4-Oct-21 21:37 
QuestionHow to configure it using VS2015 Windows 10. Pin
Member 1291430020-Dec-16 5:29
Member 1291430020-Dec-16 5:29 
QuestionUnderstanding the bandwith usage Pin
gemese23-Apr-16 23:45
gemese23-Apr-16 23:45 
QuestionUpdated Sourcecode Pin
sx200823-Jul-15 1:20
sx200823-Jul-15 1:20 
Hi, i've made the following changes:
* use StopWatch instead of Environment.TickCount (because Environment.TickCount failed sometimes on my machine)
* added property BlockSize (default value is 512)
Packets larger than BlockSize are splitted to make the throttling much smoother
* override ReadByte() and WriteByte()
C#
/// <summary>
/// Class for streaming data with throttling support.
/// </summary>
/// <remarks>http://www.codeproject.com/Articles/18243/Bandwidth-throttling</remarks>
public class ThrottledStream : Stream
{
    /// <summary>
    /// A constant used to specify an infinite number of bytes that can be transferred per second.
    /// </summary>
    public const long Infinite = 0;

    /// <summary>
    /// The base stream.
    /// </summary>
    private Stream _baseStream;

    /// <summary>
    /// The maximum bytes per second that can be transferred through the base stream.
    /// </summary>
    private long _maximumBytesPerSecond;

    /// <summary>
    /// The number of bytes that has been transferred since the last throttle.
    /// </summary>
    private long _byteCount;

    protected Stopwatch _stopWatch;

    /// <summary>
    /// Gets or sets the maximum bytes per second that can be transferred through the base stream.
    /// </summary>
    /// <value>The maximum bytes per second.</value>
    /// <exception cref="T:System.ArgumentOutOfRangeException">Value is negative. </exception>
    public long MaximumBytesPerSecond
    {
        get
        {
            return _maximumBytesPerSecond;
        }
        set
        {
            if (value < 0)
            {
                throw new ArgumentOutOfRangeException("MaximumBytesPerSecond",
                    value, "The maximum number of bytes per second can't be negative.");
            }
            if (_maximumBytesPerSecond != value)
            {
                _maximumBytesPerSecond = value;
                Reset();
            }
        }
    }

    public int BlockSize { get; set; }

    /// <summary>
    /// Gets a value indicating whether the current stream supports reading.
    /// </summary>
    /// <returns>true if the stream supports reading; otherwise, false.</returns>
    public override bool CanRead
    {
        get
        {
            return _baseStream.CanRead;
        }
    }

    /// <summary>
    /// Gets a value indicating whether the current stream supports seeking.
    /// </summary>
    /// <value></value>
    /// <returns>true if the stream supports seeking; otherwise, false.</returns>
    public override bool CanSeek
    {
        get
        {
            return _baseStream.CanSeek;
        }
    }

    /// <summary>
    /// Gets a value indicating whether the current stream supports writing.
    /// </summary>
    /// <value></value>
    /// <returns>true if the stream supports writing; otherwise, false.</returns>
    public override bool CanWrite
    {
        get
        {
            return _baseStream.CanWrite;
        }
    }

    /// <summary>
    /// Gets the length in bytes of the stream.
    /// </summary>
    /// <value></value>
    /// <returns>A long value representing the length of the stream in bytes.</returns>
    /// <exception cref="T:System.NotSupportedException">The base stream does not support seeking. </exception>
    /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception>
    public override long Length
    {
        get
        {
            return _baseStream.Length;
        }
    }

    /// <summary>
    /// Gets or sets the position within the current stream.
    /// </summary>
    /// <value></value>
    /// <returns>The current position within the stream.</returns>
    /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
    /// <exception cref="T:System.NotSupportedException">The base stream does not support seeking. </exception>
    /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception>
    public override long Position
    {
        get
        {
            return _baseStream.Position;
        }
        set
        {
            _baseStream.Position = value;
        }
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="T:ThrottledStream"/> class with an
    /// infinite amount of bytes that can be processed.
    /// </summary>
    /// <param name="baseStream">The base stream.</param>
    public ThrottledStream(Stream baseStream)
        : this(baseStream, ThrottledStream.Infinite)
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="T:ThrottledStream"/> class.
    /// </summary>
    /// <param name="baseStream">The base stream.</param>
    /// <param name="maximumBytesPerSecond">The maximum bytes per second that can be transferred through the base stream.</param>
    /// <exception cref="ArgumentNullException">Thrown when <see cref="baseStream"/> is a null reference.</exception>
    /// <exception cref="ArgumentOutOfRangeException">Thrown when <see cref="maximumBytesPerSecond"/> is a negative value.</exception>
    public ThrottledStream(Stream baseStream, long maximumBytesPerSecond)
    {
        if (baseStream == null)
        {
            throw new ArgumentNullException("baseStream");
        }

        _baseStream = baseStream;
        _stopWatch = Stopwatch.StartNew();
        MaximumBytesPerSecond = maximumBytesPerSecond;
        BlockSize = 512;
        _byteCount = 0;
    }

    /// <summary>
    /// Clears all buffers for this stream and causes any buffered data to be written to the underlying device.
    /// </summary>
    /// <exception cref="T:System.IO.IOException">An I/O error occurs.</exception>
    public override void Flush()
    {
        _baseStream.Flush();
    }

    /// <summary>
    /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read.
    /// </summary>
    /// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between offset and (offset + count - 1) replaced by the bytes read from the current source.</param>
    /// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the current stream.</param>
    /// <param name="count">The maximum number of bytes to be read from the current stream.</param>
    /// <returns>
    /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached.
    /// </returns>
    /// <exception cref="T:System.ArgumentException">The sum of offset and count is larger than the buffer length. </exception>
    /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception>
    /// <exception cref="T:System.NotSupportedException">The base stream does not support reading. </exception>
    /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception>
    /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
    /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception>
    public override int Read(byte[] buffer, int offset, int count)
    {
        int total = 0;
        while (count > BlockSize)
        {
            Throttle(BlockSize);
            int rb = _baseStream.Read(buffer, offset, BlockSize);
            total += rb;
            if (rb < BlockSize)
                return total;
            offset += BlockSize;
            count -= BlockSize;
        }
        Throttle(count);
        return total + _baseStream.Read(buffer, offset, count);
    }

    /// <summary>
    /// Sets the position within the current stream.
    /// </summary>
    /// <param name="offset">A byte offset relative to the origin parameter.</param>
    /// <param name="origin">A value of type <see cref="T:System.IO.SeekOrigin"></see> indicating the reference point used to obtain the new position.</param>
    /// <returns>
    /// The new position within the current stream.
    /// </returns>
    /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
    /// <exception cref="T:System.NotSupportedException">The base stream does not support seeking, such as if the stream is constructed from a pipe or console output. </exception>
    /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception>
    public override long Seek(long offset, SeekOrigin origin)
    {
        return _baseStream.Seek(offset, origin);
    }

    /// <summary>
    /// Sets the length of the current stream.
    /// </summary>
    /// <param name="value">The desired length of the current stream in bytes.</param>
    /// <exception cref="T:System.NotSupportedException">The base stream does not support both writing and seeking, such as if the stream is constructed from a pipe or console output. </exception>
    /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
    /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception>
    public override void SetLength(long value)
    {
        _baseStream.SetLength(value);
    }

    /// <summary>
    /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written.
    /// </summary>
    /// <param name="buffer">An array of bytes. This method copies count bytes from buffer to the current stream.</param>
    /// <param name="offset">The zero-based byte offset in buffer at which to begin copying bytes to the current stream.</param>
    /// <param name="count">The number of bytes to be written to the current stream.</param>
    /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
    /// <exception cref="T:System.NotSupportedException">The base stream does not support writing. </exception>
    /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception>
    /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception>
    /// <exception cref="T:System.ArgumentException">The sum of offset and count is greater than the buffer length. </exception>
    /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception>
    public override void Write(byte[] buffer, int offset, int count)
    {
        while (count > BlockSize)
        {
            Throttle(BlockSize);
            _baseStream.Write(buffer, offset, BlockSize);
            offset += BlockSize;
            count -= BlockSize;
        }
        Throttle(count);
        _baseStream.Write(buffer, offset, count);
    }

    public override int ReadByte()
    {
        Throttle(1);
        return _baseStream.ReadByte();
    }

    public override void WriteByte(byte value)
    {
        Throttle(1);
        _baseStream.WriteByte(value);
    }

    /// <summary>
    /// Returns a <see cref="T:System.String"></see> that represents the current <see cref="T:System.Object"></see>.
    /// </summary>
    /// <returns>
    /// A <see cref="T:System.String"></see> that represents the current <see cref="T:System.Object"></see>.
    /// </returns>
    public override string ToString()
    {
        return _baseStream.ToString();
    }

    /// <summary>
    /// Throttles for the specified buffer size in bytes.
    /// </summary>
    /// <param name="bufferSizeInBytes">The buffer size in bytes.</param>
    protected void Throttle(int bufferSizeInBytes)
    {
        // Make sure the buffer isn't empty.
        if (_maximumBytesPerSecond <= 0 || bufferSizeInBytes <= 0)
        {
            return;
        }

        _byteCount += bufferSizeInBytes;
        if (_byteCount < 16)
            return;

        long elapsedMilliseconds = _stopWatch.ElapsedMilliseconds;

        if (elapsedMilliseconds > 0)
        {
            // Calculate the current bps.
            long bps = _byteCount * 1000L / elapsedMilliseconds;

            // If the bps are more then the maximum bps, try to throttle.
            if (bps > _maximumBytesPerSecond)
            {
                // Calculate the time to sleep.
                long wakeElapsed = _byteCount * 1000L / _maximumBytesPerSecond;
                int toSleep = (int)(wakeElapsed - elapsedMilliseconds);

                if (toSleep > 1)
                {
                    try
                    {
                        // The time to sleep is more then a millisecond, so sleep.
                        Thread.Sleep(toSleep);
                    }
                    catch (ThreadAbortException)
                    {
                        // Eatup ThreadAbortException.
                    }

                    // A sleep has been done, reset.
                    Reset();
                }
            }
        }
    }

    /// <summary>
    /// Will reset the bytecount to 0 and reset the start time to the current time.
    /// </summary>
    protected void Reset()
    {
        long difference = _stopWatch.ElapsedMilliseconds;

        // Only reset counters when a known history is available of more then 1 second.
        if (difference > 1000)
        {
            _byteCount = 0;
            _stopWatch.Restart();
        }
    }

}

AnswerRe: Updated Sourcecode Pin
Meixger Martin22-Jan-16 0:16
Meixger Martin22-Jan-16 0:16 
AnswerRe: Updated Sourcecode Pin
jerry_wangjh4-Feb-18 21:21
jerry_wangjh4-Feb-18 21:21 
QuestionBandwidth throttling in Task-based async methods? Pin
Alex Zelid2-Apr-14 2:54
Alex Zelid2-Apr-14 2:54 
QuestionMy vote of 5 Pin
AndyInPembs12-Dec-12 0:00
AndyInPembs12-Dec-12 0:00 
Questioncreate socket Pin
Arash Zeinoddini21-Aug-12 11:44
Arash Zeinoddini21-Aug-12 11:44 
Questionupload throttled Pin
nandobv10-Mar-12 15:10
nandobv10-Mar-12 15:10 
Generalhow to use ThrottledStream class Pin
as206em19-May-11 5:28
as206em19-May-11 5:28 
QuestionHow throttle each connection TCP/UDP? Pin
egcf21-Nov-10 11:15
egcf21-Nov-10 11:15 
GeneralProblems Pin
NN---21-Sep-10 2:15
NN---21-Sep-10 2:15 
GeneralBandwidth throttling Pin
commenter210-Mar-10 11:07
commenter210-Mar-10 11:07 
QuestionWhat with asp.net worker threadpool ? Pin
neoandrew7-Mar-10 14:10
neoandrew7-Mar-10 14:10 
GeneralThanks ! Pin
Ahmed Charfeddine1-Jun-09 1:17
Ahmed Charfeddine1-Jun-09 1:17 
GeneralIt works! Pin
dimpant8-May-09 14:41
dimpant8-May-09 14:41 
GeneralRe: It works! Pin
dimpant15-May-09 23:09
dimpant15-May-09 23:09 
GeneralRe: It works! Pin
Goran___20-Aug-09 2:54
Goran___20-Aug-09 2:54 
QuestionCan I adjust the bit rate on the fly? Pin
ebonnett29-Apr-09 6:22
ebonnett29-Apr-09 6:22 
AnswerRe: Can I adjust the bit rate on the fly? Pin
st0icsmitty15-Dec-09 10:25
st0icsmitty15-Dec-09 10:25 
GeneralRe: Can I adjust the bit rate on the fly? Yes... [modified] Pin
thomastmc5-Feb-10 4:04
thomastmc5-Feb-10 4:04 
GeneralIdle for a while after creating ThrottledStream constructor Pin
SchoonMan14-Oct-08 8:07
SchoonMan14-Oct-08 8:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.