2
\$\begingroup\$

I'm not sure if this is the right subsite of stackexchange because it's not only about code review but a bit about design eihter.

I'm currently developing a network application. I need to connect a (java) server with a tcp socket. I implemented two different variants of my client. One uses a blocking socket with threads, the other one uses a non blocking socket and Unitys Update method. Personally I like the one without threads better, but my concern is that if I use Unitys Update method writing to the socket could take longer, especially if Unitys framerate is low, since Update would be called less frequently than with a seperate thread. I know there are many other questions about blocking and non-blocking sockets, but nothing about Unity3d.

The approach with a blocking TCPClient and Threads:

using UnityEngine;
using UnityEngine.Events;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Threading;

public class ThreadedTestClient : MonoBehaviour
{
    public string Server = "127.0.0.1";
    public int Port = 5001;
    public int ReadBufferSize = 1024, IncomingBufferSize=1024*1024*4;
    private byte[] ReadBuffer;
    private TcpClient Connection;
    private NetworkStream Stream;
    private Queue<OutgoingMessage> WriteQueue;
    private MessageBuffer IncomingBuffer;
    private Thread Writer,Reader;
    private bool Running;

    void Start ()
    {
        try {
            IncomingBuffer=new MessageBuffer(IncomingBufferSize);
            ReadBuffer = new byte[ReadBufferSize];
            WriteQueue = new Queue<OutgoingMessage> ();
            Connection = new TcpClient();
            Connection.Connect(Server,Port);
            Stream=Connection.GetStream();
            Running=true;
            Writer=new Thread(Writing);
            Writer.Start();
            Reader=new Thread(Reading);
            Reader.Start();
            #if UNITY_EDITOR
            UnityEditor.EditorApplication.playmodeStateChanged = delegate () {
                if (!UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode &&
                    UnityEditor.EditorApplication.isPlaying) {
                    Debug.Log (string.Format("[{0}] Exiting playmode.",GetType().Name));
                    Running=false;
                    Writer.Abort();
                    Reader.Abort();
                }
            };
            #endif

        } catch (System.ArgumentNullException e) {
            Debug.LogWarning (string.Format ("ArgumentNullException: {0}", e));
        } catch (SocketException e) {
            Debug.LogWarning (string.Format ("SocketException: {0}", e));
        }
    }

    public void SendHelloWorld(){
        string prefix="SechzehnSechzehn";
//c# implementation of javas ByteBuffer
        ByteBuffer b = ByteBuffer.allocate (1024);
        byte[] content = System.Text.Encoding.UTF8.GetBytes (prefix+"HelloWorld");
        byte[] data = new byte[b.put (MessageBuffer.intToByteArray (content.Length)).put (content).flip ().limit ()];
        b.get (data);
        Write (data,()=>Debug.Log("Sended Hello World!"));
    }

    public void SendRandomData(int size){

        byte[] content = new byte[size+4];
        new System.Random ().NextBytes (content);
        byte[] bs=MessageBuffer.intToByteArray(size);
        for (int i = 0; i < bs.Length; i++) {
            content [i] = bs [i];
        }
        Write (content,()=>Debug.Log("Sended Random data!"));
    }

    //onDone doesnt guarantee any data to have reached to server, just asures that data where passed to the underlying socket
    public void Write (byte[] b,UnityAction onDone)
    {
        WriteQueue.Enqueue (new OutgoingMessage(b,onDone));
    }

    private void CallWorker(Queue<MessageBuffer.EncryptedMessage> messages){
        //TODO
    }

    private void Reading ()
    {
        while (Running & Connection.Connected) {
                int readed = Stream.Read (ReadBuffer, 0, ReadBufferSize);
                //addFrom returns the amount of complete messages;
                if (IncomingBuffer.addFrom (ReadBuffer, 0, readed) > 0) {
                    //gets the queue of complete messages that arrived
                    CallWorker (IncomingBuffer.getAndResetQueue ());
                }
        }
    }

    private void Writing(){
        while (Running & Connection.Connected) {
            if (WriteQueue.Count > 0) {
                OutgoingMessage packet = WriteQueue.Dequeue();
                Stream.Write (packet.Data, 0, packet.Data.Length);
                packet.Done ();
            }
        }
    }

    private class OutgoingMessage{
        private readonly UnityAction OnDone;
        public readonly byte[] Data;

