8

I want to use a C# plugin in my Unity project. That plugin should act as a server which will get values from a client so that I'd be able to use those values for further processing. The issue is that the server has infinite loop. And infinite loops cause Unity to hang. How to handle this?

EDIT: I'm attaching a code snippet of server program. In my opinion, there are 2 points which may be causing problem. The infinite loops and the point where program is suspended as commented in code:

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

    // Establish the local endpoint for the socket.
    // Dns.GetHostName returns the name of the 
    // host running the application.
    IPHostEntry ipHostInfo = Dns.Resolve(Dns.GetHostName());
    IPAddress ipAddress = ipHostInfo.AddressList[0];
    IPEndPoint localEndPoint = new IPEndPoint(ipAddress, 1755);

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

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

        // Start listening for connections.
        while (true)
        {
            // Program is suspended while waiting for an incoming connection.
            Debug.Log("HELLO");     //It works
            handler = listener.Accept();
            Debug.Log("HELLO");     //It doesn't work
            data = null;

            // An incoming connection needs to be processed.
            while (true)
            {
                bytes = new byte[1024];
                int bytesRec = handler.Receive(bytes);
                data += Encoding.ASCII.GetString(bytes, 0, bytesRec);
                if (data.IndexOf("<EOF>") > -1)
                {
                    break;
                }

                System.Threading.Thread.Sleep(1);
            }   

            System.Threading.Thread.Sleep(1);
        }
    }
    catch (Exception e)
    {
        Debug.Log(e.ToString());
    }
}

EDIT: After help from @Programmer, the C# plugin is complete. But Unity is not reading the correct values. I'm attaching the Unity side code:

using UnityEngine;
using System;

using SyncServerDLL;    //That's our library

public class receiver : MonoBehaviour {

    SynchronousSocketListener obj;   //That's object to call server methods

    // Use this for initialization
    void Start() {
        obj = new SynchronousSocketListener ();
        obj.startServer ();
    }

    // Update is called once per frame
    void Update() {
        Debug.Log (obj.data);
    }
}

I have tested SynchronousSocketListener class thoroughly in Visual Studio. It is giving good results there.

Programmer
  • 121,791
  • 22
  • 236
  • 328
Salman Younas
  • 340
  • 1
  • 7
  • 20
  • Update your question with your code that is hanging – Programmer Apr 10 '16 at 05:53
  • @Programmer: He provided relevant information explaining the "hanging": the code is an infinite wait loop as you would typically write in a (naive) socket server. The problem is he is doing this inside a plugin, where he is running inside the main unity thread. This effectively "hangs" Unity.That said, some code *would* be nice. – Paul-Jan Apr 10 '16 at 05:56
  • @Paul-Jan added some code to give a rough idea. – Salman Younas Apr 10 '16 at 06:02
  • 1
    @Paul-Jan. I know the problem he is having. I want to provide a solution by fixing his code instead of spending time writing new code from scratch. Also want to make sure he has something and didn't want people to write all his code. – Programmer Apr 10 '16 at 06:17
  • @Programmer I can add more code if you want. I can do the coding part for myself though. I just need a direction. – Salman Younas Apr 10 '16 at 06:20
  • I will give you direction if that's ok.. – Programmer Apr 10 '16 at 06:24
  • Check my answer. This is easy to do. – Programmer Apr 10 '16 at 06:31

1 Answers1

15

Use Thread to do your server Listen and read and write actions. You can declare socket and other networkstream objects as public then initialize them in a thread function.

Unity does not work well with while loops in Threads and may freeze sometimes, but you can fix that by adding System.Threading.Thread.Sleep(1); in your while loop where you are reading or waiting for data to arrive from socket.

Make sure to stop the Thread in OnDisable() function. Do NOT access Unity API from the new Thread function. Just do only the socket stuff there and return the data to a public variable.

System.Threading.Thread SocketThread;
volatile bool keepReading = false;

// Use this for initialization
void Start()
{
    Application.runInBackground = true;
    startServer();
}

void startServer()
{
    SocketThread = new System.Threading.Thread(networkCode);
    SocketThread.IsBackground = true;
    SocketThread.Start();
}



private string getIPAddress()
{
    IPHostEntry host;
    string localIP = "";
    host = Dns.GetHostEntry(Dns.GetHostName());
    foreach (IPAddress ip in host.AddressList)
    {
        if (ip.AddressFamily == AddressFamily.InterNetwork)
        {
            localIP = ip.ToString();
        }

    }
    return localIP;
}


Socket listener;
Socket handler;

