- Home /
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!
Answer by thelghome · Nov 04, 2019 at 11:47 AM
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
Answer by Bunny83 · Oct 30, 2019 at 03:18 PM
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.
Thanks allot for the answer and for the overview in Networking @Bunny83 .
In that case a new question arises, which is the following:
Would you then recommend to open a separate TCP port on both sides to send the larger image byte arrays or Would you fragment the byte arrays in 64k chunks and sending them using UNET?
What do you think is the better approach?
Well, yes that probably would work. However I don't know your actual usecase. One point of UNet was to provide relay servers and matchmaking servers to avoid issues due to NET routers and the issue that the server need to port forward it's own ports in order for others to connect. Though since Unity actually deprecated UNet and their services those advantages are probably irrelevant here.
So it's all a matter of the usecase. If you can guarantee the server reachability you can have the server open a TCP port and have the clients connect to it in parallel. Though if this is only needed at loading time it usually makes more sense to stick to one protocol and just build the data transmission into your protocol as a "feature". Pretty much like for example $$anonymous$$ecraft is sending all the terrain data over the same UDP port as the usual game update data. This is how pretty much all games handle things like that. Even old games like quake allowed the sending of server assets (map, textures, audio, ...) when connecting to a server. Almost all realtime games also use UDP as for the most part only unreliable data is needed. Since UNet already provides a reliable layer ontop of UDP you don't have to worry about missing packets.
Your answer
Follow this Question
Related Questions
Unity networking tutorial? 6 Answers
[UNET] Only spawn certain server objects on local client? 0 Answers
Network how to send data/activate function 1 Answer
Can I create a separate server and use the Unity game objects in it? 0 Answers
What's the best approach for creating a secure authorative server? 0 Answers