Modifying .net SocketServer example to run in unity

Hi
Bit of a challenge for me. I need to create a TCP Socket server. Since my understanding is that the built-in network layer in Unity is UDP based, and I need TCP sockets, I need to use the .net Socket Server examples.

So I am using THIS example here.

The only change is that I added ‘using UnityEngine’ and changed all the ‘Console.Writeline’ statements to ‘Debug.Log’.
I also removed the arguments in the Main method entry point.

From Unity in a MonoBehaviour class I simply call

AsynchronousSocketListener.Main() from Start();

The problem seems to be that Unity simply ‘hangs’ when I hit play in the editor. Even though the Server example is Asynchronous could this hanging be because the application execution is blocked’ until a connection is established?

Shouldn’t I be seeing some other error?

Very frustrating is there is essentially NO working TCP socket server code I can find that just works out of the box.

ANY feedback appreciated!

My completed AsynchronousSocketListener .cs file

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine;

// State object for reading client data asynchronously 
public class StateObject
{
  // Client  socket. 
  public Socket workSocket = null;
  // Size of receive buffer. 
  public const int BufferSize = 1024;
  // Receive buffer. 
  public byte[] buffer = new byte[BufferSize];
  // Received data string. 
  public StringBuilder sb = new StringBuilder();
}

public class AsynchronousSocketListener
{
  // Thread signal. 
  public static ManualResetEvent allDone = new ManualResetEvent(false);

  public AsynchronousSocketListener()
  {
  }

  public static void StartListening()
  {
  // Data buffer for incoming data. 
  byte[] bytes = new Byte[1024];

  // Establish the local endpoint for the socket. 
  // The DNS name of the computer 
  // running the listener is "host.contoso.com". 
  //IPHostEntry ipHostInfo = Dns.Resolve(Dns.GetHostName());
  IPHostEntry ipHostInfo = Dns.GetHostEntry(Dns.GetHostName());
  Debug.Log("0 ASSERT ipHostInfo " + ipHostInfo);
  //master //IPAddress ipAddress = ipHostInfo.AddressList[0];
  IPAddress ipAddress = IPAddress.Loopback;
  
  Debug.Log("1 ASSERT ipAddress " + ipAddress);
  IPEndPoint localEndPoint = new IPEndPoint(ipAddress, 8744);

  // Create a TCP/IP socket. 
  Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

  //alt //Socket listener = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

  // Bind the socket to the local endpoint and listen for incoming connections. 
  try
  {
  listener.Bind(localEndPoint);
  listener.Listen(100);

  while (true)
  {
  // Set the event to nonsignaled state. 
  allDone.Reset();

  // Start an asynchronous socket to listen for connections. 
  Debug.Log("Waiting for a connection...");
  listener.BeginAccept(
  new AsyncCallback(AcceptCallback),
  listener);

  // Wait until a connection is made before continuing. 
  allDone.WaitOne();
  }

  }
  catch (Exception e)
  {
  Debug.LogError(e.ToString());
  }

  Debug.Log("\nPress ENTER to continue...");
  // Console.Read();

  }