void networkCode()
{
    string data;

    // Data buffer for incoming data.
    byte[] bytes = new Byte[1024];

    // host running the application.
    Debug.Log("Ip " + getIPAddress().ToString());
    IPAddress[] ipArray = Dns.GetHostAddresses(getIPAddress());
    IPEndPoint localEndPoint = new IPEndPoint(ipArray[0], 1755);

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

    // Bind the socket to the local endpoint and 
    // listen for incoming connections.

    try
    {
        listener.Bind(localEndPoint);
        listener.Listen(10);

        // Start listening for connections.
        while (true)
        {
            keepReading = true;

            // Program is suspended while waiting for an incoming connection.
            Debug.Log("Waiting for Connection");     //It works

            handler = listener.Accept();
            Debug.Log("Client Connected");     //It doesn't work
            data = null;

            // An incoming connection needs to be processed.
            while (keepReading)
            {
                bytes = new byte[1024];
                int bytesRec = handler.Receive(bytes);
                Debug.Log("Received from Server");

                if (bytesRec <= 0)
                {
                    keepReading = false;
                    handler.Disconnect(true);
                    break;
                }

                data += Encoding.ASCII.GetString(bytes, 0, bytesRec);
                if (data.IndexOf("<EOF>") > -1)
                {
                    break;
                }

                System.Threading.Thread.Sleep(1);
            }

            System.Threading.Thread.Sleep(1);
        }
    }
    catch (Exception e)
    {
        Debug.Log(e.ToString());
    }
}

void stopServer()
{
    keepReading = false;

    //stop thread
    if (SocketThread != null)
    {
        SocketThread.Abort();
    }

    if (handler != null && handler.Connected)
    {
        handler.Disconnect(false);
        Debug.Log("Disconnected!");
    }
}

