0

I'm trying to learn Game Networking programming so I started a very simple async udp socket system using Unity, however when I added JSON serialization things got a little bit weird.

I can connect to the server and send packets with the SendPacket(string msg) method and it will receive it fine on the server side. And it will work fine as long as the size of the msg variable is the same size or bigger. So if I send a string "Hello" and then one "Hello World" will work fine and be able to print it on the other size. However if I was now to send another packet with the string "Hi" nothing would happen and no more connections will work on that socket neither sending new packets.

I have checked and I'm receiving the packet over the network and it's not empty it has the information however it seems to stop when it gets to the code:

Packet p = e.Buffer.FromJsonBinary<Packet>();

Which is on the OnConnect function on my server side.After that function It seems my server stops listening not accepting new connections and no other packets will be receive, I suspect it somehow stops my async process.

For the Json Serialization I'm using JsonUtility from Unity.

Any ideas what is happening?

Server Class

public class Server
{
    private static string _protocolID = "hash";
    private static ushort _port = 11000;
    private Socket _socket;

    private SocketAsyncEventArgs _event;
    private List<Socket> _connections = new List<Socket>();
    
    public void Start()
    {
        Listen();
    }

    private bool Listen()
    {
        // Create UDP Socket
        _socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

        // Set the socket into non-blocking mode 
        _socket.Blocking = false;

        try
        {
            _socket.Bind(new IPEndPoint(IPAddress.Any, _port));
            _event = new SocketAsyncEventArgs();
            _event.Completed += OnConnect;

            byte[] buffer = new byte[1024];
            _event.SetBuffer(buffer, 0, 1024);

            
            //_socket.ReceiveAsync(_event);
            StartListening(_event);
            

        }
        catch (Exception e)
        {
            Debug.LogException(e);
            return false;
        }

        return true;
    }

    private void StartListening(SocketAsyncEventArgs e)
    {
        //e.AcceptSocket = null;
        _socket.ReceiveAsync(e);
        
    }
    private void OnConnect(object sender, SocketAsyncEventArgs e)
    {
        if (e.BytesTransferred > 0)
        {
            if (e.SocketError != SocketError.Success)
                Debug.Log("ERROR"); // TODO: Close Socket

            Packet p = e.Buffer.FromJsonBinary<Packet>();
            if (p._protocolID != _protocolID)
                Debug.Log("Protocol Error");

            Debug.Log("Connect:" + p._msg);

            if (!_socket.ReceiveAsync(e))
            {
                // Call completed synchonously
                StartListening(e);
            }
        }
        else
        {
            Debug.Log("No data");
        }
    }
}

Client Class

public class Client
{
    private static string _protocolID = "hash";
    private ushort _port = 11000;
    private string _ip = "127.0.0.1";
    private Socket _socket;
    private SocketAsyncEventArgs _event;

    public bool Connect()
    {

        // Create UDP Socket
        _socket = new Socket(SocketType.Dgram, ProtocolType.Udp);

        // Set the socket into non-blocking mode 
        _socket.Blocking = false;
        IPEndPoint ip = new IPEndPoint(IPAddress.Parse(_ip), _port);

        _event = new SocketAsyncEventArgs();
        //_event.Completed += Callback;
       

        try 
        {
            _socket.Connect(ip);
            Debug.Log($"Connection was to {_ip} was sucessfull");
        } 
        catch(Exception e)
        {
            Debug.LogException(e);
            Debug.Log("Couldn't connect Socket");
            return false;
        }

        return true;
    }

    public void SendPacket<T>(T t)
    {
        try
        {
            if (_socket == null)
                Debug.Log("Null socket");

            // Encode the data string into a byte array.  
            byte[] buffer = t.ToJsonBinary();
            _event.SetBuffer(buffer, 0, buffer.Length);

            // Send the data through the socket.  
            //_socket.SendAsync(_event);

            bool willRaiseEvent = _socket.SendAsync(_event);
            //if (!willRaiseEvent)
            //{
            //    SendPacket<T>(t);
            //}
        }
        catch (SocketException e)
        {a
            Debug.LogException(e);
            Debug.Log("Couldn't Send Packet");
        }
    }

    public void SendPacket(string msg)
    {
        Packet packet = new Packet(_protocolID, msg);
        SendPacket<Packet>(packet);
    }
}

Json Serialization

    public static byte[] ToJsonBinary(this object obj)
    {
        return Encoding.ASCII.GetBytes(JsonUtility.ToJson(obj));
    }

    public static T FromJsonBinary<T>(this byte[] data)
    {
        return JsonUtility.FromJson<T>(Encoding.ASCII.GetString(data));
    }

Packet

[Serializable]
public struct Packet
{
    public string _protocolID;
    public string _msg;

    public Packet(string protocolID, string msg)
    {
        _protocolID = protocolID;
        _msg = msg;
    }
}

Edit: I found out it's crashing due to Json Utility

  System.ArgumentException: JSON parse error: The document root must not follow by other values.

Json string before being sent (top), Json string reiceived at the server (bottom)