  public static void AcceptCallback(IAsyncResult ar)
  {
  // Signal the main thread to continue. 
  Debug.Log("++++++++++++++++++++++++++++++++");
  Debug.Log(" ");
  Debug.Log(" ");
  Debug.Log("1 ASSERT AcceptCallback "+ar);
  Debug.Log(" ");
  Debug.Log(" ");
  Debug.Log("++++++++++++++++++++++++++++++++");
  allDone.Set();

  // Get the socket that handles the client request. 
  Socket listener = (Socket)ar.AsyncState;
  Socket handler = listener.EndAccept(ar);

  // Create the state object. 
  StateObject state = new StateObject();
  state.workSocket = handler;
  handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
  new AsyncCallback(ReadCallback), state);
  }

  public static void ReadCallback(IAsyncResult ar)
  {
  String content = String.Empty;
  Debug.Log("2 ASSERT ReadCallback " + ar);
  // Retrieve the state object and the handler socket 
  // from the asynchronous state object. 
  StateObject state = (StateObject)ar.AsyncState;
  Socket handler = state.workSocket;

  // Read data from the client socket.  
  int bytesRead = handler.EndReceive(ar);
  Debug.Log("3 ASSERT ReadCallback bytesRead " + bytesRead);
  if (bytesRead > 0)
  {
  // There  might be more data, so store the data received so far. 
  state.sb.Append(Encoding.ASCII.GetString(
  state.buffer, 0, bytesRead));

  // Check for end-of-file tag. If it is not there, read  
  // more data. 
  content = state.sb.ToString();

  if (content.IndexOf("TRIGGER") > -1)
  {

  Debug.LogWarning("4 ASSERT YES TRIGGER!!");
  }
  if (content.IndexOf("<EOF>") > -1)
  {
  // All the data has been read from the  
  // client. Display it on the console. 
  Debug.Log("4 ASSERT Read {0} bytes from socket. \n Data : {1} " + content.Length+ " and content is "+ content);
  // Echo the data back to the client. 
  // Send(handler, content);
  }
  else
  {
  // Not all data received. Get more. 
  handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
  new AsyncCallback(ReadCallback), state);
  }
  }
  }

  private static void Send(Socket handler, String data)
  {
  // Convert the string data to byte data using ASCII encoding. 
  byte[] byteData = Encoding.ASCII.GetBytes(data);

  // Begin sending the data to the remote device. 
  handler.BeginSend(byteData, 0, byteData.Length, 0,
  new AsyncCallback(SendCallback), handler);
  }

  private static void SendCallback(IAsyncResult ar)
  {
  try
  {
  // Retrieve the socket from the state object. 
  Socket handler = (Socket)ar.AsyncState;

  // Complete sending the data to the remote device. 
  int bytesSent = handler.EndSend(ar);
  string temp = bytesSent.ToString();
  Debug.Log("Sent {0} bytes to client."+ temp);

  handler.Shutdown(SocketShutdown.Both);
  handler.Close();

  }
  catch (Exception e)
  {
  Debug.Log(e.ToString());
  }
  }

  //public static int Main(String[] args)
  public static int Main()
  {
  StartListening();
  return 0;
  }
}

First off, lets format this code nicely so it’s reasonable readable:

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine;

// State object for reading client data asynchronously
public class StateObject
{
  // Client  socket.
  public Socket workSocket = null;
  // Size of receive buffer.
  public const int BufferSize = 1024;
  // Receive buffer.
  public byte[] buffer = new byte[BufferSize];
  // Received data string.
  public StringBuilder sb = new StringBuilder();
}

public class AsynchronousSocketListener
{
  // Thread signal.
  public static ManualResetEvent allDone = new ManualResetEvent(false);

  public AsynchronousSocketListener()
  {
  }

  public static void StartListening()
  {
    // Data buffer for incoming data.
    byte[] bytes = new Byte[1024];
  
    // Establish the local endpoint for the socket.
    // The DNS name of the computer
    // running the listener is "host.contoso.com".
    //IPHostEntry ipHostInfo = Dns.Resolve(Dns.GetHostName());
    IPHostEntry ipHostInfo = Dns.GetHostEntry(Dns.GetHostName());
    Debug.Log("0 ASSERT ipHostInfo " + ipHostInfo);
    //master //IPAddress ipAddress = ipHostInfo.AddressList[0];
    IPAddress ipAddress = IPAddress.Loopback;
  
    Debug.Log("1 ASSERT ipAddress " + ipAddress);
    IPEndPoint localEndPoint = new IPEndPoint(ipAddress, 8744);
  
    // Create a TCP/IP socket.
    Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
  
    //alt //Socket listener = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
  
    // Bind the socket to the local endpoint and listen for incoming connections.
    try
    {
      listener.Bind(localEndPoint);
      listener.Listen(100);
    
      while (true)
      {
        // Set the event to nonsignaled state.
        allDone.Reset();
      
        // Start an asynchronous socket to listen for connections.
        Debug.Log("Waiting for a connection...");
        listener.BeginAccept(
          new AsyncCallback(AcceptCallback),
          listener);
      
        // Wait until a connection is made before continuing.
        allDone.WaitOne();
      }
    
    }
    catch (Exception e)
    {
      Debug.LogError(e.ToString());
    }
  
    Debug.Log("\nPress ENTER to continue...");
    // Console.Read();
  
  }
  
