Search Unity

How exactly does the transport network send/recieve work for LLAPI ?

Discussion in 'Multiplayer' started by TheCelt, Jul 26, 2017.

  1. TheCelt

    TheCelt

    Joined:
    Feb 27, 2013
    Posts:
    742
    So i am confused how this stuff works.

    Say i am sending from multiple clients their position to the server. What happens if the number of messages sent per x amount of time is faster than the receive rate on the server end? Do the messages not get recieved? Or does it add them to a queue for when the receive method is called?
     
  2. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    All received messages get put into a queue and if you dont call NetworkTransport.Receive fast enough, the queue will get full and other messages will get dropped.
    To see how many received messages are waiting in the queue, call NetworkTransport.GetIncomingMessageQueueSize
    To increase the queue size (default is 1024), you can set GlobalConfig like this...
    Code (CSharp):
    1. GlobalConfig config = new GlobalConfig();
    2. config.ReactorMaximumReceivedMessages = 2000;
    3. NetworkTransport.Init(config);
    Keep in mind that messages != packets. A single packet can contain multiple messages.
     
    aabramychev likes this.
  3. TheCelt

    TheCelt

    Joined:
    Feb 27, 2013
    Posts:
    742
    I would presume in LLAPI i don't actually deal with handling of packets thats done for me?

    Is 1024 generally considered enough for FPS ? I don't really know what to expect. Also if i run out of queue space can that result in "partial" messages, or will it just disallow a new message that exceeds the available space?
     
  4. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    The llapi writes the packets for you.

    1024 should be fine, but that depends on how many players as well as how you are setting up your messages.
    For example, every call to NetworkTransport.Send or NetworkTransport.QueueMessageForSending is writing a new message. However, you can reduce the amount of messages by packing your own message system into a single byte array and then only make one call to NetworkTransport.Send. This way, that message only counts as a single message to unet, but really it holds many messages. This is actually how unets HLAPI handles things.
    Lets say you want to send these messages...
    Msg 1 - player moved
    Msg 2 - player typed something in chat
    Msg 3 - player died
    Instead of calling NetworkTransport.Send 3 times, you can write your own message system such that you give the player move message an ID, player typed in chat message an ID, and player died message an ID, as well as a length of expected bytes if the length isnt constant, and pack those into a single byte array and call NetworkTransport.Send only 1 time.
    You can have a look at the HLAPI source code here to see how they handle it https://bitbucket.org/Unity-Technol...1dbc3d08eaadfc07c24dcf75866e7a1bfa/?at=2017.1

    If you run out of queue space then I am pretty sure it will just disallow new messages.
     
  5. TheCelt

    TheCelt

    Joined:
    Feb 27, 2013
    Posts:
    742
    Thanks for the info, seems this info is hard to come by - no idea where to learn the LLAPI in detail other than looking at their code which is not easy, or a good way to learn how it all works. I still have some questions.

    You mention sending many messages in one send, for example lets say i send position + rotation in one message. Thats no problem. But what is more optimised for a network, lots of send messages of which is small, or one message of which also will probably have extra bytes since you need some way to separate the two messages.

    Or is lots of small messages going to cause other unforeseen problems?
     
  6. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    So you want to know if its better to call NetworkTransport.Send 2 times, one for position and one for rotation, or to combine position and rotation into your own message? It will most likely be much better to combine them into your own single message.
    The reason for this is that based on what I have seen by sniffing the packets unity sends, each call to NetworkTransport.Send will add 1 byte for the channelID, 1 or more bytes for the length of the message depending on its length, and then the message itself. This means every call to NetworkTransport.Send will add at least 2 bytes.
    If you handled combining messages yourself, then you can do things in a smarter way.

    For example, lets say you create a TransformMessage that contains position and rotation. You will need 1-2 bytes for the unique id of this message, and then you can be smart and know that the position will always be lets say 8 bytes and the rotation will also be 8 bytes (im just giving random numbers).
    So assuming the unqiue id is 2 bytes, the message will be
    - ChannelID (1 byte) + Message length (1 byte since its below 240 bytes) + UniqueID (2 bytes) + Position (8 bytes) + Rotation (8 bytes) = 20 bytes.

    If you were to send a Position message and Rotation message separately, how would you be able to do that? You would basically need a unique ID for the position message and a unique ID for the Rotation message, and then the 8 bytes per message, and then since you made 2 calls to NetworkTransport.Send, its going to add a channelID 2 times and a length 2 times.
    So it ends up being
    - ChannelID (1 byte) + Message length (1 byte since its below 240 bytes) + UniqueID (2 bytes) + Position (8 bytes) + ChannelID (1 byte) + Message length (1 byte since its below 240 bytes) + UniqueID (2 bytes) + Rotation (8 bytes) = 24 bytes.

    Now in this case, its only 4 bytes more, however, imagine you have a bunch of variables you want to sync. It would be best to group them all under a single message with a single UniqueID so that you avoid adding a 2 byte overhead per variable.

    You can look at unitys NetworkWriter.WritePackedUInt32 and NetworkReader.ReadPackedUInt32 method to see how they can save on bytes depending on how large the number is. However, if the number is large, it will actually cost an extra byte, so only use this if the number will usually be low.

    If you arent sure about what I mean about UniqueIDs for messages, I can probably go into a little more detail.
     
  7. TheCelt

    TheCelt

    Joined:
    Feb 27, 2013
    Posts:
    742
    Great info, this is really helpfu!

    I presume unique ID is the connection ID to represent which player is sending the message?
     
  8. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    No, the unique id is something you create to represent a message.
    You can look at unitys HLAPI source code to get an idea
    Messages
    NetworkMessageHandlers
    NetworkMessageDelegate
    NetworkConnection.HandleReader
    Examples of usage in
    ClientScene.RegisterSystemHandlers / OnObjectSpawn

    When you call NetworkTransport.Receive and you receive bytes of data, how do you know what to do with that data? What do these bytes represent? Thats where the unique ID comes in.
    I can give every single message some kind of ID that never changes, so for example I can have my TransformMessage have an ID of 12. I can then have a ChatMessage with an ID of 22.
    When I call NetworkTransport.Receive and I start to read the bytes received, I first read the first 2 bytes (or however many bytes all unique IDs will be) to grab the message ID. I see that the message ID was 12, so I call a Dictionary<UInt16, NetworkMessageDelegate> that holds all my messages and the delegate/method that knows how to read the bytes and do something with it.

    The links above should help explain through code.
    The ClientScene.RegisterSystemHandlers shows examples of registering these messages.
    The NetworkConnection.HandleReader shows how the byte array gets read by first reading the message ID and then grabbing the delegate from the dictionary.
    The ClientScene.OnObjectSpawn(NetworkMessage netMsg) shows the method that will be called by the NetworkConnection.HandleReader if it read its message ID that was registered in ClientScene.RegisterSystemHandlers. Notice that in the OnObjectSpawn method it calls "netMsg.ReadMessage(s_ObjectSpawnMessage);". The s_ObjectSpawnMessage is a variable of type MessageBase (more specifically of type ObjectSpawnMessage) that was declared at the top of the page of the ClientScene class. You can find the ObjectSpawnMessage class in the Messages link above. Notice that it has variables as well as a Serialize / Deserialize method that knows how to handle reading / writing those variables into bytes.
     
  9. TheCelt

    TheCelt

    Joined:
    Feb 27, 2013
    Posts:
    742
    Oh in which case i already do that with enums. I forgot about that :p
     
    HiddenMonk likes this.