Search Unity

LLAPI Issues

Discussion in 'Multiplayer' started by Cirmik, Aug 3, 2015.

  1. Cirmik

    Cirmik

    Joined:
    Jan 8, 2014
    Posts:
    6
    Hi
    I started experimenting with the UNET LLAPI yesterday and I can't seem to get it work right. The documentation seems very limited also.

    I created a simple client and server system. Server accepts multiple client connections and client just tries to connect to an ip of the server. Server keeps active connections in a list. If connection is made, it adds it to the list, if connection disconnects, it is removed from the list. When the connection is not in the list, no messages are sent by my code.
    Clients then start sending their input data every fixedUpdate (I used 0.015 timesetep and data size was 27 bytes). Server simulates game world based on the input data and sends it back to the clients on every of its own fixedupdate. Clients apply the simulation data whenever one is received from server.
    Then all kind of things started going wrong. If there is 2 clients connected and 1 of them disconnects (without sending disconnect message before disconnecting), then about 50% times the server either crashes or spams some meaningless error like ">= 0" or "> 0", I think it comes from a code line that does NetworkTransport.Send, but there is no data about where the error message came from and I can't reliably reproduce it either. Seems to be completely random. Second option is that Unity editor just crashes. I haven't tried with sending a disconnect message, but that can't be a requirement, every client could just crash the server then by not sending it if that would be the case.

    I set gConf.ReactorMaximumSentMessages to 2048 and lowered the fixedupdate timestep from 0.015 to 0.0333, now it doesn't seem to crash. Haven't been able to reproduce it ever since I changed that, even when setting them back. I'd still assume that 66 <50 byte messages per second shouldn't be an issue on a local network and that it would give me errors instead of crashing the editor. The crashes and strange errors only occurred when clients disconnected so there is probably some kind of bug there.
    I tried using NetworkTransport.GetCurrentOutgoingMessageAmount() to get the status of the buffer, but all it shows is how many messages have been sent in runtime. The number never goes down. The function description says "Return total message amount waiting for sending", so I assumed it would tell me how many messages are sitting in the buffer to be sent. The client receives the messages just fine. I tried using both unreliable channel and stateupdate channel, with the only difference being that with stateupdate at some point it decides it will not send any messages at all through that channel.

    Next issue is that I have no idea when the packets are actually sent. If I do NetworkTransport.Send(), does it send packet right away, or does it pack multiple of them in one packet and then sends it out? If the latter then what would be the send rate at which it sends packets? I feel like that's something that would be nice to have control over, It would be hard to do any kind of more advanced server-client synchronization otherwise. For example for my purpose, I'd like to have at least 30 updates per second and if the client's connection can't handle that, he will get disconnected. If it uses other send rate internally, it would not be very optimal because they'd get out of sync, which would cause extra latency.

    Now about the provided examples. The example provided in the documentation shows the following receiving code in Update(): (http://docs.unity3d.com/Manual/UNetUsingTransport.html)

    Code (CSharp):
    1. NetworkEventType recData = NetworkTransport.Receive(out recHostId, out connectionId, out channelId, recBuffer, bufferSize, out dataSize, out error);
    2.     switch (recData)
    3.     {
    4.         case NetworkEventType.Nothing:         //1
    5.             break;
    6.         case NetworkEventType.ConnectEvent:    //2
    7.             break;
    8.         case NetworkEventType.DataEvent:       //3
    9.             break;
    10.         case NetworkEventType.DisconnectEvent: //4
    11.             break;
    12.     }
    Wouldn't it be a (while) loop until the event is "NetworkEventType.Nothing" in case multiple events happen within a single frame? Or am I missing something.

    The next problem is that sometimes when I do NetworkTransport.Send() in server and the client just disconnected, I sometimes get the warning that I'm sending to a not connected connection. I can imagine that is because the networking runs on another thread and the client disconnects in the middle of the game frame code, so I haven't processed the disconnect event, even though the connection is dead already. If I receive connection events in Update() and send messages in FixedUpdate(), it can happen very often as Unity can drop rendering frames and the code keeps trying to send stuff in fixedupdate() without update() being called. The warning spam is just annoying and made me wonder if there is some way to solve it?

    Attached all the networking code I used.
     

    Attached Files:

  2. Ghosthowl

    Ghosthowl

    Joined:
    Feb 2, 2014
    Posts:
    228
    I have been using the LLAPI for a while now and I do agree with you about the documentation. It caused me a great deal of problems, but as with Unity things take time to mature.

    For your Update loop I have tried many things, but the best way I have found at least is like so. It is my network polling code. 'totalNetworkEventCount' is usually ~500 for me.

    Code (csharp):
    1.  
    2.   private static void PollNetwork()
    3.   {
    4.   if (HasSocket)
    5.   {
    6.   //Temporarily for this frame set the network event count to be to total to count down from
    7.   int networkEventCount = totalNetworkEventCount;
    8.  
    9.   //Performance Degradation
    10.   //This will force a frame to poll all events received until there is nothing,
    11.   //allowing us to grab ALL messages we should receive for this frame
    12.   while (networkEventCount-- >= 0)
    13.   {
    14.   //Receive all events
    15.   ev.eventType = NetworkTransport.Receive(out ev.hostId, out ev.connId, out ev.chanId,
    16.   ev.dataBuff, ev.dataBuff.Length, out ev.dataSize, out ev.error);
    17.  
    18.   //Break out if we have received no events worthwhile left in this frame
    19.   if (ev.eventType == NetworkEventType.Nothing)
    20.   break;
    21.   //Handle them accordingly
    22.   HandleNetworkEvent(ev.eventType, ev.hostId, ev.connId, ev.chanId, ev.dataSize, ev.dataBuff, ev.error);
    23.   //Handle Errors
    24.   if (ev.error != (byte)NetworkError.Ok)
    25.   {
    26.   HandleError(ev.error);
    27.   }
    28.   }
    29.   }
    30.   }
    31.  
    The HandleNetworkEvent just does as you have done before:

    Code (csharp):
    1.  
    2.   static void HandleNetworkEvent(NetworkEventType recDataType, int recHostId, int recConnectionId, int recChannelId, int dataSize, byte[] data, byte error)
    3.   {
    4.   switch (recDataType)
    5.   {
    6.   case NetworkEventType.BroadcastEvent:
    7.   HandleBroadCastEvent(recHostId, recConnectionId, recChannelId, dataSize, data, error);
    8.   break;
    9.   case NetworkEventType.ConnectEvent:
    10.   HandleConnectEvent(recHostId, recConnectionId, recChannelId, dataSize, data, error);
    11.   break;
    12.   case NetworkEventType.DataEvent:
    13.   HandleDataEvent(recHostId, recConnectionId, recChannelId, dataSize, data, error);
    14.   break;
    15.   case NetworkEventType.DisconnectEvent:
    16.   HandleDisconnectEvent(recHostId, recConnectionId, recChannelId, dataSize, data, error);
    17.   break;
    18.   case NetworkEventType.Nothing:
    19.   break;
    20.   default:
    21.   break;
    22.   }
    23.   }
    24.  
    As for making things more snappy, try setting your Reactor Model Type to 'Select Reactor' and the 'Thread Awake Timeout' to 0.

    As for sending and receiving, I do it all in FixedUpdate. I set the timestep manually at the beginning of both the client and server to be 60fps. As the for networking rate, I normally go with 20 packets a second, using some logic in the FixedUpdate to count frames (using modulus).

    Code (csharp):
    1. if ((frame % SendRate) == 0) Send()
    Hope this helps a little bit.
     
  3. Cirmik

    Cirmik

    Joined:
    Jan 8, 2014
    Posts:
    6
    Thanks for the reply.

    I found the loopless code in the documentation misleading and it made me wonder if that is the right way. Having a loop there does make much more sense.

    I'll try these values for the config. I hope they complement the documentation soon.

    I've still had few random editor crashes which bothers me the most. But the API is fairly new so I can understand that.
     
  4. Tinus

    Tinus

    Joined:
    Apr 6, 2009
    Posts:
    437
    Polling with a while loop until NetworkEventType.Nothing pops up is definitely the way to go. Though a note on FixedUpdate: it runs on the render thread, giving you 0 or more updates per render frame, in non-realtime. Setting it to 60 is not necessarily a way to get a steady, evenly spaced 60 update calls per second. Depending on how long the previous render frame took you might get 0 or multiple FixedUpdate calls to advance the physics in a way that keeps up roughly with real time, instead of the 1 call you were expecting.

    For example, if your render framerate is floating between 30 and 120 fps, you'll see as little as 0 and as many as 3 FixedUpdate calls per render frame. Multiple FixedUpdates per render frame will not spread out evenly over the realtime duration of that render frame, but execute as fast as possible in non-realtime at the start of that render frame.

    Suppose you get two FixedUpdates in a single frame Update because physics is playing catch-up to realtime, you'll effectively be sending/receiving over the network twice at nearly the same instant in realtime. Or if the last frame finished rendering very fast, a FixedUpdate might be skipped and you'll not send/receive anything.

    This might not be a problem for your game, but it's something to keep in mind.
     
    Last edited: Aug 4, 2015
  5. Cirmik

    Cirmik

    Joined:
    Jan 8, 2014
    Posts:
    6
    I see, I had not thought about that. I'll probably end up using about 3+ frames worth of buffer to get them evenly spaced, network jitter alone probably messes them up quite a bit with a remote connection. The 0-many fixedupdates in a single frame probably isn't a problem for me because I want to try a dedicated headless server which doesn't render anything. The client won't do any time sensitive things. As long as it keeps the longer term timing precise enough, a small buffer should solve that with the cost of extra (predictable) latency.
     
  6. aabramychev

    aabramychev

    Unity Technologies

    Joined:
    Jul 17, 2012
    Posts:
    574
    If I do NetworkTransport.Send(), does it send packet right away, or does it pack multiple of them in one packet and then sends it out?
    It packs. You have 2 parameters to manage this GlobalConfig.ThreadAwakeTimeout and ConnectionConfig.MinUpdateTimeout by setting both to 1 you will have "almost immediately" sends. Hence, the last timeout can be changed dynamically due flow control
     
  7. STBarnard

    STBarnard

    Joined:
    Jan 19, 2015
    Posts:
    3
    "Then all kind of things started going wrong. If there is 2 clients connected and 1 of them disconnects (without sending disconnect message before disconnecting), then about 50% times the server either crashes or spams some meaningless error like ">= 0" or "> 0", I think it comes from a code line that does NetworkTransport.Send, but there is no data about where the error message came from and I can't reliably reproduce it either."

    This error occurs because you are not calling Networktransport.AddHost on both sides(Yes even the client). I fought with that for ages.

    But now I am having a similar issue where the server upchucks when any Data event is received. Anyone run into this?