Search Unity

[Code Review] ThreadedTCPClient vs TCPClient

Discussion in 'Scripting' started by Zocker1996, Oct 6, 2016.

  1. Zocker1996

    Zocker1996

    Joined:
    Jan 12, 2015
    Posts:
    20
    I'm currently developing a network application.
    I need to connect a 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.

    Here comes the code:

    ThreadedTestClient
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Events;
    3. using System.Collections.Generic;
    4. using System.Net.Sockets;
    5. using System.Threading;
    6.  
    7. public class ThreadedTestClient : MonoBehaviour
    8. {
    9.     public string Server = "127.0.0.1";
    10.     public int Port = 5001;
    11.     public int ReadBufferSize = 1024, IncomingBufferSize=1024*1024*4;
    12.     private byte[] ReadBuffer;
    13.     private TcpClient Connection;
    14.     private NetworkStream Stream;
    15.     private Queue<OutgoingMessage> WriteQueue;
    16.     private MessageBuffer IncomingBuffer;
    17.     private Thread Writer,Reader;
    18.     private bool Running;
    19.  
    20.     void Start ()
    21.     {
    22.         try {
    23.             IncomingBuffer=new MessageBuffer(IncomingBufferSize);
    24.             ReadBuffer = new byte[ReadBufferSize];
    25.             WriteQueue = new Queue<OutgoingMessage> ();
    26.             Connection = new TcpClient();
    27.             Connection.Connect(Server,Port);
    28.             Stream=Connection.GetStream();
    29.             Running=true;
    30.             Writer=new Thread(Writing);
    31.             Writer.Start();
    32.             Reader=new Thread(Reading);
    33.             Reader.Start();
    34.             #if UNITY_EDITOR
    35.             UnityEditor.EditorApplication.playmodeStateChanged = delegate () {
    36.                 if (!UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode &&
    37.                     UnityEditor.EditorApplication.isPlaying) {
    38.                     Debug.Log (string.Format("[{0}] Exiting playmode.",GetType().Name));
    39.                     Running=false;
    40.                     Writer.Abort();
    41.                     Reader.Abort();
    42.                 }
    43.             };
    44.             #endif
    45.  
    46.         } catch (System.ArgumentNullException e) {
    47.             Debug.LogWarning (string.Format ("ArgumentNullException: {0}", e));
    48.         } catch (SocketException e) {
    49.             Debug.LogWarning (string.Format ("SocketException: {0}", e));
    50.         }
    51.     }
    52.  
    53.     public void SendHelloWorld(){
    54.         string prefix="SechzehnSechzehn";
    55. //c# implementation of javas ByteBuffer
    56.         ByteBuffer b = ByteBuffer.allocate (1024);
    57.         byte[] content = System.Text.Encoding.UTF8.GetBytes (prefix+"HelloWorld");
    58.         byte[] data = new byte[b.put (MessageBuffer.intToByteArray (content.Length)).put (content).flip ().limit ()];
    59.         b.get (data);
    60.         Write (data,()=>Debug.Log("Sended Hello World!"));
    61.     }
    62.  
    63.     public void SendRandomData(int size){
    64.  
    65.         byte[] content = new byte[size+4];
    66.         new System.Random ().NextBytes (content);
    67.         byte[] bs=MessageBuffer.intToByteArray(size);
    68.         for (int i = 0; i < bs.Length; i++) {
    69.             content [i] = bs [i];
    70.         }
    71.         Write (content,()=>Debug.Log("Sended Random data!"));
    72.     }
    73.  
    74.     //onDone doesnt guarantee any data to have reached to server, just asures that data where passed to the underlying socket
    75.     public void Write (byte[] b,UnityAction onDone)
    76.     {
    77.         WriteQueue.Enqueue (new OutgoingMessage(b,onDone));
    78.     }
    79.  
    80.     private void CallWorker(Queue<MessageBuffer.EncryptedMessage> messages){
    81.         //TODO
    82.     }
    83.  
    84.     private void Reading ()
    85.     {
    86.         while (Running & Connection.Connected) {
    87.                 int readed = Stream.Read (ReadBuffer, 0, ReadBufferSize);
    88.                 //addFrom returns the amount of complete messages;
    89.                 if (IncomingBuffer.addFrom (ReadBuffer, 0, readed) > 0) {
    90.                     //gets the queue of complete messages that arrived
    91.                     CallWorker (IncomingBuffer.getAndResetQueue ());
    92.                 }
    93.         }
    94.     }
    95.  
    96.     private void Writing(){
    97.         while (Running & Connection.Connected) {
    98.             if (WriteQueue.Count > 0) {
    99.                 OutgoingMessage packet = WriteQueue.Dequeue();
    100.                 Stream.Write (packet.Data, 0, packet.Data.Length);
    101.                 packet.Done ();
    102.             }
    103.         }
    104.     }
    105.  
    106.     private class OutgoingMessage{
    107.         private readonly UnityAction OnDone;
    108.         public readonly byte[] Data;
    109.  
    110.         public OutgoingMessage(byte[] data, UnityAction onDone){
    111.             Data=data;
    112.             OnDone=onDone;
    113.         }
    114.  
    115.         public void Done(){
    116.             if (OnDone != null) {
    117.                 OnDone ();
    118.             }
    119.         }
    120.     }
    121. }
    122.  

    TestClient
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Events;
    3. using System.Collections.Generic;
    4. using System.Net.Sockets;
    5.  
    6. public class TestClient : MonoBehaviour
    7. {
    8.     public string Server = "127.0.0.1";
    9.     public int Port = 5001;
    10.     public int ReadBufferSize = 1024, IncomingBufferSize=1024*1024*4;
    11.     private byte[] ReadBuffer;
    12.     private TcpClient Connection;
    13.     private Queue<OutgoingMessage> WriteQueue;
    14.     private int WriteOffset;
    15.     private MessageBuffer IncomingBuffer;
    16.  
    17.     void Start ()
    18.     {
    19.         try {
    20.             IncomingBuffer=new MessageBuffer(IncomingBufferSize);
    21.             ReadBuffer = new byte[ReadBufferSize];
    22.             WriteQueue = new Queue<OutgoingMessage> ();
    23.             Connection = new TcpClient();
    24.             Connection.Connect(Server,Port);
    25.             Connection.Client.Blocking = false;
    26.         } catch (System.ArgumentNullException e) {
    27.             Debug.LogWarning (string.Format ("ArgumentNullException: {0}", e));
    28.         } catch (SocketException e) {
    29.             Debug.LogWarning (string.Format ("SocketException: {0}", e));
    30.         }
    31.     }
    32.  
    33.     public void SendHelloWorld(){
    34.         string prefix="SechzehnSechzehn";
    35.         ByteBuffer b = ByteBuffer.allocate (1024);
    36.         byte[] content = System.Text.Encoding.UTF8.GetBytes (prefix+"HelloWorld");
    37.         byte[] data = new byte[b.put (MessageBuffer.intToByteArray (content.Length)).put (content).flip ().limit ()];
    38.         b.get (data);
    39.         Write (data,()=>Debug.Log("Sended Hello World!"));
    40.     }
    41.  
    42.     public void SendRandomData(int size){
    43.        
    44.         byte[] content = new byte[size+4];
    45.         new System.Random ().NextBytes (content);
    46.         byte[] bs=MessageBuffer.intToByteArray(size);
    47.         for (int i = 0; i < bs.Length; i++) {
    48.             content [i] = bs [i];
    49.         }
    50.         Write (content,()=>Debug.Log("Sended Random data!"));
    51.     }
    52.  
    53.     public void Write (byte[] b,UnityAction onDone)
    54.     {
    55.         WriteQueue.Enqueue (new OutgoingMessage(b,onDone));
    56.     }
    57.  
    58.     private void CallWorker(Queue<MessageBuffer.EncryptedMessage> messages){
    59.         //TODO
    60.     }
    61.  
    62.     void Update ()
    63.     {
    64.         if (Connection.Connected) {
    65.             if (Connection.Client.Available > 0) {
    66.                 int readed=Connection.Client.Receive (ReadBuffer);
    67.                 if (IncomingBuffer.addFrom (ReadBuffer, 0, readed) > 0) {
    68.                     CallWorker (IncomingBuffer.getAndResetQueue ());
    69.                 }
    70.             }
    71.             if (WriteQueue.Count > 0) {
    72.                 OutgoingMessage packet = WriteQueue.Peek ();
    73.                 WriteOffset += Connection.Client.Send (packet.Data, WriteOffset, packet.Data.Length - WriteOffset, SocketFlags.None);
    74.                 if (WriteOffset >= packet.Data.Length) {
    75.                     WriteQueue.Dequeue ();
    76.                     WriteOffset = 0;
    77.                     packet.Done ();
    78.                 }
    79.             }
    80.         }
    81.     }
    82.  
    83.     private class OutgoingMessage{
    84.         private readonly UnityAction OnDone;
    85.         public readonly byte[] Data;
    86.  
    87.         public OutgoingMessage(byte[] data, UnityAction onDone){
    88.             Data=data;
    89.             OnDone=onDone;
    90.         }
    91.  
    92.         public void Done(){
    93.             if (OnDone != null) {
    94.                 OnDone ();
    95.             }
    96.         }
    97.     }
    98. }
    99.