  public static void AcceptCallback(IAsyncResult ar)
  {
    // Signal the main thread to continue.
    Debug.Log("++++++++++++++++++++++++++++++++");
    Debug.Log(" ");
    Debug.Log(" ");
    Debug.Log("1 ASSERT AcceptCallback "+ar);
    Debug.Log(" ");
    Debug.Log(" ");
    Debug.Log("++++++++++++++++++++++++++++++++");
    allDone.Set();
  
    // Get the socket that handles the client request.
    Socket listener = (Socket)ar.AsyncState;
    Socket handler = listener.EndAccept(ar);
  
    // Create the state object.
    StateObject state = new StateObject();
    state.workSocket = handler;
    handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
    new AsyncCallback(ReadCallback), state);
  }
  
  public static void ReadCallback(IAsyncResult ar)
  {
    String content = String.Empty;
    Debug.Log("2 ASSERT ReadCallback " + ar);
    // Retrieve the state object and the handler socket
    // from the asynchronous state object.
    StateObject state = (StateObject)ar.AsyncState;
    Socket handler = state.workSocket;
  
    // Read data from the client socket. 
    int bytesRead = handler.EndReceive(ar);
    Debug.Log("3 ASSERT ReadCallback bytesRead " + bytesRead);
    if (bytesRead > 0)
    {
      // There  might be more data, so store the data received so far.
      state.sb.Append(Encoding.ASCII.GetString(
      state.buffer, 0, bytesRead));
    
      // Check for end-of-file tag. If it is not there, read 
      // more data.
      content = state.sb.ToString();
    
      if (content.IndexOf("TRIGGER") > -1)
      {
    
        Debug.LogWarning("4 ASSERT YES TRIGGER!!");
      }
      if (content.IndexOf("<EOF>") > -1)
      {
        // All the data has been read from the 
        // client. Display it on the console.
        Debug.Log("4 ASSERT Read {0} bytes from socket. \n Data : {1} " + content.Length+ " and content is "+ content);
        // Echo the data back to the client.
        // Send(handler, content);
      }
      else
      {
        // Not all data received. Get more.
        handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
        new AsyncCallback(ReadCallback), state);
      }
    }
  }

  private static void Send(Socket handler, String data)
  {
    // Convert the string data to byte data using ASCII encoding.
    byte[] byteData = Encoding.ASCII.GetBytes(data);
  
    // Begin sending the data to the remote device.
    handler.BeginSend(byteData, 0, byteData.Length, 0,
    new AsyncCallback(SendCallback), handler);
  }

  private static void SendCallback(IAsyncResult ar)
  {
    try
    {
      // Retrieve the socket from the state object.
      Socket handler = (Socket)ar.AsyncState;
    
      // Complete sending the data to the remote device.
      int bytesSent = handler.EndSend(ar);
      string temp = bytesSent.ToString();
      Debug.Log("Sent {0} bytes to client."+ temp);
    
      handler.Shutdown(SocketShutdown.Both);
      handler.Close();
  
    }
    catch (Exception e)
    {
      Debug.Log(e.ToString());
    }
  }

  //public static int Main(String[] args)
  public static int Main()
  {
    StartListening();
    return 0;
  }
}

Next, the reason it hangs is because in ‘StartListening’, the WaitHandle ‘allDone’ has ‘WaitOne’ called. This causes that method to block until the work is all done.

So you’re going to get hang.

Sure it’s asynchronous in that the work is done on another thread… but you’re blocking the main thread while waiting. Don’t do that.

Do note though, that when the work is done, getting back to the main thread isn’t as simple as one may hope though. A callback/delegate won’t work as it’ll be on the other thread. You may want to look into the various methods for returning to the main thread in unity. Personally I like the implementation used by ThreadNinja.

Thanks!. Apologies as I am a networking novice. Is the solution as simple as commenting out

‘allDone.WaitOne()’ or…?