        public OutgoingMessage(byte[] data, UnityAction onDone){
            Data=data;
            OnDone=onDone;
        }

        public void Done(){
            if (OnDone != null) {
                OnDone ();
            }
        }
    }
}

The approach with a non-blocking TCPClient and Unitys Update:

using UnityEngine;
using UnityEngine.Events;
using System.Collections.Generic;
using System.Net.Sockets;

public class TestClient : MonoBehaviour
{
    public string Server = "127.0.0.1";
    public int Port = 5001;
    public int ReadBufferSize = 1024, IncomingBufferSize=1024*1024*4;
    private byte[] ReadBuffer;
    private TcpClient Connection;
    private Queue<OutgoingMessage> WriteQueue;
    private int WriteOffset;
    private MessageBuffer IncomingBuffer;

    void Start ()
    {
        try {
            IncomingBuffer=new MessageBuffer(IncomingBufferSize);
            ReadBuffer = new byte[ReadBufferSize];
            WriteQueue = new Queue<OutgoingMessage> ();
            Connection = new TcpClient();
            Connection.Connect(Server,Port);
            Connection.Client.Blocking = false;
        } catch (System.ArgumentNullException e) {
            Debug.LogWarning (string.Format ("ArgumentNullException: {0}", e));
        } catch (SocketException e) {
            Debug.LogWarning (string.Format ("SocketException: {0}", e));
        }
    }

    public void SendHelloWorld(){
        string prefix="SechzehnSechzehn";
        ByteBuffer b = ByteBuffer.allocate (1024);
        byte[] content = System.Text.Encoding.UTF8.GetBytes (prefix+"HelloWorld");
        byte[] data = new byte[b.put (MessageBuffer.intToByteArray (content.Length)).put (content).flip ().limit ()];
        b.get (data);
        Write (data,()=>Debug.Log("Sended Hello World!"));
    }

    public void SendRandomData(int size){

        byte[] content = new byte[size+4];
        new System.Random ().NextBytes (content);
        byte[] bs=MessageBuffer.intToByteArray(size);
        for (int i = 0; i < bs.Length; i++) {
            content [i] = bs [i];
        }
        Write (content,()=>Debug.Log("Sended Random data!"));
    }

    public void Write (byte[] b,UnityAction onDone)
    {
        WriteQueue.Enqueue (new OutgoingMessage(b,onDone));
    }

    private void CallWorker(Queue<MessageBuffer.EncryptedMessage> messages){
        //TODO
    }

    void Update ()
    {
        if (Connection.Connected) {
            if (Connection.Client.Available > 0) {
                int readed=Connection.Client.Receive (ReadBuffer);
                if (IncomingBuffer.addFrom (ReadBuffer, 0, readed) > 0) {
                    CallWorker (IncomingBuffer.getAndResetQueue ());
                }
            }
            if (WriteQueue.Count > 0) {
                OutgoingMessage packet = WriteQueue.Peek ();
                WriteOffset += Connection.Client.Send (packet.Data, WriteOffset, packet.Data.Length - WriteOffset, SocketFlags.None);
                if (WriteOffset >= packet.Data.Length) {
                    WriteQueue.Dequeue ();
                    WriteOffset = 0;
                    packet.Done ();
                }
            }
        }
    }

    private class OutgoingMessage{
        private readonly UnityAction OnDone;
        public readonly byte[] Data;

        public OutgoingMessage(byte[] data, UnityAction onDone){
            Data=data;
            OnDone=onDone;
        }

        public void Done(){
            if (OnDone != null) {
                OnDone ();
            }
        }
    }
}
\$\endgroup\$
  • \$\begingroup\$ If the code is working then it's the right place to ask for a review ;-] \$\endgroup\$ – t3chb0t Oct 7 '16 at 9:54
  • 1
    \$\begingroup\$ I think you might want to look into unity's IEnumerator or FixedUpdate instead of the Update() you are using right now. Considering 30fps, you would execute that code 30times / second. Let's not even consider playing at 120+ fps \$\endgroup\$ – MX D Oct 7 '16 at 11:31
  • \$\begingroup\$ How often should this code be executed, how often is enough? I think you are right that in non blocking way reading so often isnt nessecary but I just dont know how often is right... \$\endgroup\$ – Eric Oct 7 '16 at 11:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.