void OnDisable()
{
    stopServer();
}
Programmer
  • 121,791
  • 22
  • 236
  • 328
  • I'm going to try that and will get back to you. Also, the server is currently synchronous server. Is it fine or should it be asynchronous? – Salman Younas Apr 10 '16 at 06:37
  • synchronous server is fine as long as you use Thread. The method above is for synchronous server. – Programmer Apr 10 '16 at 06:38
  • I updated the code to match what you have in your question.Few things you are missing should be added and it should work well. – Programmer Apr 10 '16 at 07:00
  • I have made these changes. Now to test that, I called networkCode() inside Main(). In Unity, would I have to call networkCode() or the Start() method? Thanks a lot. – Salman Younas Apr 10 '16 at 07:30
  • 1
    You do not call `networkCode` or the `Start()` method. You call the `startServer();` method. The `Start()` method is automatically called by Unity when Game starts. You call the `startServer();` code which I already did by calling it from the `Start()`. Please look at the code I posted above and I would suggest learning Unity API if you don't know how and when `Start()` is called. – Programmer Apr 10 '16 at 07:34
  • 1
    When `startServer()` is called, it creates a Thread that points to `networkCode` function and that Thread will call the `networkCode()` function after `SocketThread.Start();` is called in the `startServer()` function. – Programmer Apr 10 '16 at 07:37
  • I know that well, I just got confused because when I called startServer() from Main() just to test, it showed nothing. But now I noticed and changed IsBackground to false and called startServer() and that worked perfectly. Now I'll test that in Unity. I appreciate your help :). Just a few steps to go. – Salman Younas Apr 10 '16 at 07:39
  • 1
    Ok. I recommend you keep IsBackground to true during developement in Unity or there will be many Threads running in Unity leading to socket not disconnect properly. And when you have 1 socket still listening, you will be getting errors if you try to test the code again in Unity. This topic is advanced but read about IsBackground when you have time to understand the different between Background and Foreground Threads. – Programmer Apr 10 '16 at 07:41
  • So, it works perfectly fine in Visual Studio, but when I import that to Unity, the 'data' just keeps giving me null. May be that's because I'm testing it in editor? Any thoughts? In Unity, I just called startServer() inside Start() and then in Update(), I accessed that public 'data' variable. – Salman Younas Apr 10 '16 at 08:12
  • This should work in the editor. There should be only one instance of this code running. I did not implement that so call the function in the Start() function for now. Don't call it from the Update function because new Threads will be created every frame. That is not what you want. It is hard to help without seeing your whole code. If you want to put **EDIT** in your question and post your current code, that would be helpful. – Programmer Apr 10 '16 at 08:23
  • Updated the question. – Salman Younas Apr 10 '16 at 08:38
  • No no no. . Make that code I have above to work inside Unity. That is the first step. When it starts working, then convert it into DLL. You skipped that important step. – Programmer Apr 10 '16 at 08:53
  • Can I ask why so? Because before that, I created a test DLL just to experiment. I first tested that code in Visual Studio and then imported it in Unity and it worked there too. But that was just to return a string (just for testing). – Salman Younas Apr 10 '16 at 08:56
  • This is socket. Because a socket code works in VS does not mean that it will work in Unity. Especially the socket.read, some of the overloaded method doesn't work as expected. I am saying this based on experience from making tcplistener work with Unity and I am sure this applies to socket since socket is just a base class of tcpListener. Just test it on Unity side first. – Programmer Apr 10 '16 at 09:01
  • Ok I'll do that. But the issue is that the server which I'm implementing, I want to use that in other tools too, not just in Unity. So, you're saying that I must write my server for each platform i.e., I can't use the same DLL for unity and let's say any other other development environment? – Salman Younas Apr 10 '16 at 09:06
  • It looks like you need 2 DLL's to do that. One for Unity and One for other whatever thing you are doing. The one for Unity should work for all platforms Unity can build for. Once you finish testing this and it works, let me know and I will send you a link on how to compile DLL for Unity. I think you are doing it wrong now. – Programmer Apr 10 '16 at 09:13
  • So, as you mentioned in your answer, 'Unity does not work well with while loops in Threads'. Any code inside the while loops is not being executed. – Salman Younas Apr 10 '16 at 10:13
  • Yeah but I said the solution is `System.Threading.Thread.Sleep(1);` in each while loop. So use `Debug.Log();` to verify this. Just like I said, if it doesn't work in Unity, it won't work as a DLL. `Debug.Log();` is almost the only way to trouble Thread in Unity. When doing network stuff, download Application called "`Hercules Setup Utility`" to test your unity app by connecting to the server. I think the while loop is not being reached because it it waiting for client to connect before the while loop. Use "`Hercules Setup Utility`" to connect to it and then even send something to your app. – Programmer Apr 10 '16 at 10:27
  • I have updated the code. Have a look at that. Now the while loop is being reached but not beyond the point 'handler = listener.Accept();'. Any thoughts? – Salman Younas Apr 10 '16 at 10:58
  • I don't think you read my last post. It stops there because it is waiting for client to connect. Connect to it and it should pass there. I ask you to download `"Hercules Setup Utility"`. It was made for testing server or client. You have to connect to the server before it can move on. Try that first. Also you don't need the first while loop. I put it in my code because you had it in your code. Even though it is not the problem, remove it. Just removed it from my answer. – Programmer Apr 10 '16 at 11:22
  • Ok I'll do that. The point is that I already have my client good to go and that client does connect to the server when I try that in VS. But I'll try "Hercules Setup Utility" now. – Salman Younas Apr 10 '16 at 11:24
  • Ok. I updated my comment about the removing the first while loop just before you made your last comment. I suggest you re-read my last comment again. If you try it with "Hercules Setup Utility" and it doesn't work then I will run the code myself. – Programmer Apr 10 '16 at 11:28
  • That loop is there because the client will not just send 1 message but will keep sending. However, I removed that loop. Also tested it with "Hercules Setup Utility" and got this error 'TCP connection error :10061' after trying to connect. Please note that when I ran the same server in VS, I was able to receive messages from Hercules client. – Salman Younas Apr 10 '16 at 11:37
  • Ok. I will take a look at it. Will be back. – Programmer Apr 10 '16 at 11:44
  • Any ideas about this? – Salman Younas Apr 10 '16 at 13:50
  • This is a bug. The problem is from `IPHostEntry ipHostInfo = Dns.Resolve(Dns.GetHostName()); IPAddress ipAddress = ipHostInfo.AddressList[0]; IPEndPoint localEndPoint = new IPEndPoint(ipAddress, 1755);`. Just like I mentioned before, if it works on Visual Studio, it doesn't mean it works on Unity. I found a fix and will just paste it here. The fix contains others fixes for other problems such as client disconnection... I will just post the code here. Test it the new code I uplaoded then go to http://jacksondunstan.com/articles/3052 and learn how to build plugin for Unity in C#. – Programmer Apr 10 '16 at 14:27
  • I replaced that bad code with `getIPAddress()` then fixed some other bugs. Good luck. – Programmer Apr 10 '16 at 14:33
  • Everything is working fine now. Thanks a lot for your help. I really appreciate that :). My client will be continuously sending values to server and the server has to pass those values to my game objects. Should i change the server to Async or is it good to go? – Salman Younas Apr 10 '16 at 15:12
  • 1
    I spent time fixing those stuff. Don't change anything or you'll get new bug. You don't need async. oh and remember to remove startServer(); from Start before compiling it as plugin so that you wont be calling it twice. you can then call startServer(); from your Start() in your main application. Tested with Hercules and everything works 100%. No freezing or error. You are welcome! – Programmer Apr 10 '16 at 15:17
  • here's a challenge, @Programmer ! http://stackoverflow.com/questions/36555521/unity3d-build-png-from-panel-of-a-unity-ui – Fattie Apr 11 '16 at 17:44