Count bytes transmitted by TcpClient via NetworkStream BinaryReader/BinaryWriter
Asked Answered
K

2

7

I am using a networking protocol built around TcpClient, using BinaryReader to read bytes from the underlying NetworkStream (and, conversely, using BinaryWriter to write).

The protocol transmits strings in UTF-8 encoding, and is calling reader.ReadString() to read them from the stream (using writer.Write(someStr) to write).

Is there an easy way to determine the number of bytes read from (or written to) the NetworkStream, without having to jump through hoops to calculate the actual byte lengths of the strings transmitted?

Note that BinaryWriter.Write() writes a 7-bit-encoded integer before the actual bytes of the string, which makes any manual calculation additionally complex.

Also note that NetworkStream does not support the Position property, since it complains about not being able to Seek.

Furthermore, I would like to avoid introducing intermediaries that have to copy/scan data into the process of reading/writing as not to affect the performance of the whole system.

Is there an easy, high-level way of counting bytes passing through the network interface without having to manually account for the encoding and lengths of the strings?

Kiblah answered 14/12, 2015 at 18:19 Comment(2)
What do you want to achieve doing that? You could insert a custom stream between the networkstream and the reader that counts bytes.Alberich
@Alberich I just need to know the total bytes read/written - for reporting purposes. Something in the underlying protocol stack must know the count of bytes sent/received... I'd like to get at that information - with minimal hassle.Kiblah
A
1

You could insert a custom stream between the networkstream and the reader that counts bytes.

It is not necessary to copy data to do that. Just add the number of bytes that passes by to a counter.

Alberich answered 14/12, 2015 at 18:34 Comment(5)
Would you be able to sketch out an example of how to do that?Kiblah
Any concrete problems? Derive from Stream and wrap another stream instance that way.Alberich
Wouldn't that have to involve buffering the data? I don't want to slow things down.Kiblah
I already said that there would be no buffering. If you don't think that's true provide a reason why there would be buffering. Maybe you should get started with the stream implementation so that you see how it looks like. In the Read method, for example, you pass the incoming buffer through to the inner stream.Alberich
And I said I wanted an easy way. While creating the custom stream wasn't terrible - you were right, it was easier than I thought :-) - ensuring that it is used in all places where NetworkStream was being used previously proved a major hassle. Not the easy solution I was looking for, but it works, so I'll accept your answer. Thanks.Kiblah
K
2

For those who are curious about how I implemented the byte-counting stream, here it is in all of its glory (or infamy, as the case may be):

using System;
using System.IO;

namespace Streams
{
    /// <summary>
    /// A wrapper around a <see cref="Stream"/> that keeps track of the number of bytes read and written.
    /// </summary>
    public class ByteCountingStream : Stream
    {
        private readonly Stream inner;

        private long totalBytesWritten;
        private long totalBytesRead;


        public ByteCountingStream(Stream inner)
        {
            this.inner = inner;
        }

        public override void Flush()
        {
            inner.Flush();
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            throw new NotImplementedException();
        }

        public override void SetLength(long value)
        {
            throw new NotImplementedException();
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            int readBytes = inner.Read(buffer, offset, count);
            totalBytesRead += readBytes;
            return readBytes;
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            inner.Write(buffer, offset, count);
            totalBytesWritten += count;
        }

        public override bool CanRead => true;
        public override bool CanSeek => false;
        public override bool CanWrite => true;

        public override long Length
        {
            get
            {
                throw new NotImplementedException();
            }
        }

        public override long Position { get; set; }

        public long TotalBytesWritten => totalBytesWritten;
        public long TotalBytesRead => totalBytesRead;
    }
}

The implementation passes the buffer to the underlying stream so, indeed, there is no data copying involved.

Kiblah answered 14/12, 2015 at 20:21 Comment(2)
Length looks broken. Does this not always return 0? Should throw.Alberich
Position, too. I almost overlooked it with the new terse 6.0 syntax. Need to get used to that. Also, I think without disposal that class is quite dangerous.Alberich
A
1

You could insert a custom stream between the networkstream and the reader that counts bytes.

It is not necessary to copy data to do that. Just add the number of bytes that passes by to a counter.

Alberich answered 14/12, 2015 at 18:34 Comment(5)
Would you be able to sketch out an example of how to do that?Kiblah
Any concrete problems? Derive from Stream and wrap another stream instance that way.Alberich
Wouldn't that have to involve buffering the data? I don't want to slow things down.Kiblah
I already said that there would be no buffering. If you don't think that's true provide a reason why there would be buffering. Maybe you should get started with the stream implementation so that you see how it looks like. In the Read method, for example, you pass the incoming buffer through to the inner stream.Alberich
And I said I wanted an easy way. While creating the custom stream wasn't terrible - you were right, it was easier than I thought :-) - ensuring that it is used in all places where NetworkStream was being used previously proved a major hassle. Not the easy solution I was looking for, but it works, so I'll accept your answer. Thanks.Kiblah

© 2022 - 2024 — McMap. All rights reserved.