A anti-segmentation C# networking buffer for TCP connections.

I was in the process of writing a client-api for my server when I noticed that C# didn’t really have any ‘Anti-TCP-segmentation’ buffers, so I decided to write one.

/// <summary>
    /// A custom buffer implementation that will be read by the ogserver-framework to prevent
    /// the TCP stack from causing errors due to segmentation. This buffer stores all data
    /// inside of a collection and then parses the length of the collection to the network
    /// in the form of a 32-bit integer. The server will read the integer and then wait to
    /// process any logic until the amount of bytes sent by the buffer has been received by
    /// the server.
    /// </summary>
    public class ByteBuffer
    {
        List<Byte> buffer;

        private int packetId { get; set; }

        public ByteBuffer(int packetId)
        {
            this.packetId = packetId;
            this.buffer = new List<Byte>();
        }

        public void WriteByte(int value)
        {
            buffer.Add((byte)value);
        }

        public void WriteChar(char value)
        {
            buffer.Add((byte)(((uint)value >> 8) & 0xFF));
            buffer.Add((byte)(((uint)value >> 0) & 0xFF));
        }

        public void WriteDouble(double value)
        {
            WriteLong(BitConverter.DoubleToInt64Bits(value));
        }

        public void WriteFloat(float value)
        {
            WriteInt(floatToIntBits(value));
        }

       public void WriteShort(int value)
        { 
            buffer.Add((byte)(((ushort)value >> 8) & 0xFF));
            buffer.Add((byte)(((ushort)value >> 0) & 0xFF));
        }

        public void WriteInt(int value)
        {
            buffer.Add((byte)(((uint)value >> 24) & 0xFF));
            buffer.Add((byte)(((uint)value >> 16) & 0xFF));
            buffer.Add((byte)(((uint)value >> 8)  & 0xFF));
            buffer.Add((byte)(((uint)value >> 0)  & 0xFF));
        }

        public void WriteBoolean(bool value)
        {
            buffer.Add(value ? (byte)1 : (byte)0);
        }

        public void WriteLong(long value)
        {
            buffer.Add((byte)(((uint)value >> 56) & 0xFF));
            buffer.Add((byte)(((uint)value >> 48) & 0xFF));
            buffer.Add((byte)(((uint)value >> 40) & 0xFF));
            buffer.Add((byte)(((uint)value >> 32) & 0xFF));
            buffer.Add((byte)(((uint)value >> 24) & 0xFF));
            buffer.Add((byte)(((uint)value >> 16) & 0xFF));
            buffer.Add((byte)(((uint)value >> 8)  & 0xFF));
            buffer.Add((byte)(((uint)value >> 0)  & 0xFF));
        }

        public void WriteString(string value)
        {
            WriteShort(value.Length);
            foreach(Char c in value.ToCharArray())
            {
                WriteChar(c);
            }
        }

        public void WriteVector2(Vector2 value)
        {
            WriteFloat(value.x);
            WriteFloat(value.y);
        }

        public void WriteVector3(Vector3 value)
        {
            WriteFloat(value.x);
            WriteFloat(value.y);
            WriteFloat(value.z);
        }

        private int floatToIntBits(float value)
        {
            int result = BitConverter.ToInt32(BitConverter.GetBytes(value), 0);
            if (((result & 0x7F800000) == 0x7F800000)
                && (result & 0x80000000) != 0)
                result = 0x7fc00000;
            return result;
        }

        public void Send(BinaryWriter netOut)
        {
            netOut.Write((int)buffer.Count);
            netOut.Write((int)packetId);
            netOut.Write(buffer.ToArray());
        }

    }

To use this properly on the servers side you must first read the 32-bit integer from the stream, this is the “Header” value and signifies the length of the packet, if 32bits(4bytes) are not available from the stream, you simply skip that networking loop and process it again, repeat until 4bytes have been received.

Once 4bytes have been received, read the integer from the steam, this will determine how many bytes are in the “Buffer”.