It seems on the server the buffer isn't being cleared so it still has information in it.

  • In general the `UDP` protocol is connection less ... you fire out packets and (maybe) someone listens and receives packets but you don't care and don't know whether packets get lost in between. If you want a stable connection and guarantee that everything is received and in order you would rather need the TCP protocol. I would also suggest to directly use [`UdpClient`](https://learn.microsoft.com/dotnet/api/system.net.sockets.udpclient) – derHugo Jan 17 '22 at 03:32
  • Also not sure but I think you would do `if (!_socket.ReceiveAsync(e)) { OnConnect(null, e); }` as it returns `true if the I/O operation is pending. The Completed event on the e parameter will be raised upon completion of the operation. false if the I/O operation completed synchronously. In this case, The Completed event on the e parameter will not be raised and the e object passed as a parameter may be examined immediately after the method call returns to retrieve the result of the operation.` it should also be called `OnReceived` not `OnConnect` ;) – derHugo Jan 17 '22 at 03:47
  • I'm making it for a game so TCP would be a little slow for it, but the problem is fine to lose packets. However the problem is if I use the function SendPacket with a string size of five, and then one with string size of six and so on as long as string size is bigger or the same as the last one the packet will arrive no problem. But if I would send a smaller string now it won't work. I will receive the packet I just won't be able to deserialize it! Also you are totally right it should be OnReceived, but I was trying to do a virtual connection over UDP sort of like TCP does! – João Guilherme Jan 17 '22 at 05:31
  • `but I was trying to do a virtual connection over UDP sort of like TCP does` .. as said there is no such thing really. This would require you have a message counter and confirm messages for each received package and resend lost packages and delay until you have all packages received in order ... then again you could just use TCP in the first place ;) – derHugo Jan 17 '22 at 07:47
  • As said have you tried using rather `if (!_socket.ReceiveAsync(e)) { OnConnect(null, e); }` ? Also have in mind that each UDP message has a header of about [46 bytes](https://stackoverflow.com/questions/4218553/what-is-the-size-of-udp-packets-if-i-send-0-payload-data-in-c/4218766#4218766) overhead ... So sending such small messages is a huge waste and you might want to come up with a system for bundling multiple messages together in a single UDP package with total size 1500 bytes ... – derHugo Jan 17 '22 at 07:53
  • I was following the https://gafferongames.com/post/virtual_connection_over_udp/ for the udp virtual connection it's rather simple it's just assumes you have a connection as long as you send a packet and it has the right protocol identification rather than a handshake like in TCP. I tried TCP but it's rather slow for games specially fast-paced fps. – João Guilherme Jan 17 '22 at 09:42
  • I just tried ```if (!_socket.ReceiveAsync(e)) { OnConnect(null, e); } ``` it seems to have the same problem. I believe the problem seems to be how I'm using ``` Packet p = e.Buffer.FromJsonBinary();``` I have nailed that it runs till there than abruptly stops somehow. If you want to try and run it, I have the source code here: https://github.com/Sioyth/Jnet – João Guilherme Jan 17 '22 at 09:43
  • I have implemented my own local socket multiuser backend ;) Therefore I would always recommend the following: Start with a reliable TCP connection channel you maintain connected for the entire time of the session. Here you send anything that is necessary to be reliable and in order like certain single event method calls etc. Additionally open an unreliable UDP channel once the connection is established for sending rapid frame based things like position data. – derHugo Jan 17 '22 at 09:47
  • I think your main issue will be: The `buffer` is a `byte[]` with length 1024! Unlikely that your 7 byte strings fill up this buffer so when you try to deserialize you do it on the entire array which results in a huge string with a lot of empty bytes converted to chars ... you might want to stripe these off using `Trim` before JSON deserialize – derHugo Jan 17 '22 at 09:49
  • That sounds good, I was thinking for things like a chat and other data TCP is nice due to it's reliability. I could use both like that for different types of data. ```byte[] buffer = t.ToJsonBinary(); _event.SetBuffer(buffer, 0, buffer.Length); ``` I'm actually not sending over 1024 bytes I'm sending the length of the packet – João Guilherme Jan 17 '22 at 09:56
  • `I'm actually not sending over 1024 bytes I'm sending the length of the packet` yes but on the receiver you receive into a buffer with size `1024` and then forward that entire buffer to the deserialization.. might be wrong about that but it looks like it – derHugo Jan 17 '22 at 10:38
  • Oh sorry I'm still new to all of this so it's a bit confusing, but you are right it seems on the server side the buffer replaces the old information with the new one but doesn't clear the bytes. So if I have a smaller string at the end of it there will be trash remaining from the last string I used in the buffer. Is there any way to clear the buffer? I guess I can manually just do ```byte[] buffer = new byte[1024]; _event.SetBuffer(buffer, 0, 1024);``` again but that seems kinda hackish. Honestly I thought it would clear automatically after each call – João Guilherme Jan 17 '22 at 10:45
  • Never used sockets directly .. I know that the `UdpClient` either only returns according bytes or at least a count how many bytes to use from the buffer .. not sure how `ReceiveAsync` handles that – derHugo Jan 17 '22 at 11:09

1 Answers1

0

Okay so it seems the problem was due to how SocketAsyncEventArgs works. Since I'm calling recursively and passing the same event it never clears it's buffer.

 if (!_socket.ReceiveAsync(e))
       {
            OnConnect(null, e);
       }

I found two solutions one was to do pass a new SocketAsyncEventArgs which will have a clear buffer. But I believe a better idea would be to make a pool to handle and reuse a set of predefined SocketAsyncEventArgs instead of constantly creating new ones.

Another solution I found was to simple set a new buffer as a way to clear the last one.

byte[] buffer = t.ToJsonBinary();
_event.SetBuffer(buffer, 0, buffer.Length);