Qos type ReliableFragmented channel, won't fragment our stream

We try to send an image from server to client over a reliable fragmented channel in UNET. We get an error if we exceed the buffer size (max. ~64000 byte). Have we forgotten to define any attributes for fragmented channel or should it fragment by itself?

We are using Unity 2018.4
We’ve tried to make the buffer size “dynamic”, by defining it as the length of the serialized message. And on the Client side we have a buffer size of 64000. We have the impression, that the fragmentation doesn’t happen automatically.

Here are some relevant snippets from our code

Server side (sender)

    NetworkTransport.Init();

        ConnectionConfig cc = new ConnectionConfig();
        reliableChannel = cc.AddChannel(QosType.Reliable); 
        reliableFragmentedChannel = cc.AddChannel(QosType.ReliableFragmented); //here we add the reliable fragmented channel

        HostTopology topo = new HostTopology(cc, MAX_USER);

//This function should send fragmented messages.
public void SendClientFragmented(int recHost, int connectionId, NetMsg msg)
    {
        // This is where we hold our data
        //byte[] buffer = new byte[BYTE_SIZE];

        // This is where you would crush your data into a byte[]
        BinaryFormatter formatter = new BinaryFormatter();
        MemoryStream ms = new MemoryStream();
        formatter.Serialize(ms, msg);

        byte [] serializedStructure = ms.ToArray();
        Debug.Log("Structure Length:" + serializedStructure.Length);
        //if (recHost == 0)
        NetworkTransport.Send(hostId, connectionId, reliableFragmentedChannel, serializedStructure, serializedStructure.Length, out error);

//Our function to send the image (the Arrays are in case of multiple clients logged in). This function is called in the next section
    public void SendImageToClient(byte[] byteTexture)
    {
        int[] recHostIdArray = recHostIdList.ToArray();
        int[] connectionIdArray = connectionIdList.ToArray();
        int[] channelIdArray = channelIdList.ToArray();

        Net_SendImage im = new Net_SendImage();

        im.imageByte = byteTexture;

        SendClientFragmented(recHostIdArray[0], connectionIdArray[0], im);
    }

//Here we transform the image to a byte array
    public void OnClickSendImage()
    {
        Texture2D image = (Texture2D)GameObject.Find("PhotoDisplay").GetComponent<Renderer>().material.mainTexture;

        byte[] jpgBytes = image.EncodeToJPG(10);      //Vielleicht Länge des byte arrays mitsenden.

        Debug.Log("Byte Array created");
        Server.Instance.SendImageToClient(jpgBytes);
    }

Here’s the Client side code for receiving the byte array:

private const int BYTE_SIZE = 64000; 

//Reliable Fragmented channel definition
NetworkTransport.Init();

        ConnectionConfig cc = new ConnectionConfig();
        reliableChannel = cc.AddChannel(QosType.Reliable);
        reliableFragmentedChannel = cc.AddChannel(QosType.ReliableFragmented);

        HostTopology topo = new HostTopology(cc, MAX_USER);

//Receiving messages
NetworkEventType type = NetworkTransport.Receive(out recHostId, out connectionId, out channelId, recBuffer, BYTE_SIZE, out dataSize, out error);
        switch (type)
        {
            case NetworkEventType.Nothing:
                break;

            case NetworkEventType.ConnectEvent:
                Debug.Log("We have connected to the Server");
                break;

            case NetworkEventType.DisconnectEvent:
                Debug.Log("We have been disconnected!");
                break;

            case NetworkEventType.DataEvent:
                BinaryFormatter formatter = new BinaryFormatter();
                MemoryStream ms = new MemoryStream(recBuffer, 0, dataSize);
                ms.Seek(0, SeekOrigin.Begin);
                //ms.Write(recBuffer, 0, recBuffer.Length);
                Debug.Log("Datasize: " + dataSize);
                NetMsg msg = (NetMsg)formatter.Deserialize(ms);

                OnData(connectionId, channelId, recHostId, msg);
                break;

            default:
            case NetworkEventType.BroadcastEvent:
                Debug.Log("Unexpected network event type");
                break;
        }

We expect our image bytearray being fragmented automatically and send over to the client. What should we add or are we having a bad approach?

Thanks for any help!

FMETP STREAM may solve your problem. It includes Live streaming demo for all platforms via UDP/TCP/WebSocket.

All core scripts are written in C# and you can easily modify them for your own networking, for example, large file encoder and decoder, game view encoder and decoder could be useful reference for your UNet.

Asset Store: http://u3d.as/1uHj

Forum: https://forum.unity.com/threads/670270/

Well I there are a few things that come into play here. Networking is a relatively complex topic so I’ll do my best to explain what is going on here.

Let’s start at the basics. The internet and the Internet Protocol (IP) is purely packet based. A single IP packet can only be 64k in total. This is where the MTU comes into play. The MTU is usually a hardware limitation of how large a single packet can be. IP allows the fragmentation of packets into smaller ones but reassembled a single packet can not be larger that 64k. Since the majority nowadays are connected to the internet through a local ethernet we are already bound to a 1500 bytes MTU and that’s why most unreliable protocols limit their size to something around 500 to 1000 bytes. Anything larger could cause fragmentation which increases the likeliness for loosing a packet. Note that an IP packet can be marked as “don’t fragment” which disallows a routing device to fragment the packet. If the routing device MTU is too small to handle such a packet the router will answer with an ICMP packet that the packet is too large and will drop / ignore the packet.

UNet uses UDP as a transport protocol (except for WebGL where of course WebSockets are used). Since UDP builds on top of the IP protocol and doesn’t add anything to packet management we are bound to the same restrictions for single packets. The only thing UDP adds are “ports” to distinguish different applications. That’s why UDP is often used as a basis for custom application protocols as it adds just a little bit overhead.

I didn’t look into how UNet messages are actually send but since it’s also a message / packet based protocol we most likely are bound to the same restrictions that the IP protocol has. Of course one could build another fragmentation / reassemble layer on top of UDP but this usually isn’t required and would just add much more overhead and complexity.

The fragment size that you can set in UNet is also limited to 64k (since a 16 bit value is used). Keep in mind that UNet is a message based protocol for games and not a stream protocol like TCP. TCP is a streaming protocol that is based on “virtual connections”. So once a connection has been established both ends can simply send arbitrary data as an endless stream to the other side. The data in streaming protocols might arrive in arbitrary fragments but is guaranteed to be in the right order. So if you send out 5 gig of memory over time you will receive those 5 gig of memory over time. There are not packets or messages in a TCP stream unless you specifically create an application layer protocol and interpret the stream that way. This is also how HTTP works.

Pretty much all transport layer protocols have a relatively small packet size. If you want to send large data over a packet based protocol you have to actually chop up your data into actual messages which fit into a single packet and reassemble the data yourself on the other end.

Finally I want to point out that using the BinaryFormatter for anything network based is a complete overkill. The remoting protocol that is used to serialize C# objects has a hell of overhead.

So if you want to send large chunks of data that span multiple packets you have to split it yourself. Since you often need to send multiple such large chunks you need some sort of identifier anyways.