Next you should keep doing what you did in step one, skip the networking loop until the total amount of bytes available from the network is equal(or greater to) the amount of bytes in the buffer, once you have the proper amount of bytes available in the stream, read and process your networking logic.

This buffer also sends a packet id, make sure to read it after your initial verification code.

An example on the server-side(Java):

// Verify that the packet-header is available.
        if(buffer.readableBytes() < 4) {
            Log.debug(getClass(), "Not enough data read for header, only read: " + buffer.readableBytes() + " / 4");
            return;
        }
                            
        // Set the index to the current position, so we no longer attempt
        // to read the same information again.
        buffer.markReaderIndex();
                            
        // Read the packet-header and store it into an integer.
        int length = buffer.readInt();
        Log.debug(getClass(), "Packet length is: " + length);
    
        // Verify that we have all of the data that was specified in
        // the packet header.
        if(buffer.readableBytes() < length) {
            // If we do not have enough data, reset the current index that
            // We attempts to read from, back to the previous index.
            buffer.resetReaderIndex();
            Log.debug(getClass(), "Read: ("+buffer.readableBytes()+" / " + length + ")");
            return;
        }

        // Do logic here(IE Handle the packet)
        // int packetId = buffer.readInt();

While the buffer.readableBytes() was not included in here(Because it was on the clients side), I’m sure that anybody that can make use of this can figure out how to get the total amount of bytes available on the stream.

Cheers.

That a nice simple implementation. A couple thoughts:

  • ByteBuffer as presented looks more like a Message class… the buffering appears to happen on the receiving end.
  • We have overloading! Consider using Write instead of WriteVector2.
  • If you are going to have a ByteBuffer or Message class that sends data, you should have some way of deserializing it into a similar class.

Here’s a helpful tool - an extended version of the built-in BinaryReader and BinaryWriter classes:

public class MyBinaryReader : BinaryReader
{
    public MyBinaryReader(Stream stream) : base(stream, UTF8Encoding.UTF8) { }
    public ulong ReadVint()
    {
        ulong result = 0;
        byte b;
        do
        {
            b = ReadByte();
            result <<= 7;
            result += (ulong)(b & 127);
        } while ((b & 128) != 0);
        return result;
    }

    public long ReadZigZagVint()
    {
        var input = ReadVint();
        var result = ((long)(input >> 1)) ^ -((long)(input & 1));
        return result;
    }
}

public class MyBinaryWriter : BinaryWriter
{
    public MyBinaryWriter(Stream stream) : base(stream, UTF8Encoding.UTF8) { }
    byte[] buffer = new byte[10];
    public void WriteVint(ulong input)
    {
        int i = 0;
        buffer[0] = (byte)(input & 127);
        while ((input = (input >> 7)) > 0) buffer[++i] = (byte)((input & 127) ^ 128);
        while (i >= 0) Write(buffer[i--]);
    }

    public void WriteZigZagVint(long input)
    {
        ulong output = (ulong)((input << 1) ^ (input >> 63));
        WriteVint(output);
    }
}

I’ll be the first to admit that I don’t have much experience with C# and that I honestly don’t like it very much, but when it’s required it’s required, My Java Client API is much simpler because it can make use of the built-in functionality of the ByteBuf class from Netty. So I’ll go ahead and touch on some of your poitns:

True and false, this was only a very basic implementation of what I’m using, in this case you’re correct, it’s just sending the data to be buffered; However in my version (for a not-yet-released networking API for multiple languages) it’s actually used both ways, obviously this requires some changes to the ByteBuffer class. – All inbound data is stored inside of the ByteBuffer, and it’s removed from the Buffer once it’s read, bytes are stored in the order that they arrive, I’m sure this could cause issues in the future, so I’ve been thinking about a safer implementation. (Grouping the bytes by id) in something such as a HashMap<K,V> (I believe this is a Dictionary in C#?).

I’m sorry, I don’t quite understand what you’re talking about? The WriteVector2 class is mainly just a ‘ease of access’ method I implemented for Unity, you could just as easily call WriteFloat twice.

I probably understand serialization, but don’t understand the definition of it. I’m going on ten years of completely self-taught and haven’t ever really cracked open a book for definitions of things. I’ve been using practices for concurrency(thread-safe applications) for years and I didn’t even find out what concurrency meant until about a year ago. Unity still doesn’t like my methods of dealing with this though, so for now I guess an update Queue will work.

I’ll also be honest and say I have absolutely no idea what those class (extensions) are doing, “ReadZigZagVint”… What? Lost me at Zig. I’ll be the first to say I don’t completely understand bit manipulation, if anything I hardly understand the basics. I can just get by with what I need to do.

Don’t worry you’ll come around, it’s like java but better :stuck_out_tongue_winking_eye:

I found personally that when you get to the stage where you’d like more safety, a proper serialisation and deserialisation library makes life easier - serialising types rather than calling methods on a message/buffer class.

public void WriteFloat(float f) {...};
public void WriteVector2(Vector2 v2) {...};
//Instead do this:
public void Write(float f) {...};
public void Write(Vector2 v2) {...};
//And down the track with Generics
public void Write<T>(T value) {...};

You can’t do it for reading - if you want that to make more sense… then Generics are wonderful.

Google is fun!

BinaryReader and BinaryWriter are classes provided by the .NET framework that handle writing and reading to a Stream… and a Byte Array can be easily considered a Stream (wrap it in a MemoryStream)… so that’ll handle all the work you’re currently doing for free.

On top of that I’ve implemented a variable length integer - in short it uses a basic encoding scheme to write only as many bytes as necessary - kinda like how UTF8 can handle 32 bit unicode, but usually needs only one byte.

A ZigZag integer is similar, but ever second value is negative (IIRC) e.g. 1, -1, 2, -2, 3, -3 - so that we can encode positive and negative numbers efficiently.

Google is the root of all evil :wink:

This is the implementation that’s used on my server, and client, and the one that I’ve been slowly moving over the C# during my free time, I doubt that I’ll ever ‘come around’ because I’ve been a Java junkie since J2SE5 (Somewhere around '04) and I’ve never enjoyed writing in any of the other languages.

I guess it depends on how you look at it, I’m not serializing the Vector class, instead I’m just writing the contents of the class (The x/y/z coordinates) as primitive data types, the same happens on the server when readVector2/3() is called, it reads the primitive data types.

I noticed the use of

[LIST=1]
[*]public void Write(float f) {...};
[*]public void Write(Vector2 v2) {...};

[/LIST]
Instead of

[LIST=1]
[*]public void WriteFloat(float f) {...};
[*]public void WriteVector2(Vector2 v2) {...};

[/LIST]
in the binary reader/writer class-- but it doesn’t seem to have any change in the overall effect. Similar to naming a method:

getBy(Channel channel);

opposed to

getByChannel(Channel channel);

It really just seems like a preference?; However my goal is to make this as simple as possible for the end-users, the little tads of extra typing don’t bother me, I teach a game-development class. It’s much simpler for people to keep up when they see exactly what’s going on, rather than having to think on it.

Largely. It’s kinda like using var in C# - not only does it reduce the length of code… but means you can make small implementation changes without having to change as much code.

Vector3 vector = new Vector3();
buffer.WriteVector3(vector);  
//Note how Vector3 has been written three times?  

var vector = new Vector3();
buffer.Write(vector);  
//Notice how it's only written once?

//Let's say we decided that that third value is kinda redundant:
var vector = new Vector2();  //only one change!
buffer.Write(vector);

Again, this is both cool and up to personal preference - some people like to be a little stricter and explicitly type variables while I prefer the keep intent and like being able to make small implementation changes easily.

1 Like

Makes perfect sense, I’ll just implement both methods, considering this is for a API → http://forum.unity3d.com/threads/a-teaser-of-my-free-game-server-api-unity3d-client-api.283635/