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.