Convert a C # socket server to be compatible with unity

Hello guys

I created a multithreaded socket server in C # but it is not compatible with Unity, here is my code

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace MultiThreaded_Server
{
    class Program
    {
       
        class ComClient //Class interne
        {
            public Socket sock2 { get; set; } //Recupère la socket du client
            public int numero { get; set; } //Et son numéro

            public ComClient(Socket sock, int num)
            {
                this.sock2 = sock;
                this.numero = num;
            }
        }

        private int nClients;

        public Program() //Constructeur
        {
            Console.WriteLine(" >>> Setting up the server... ");

            for (int i = 0; i < 3; i++) //Waiting Time
            {
                Console.WriteLine("...");
                System.Threading.Thread.Sleep(1000); //Wait 1 Sec
            }

            Console.WriteLine(" >>> Server is up ! ");

            Socket sock1 = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //Création de la socket type IpV4, Point à Point et Protocole Tcp.
            IPEndPoint ipEP = new IPEndPoint(IPAddress.Parse("192.168.20.222"), 2906); //Adresse et port d'écoute
            sock1.Bind(ipEP); //Bind la socket sur le IpEndPoint
            sock1.Listen(1); //Demande d'écoute

            while (true) //Boucle infinie
            {
                Socket sock = sock1.Accept(); //Attente et acceptation des clients
                ++ nClients;
                ComClient ComC = new ComClient(sock, nClients); //Donne la socket et le numero au client
                Thread th1 = new Thread(Comm); //Thread pour la communication client
                th1.Start(ComC); //Recupère les 2 paramètres grâce a la class ComClient
            }
        }

        private void Comm(object obj) //Se lance a chaque connexion d'un client
        {

            try
            {
                ComClient ComC = obj as ComClient; //Pioche dans la class ComClient
                NetworkStream stream = new NetworkStream(ComC.sock2);
                TextReader textR = new StreamReader(stream); //Attente d'une chaine de caractères
                TextWriter textW = new StreamWriter(stream); //Envoie d'une chaine de caractères vers le client
                Console.WriteLine(" >>> Connection from client number: " + ComC.numero); //Affiche la connexion du client et son num
                textW.WriteLine(" >>> Connection in progress... ");
                textW.Flush(); //Nettoyage du buffer et envoie

                for (int i = 0; i < 1; i++) //Waiting Time
                {
                    textW.WriteLine("...");
                    textW.Flush(); //Nettoyage du buffer et envoie
                    System.Threading.Thread.Sleep(1000); //Wait 1 Sec
                }

                textW.WriteLine(" >>> Connection successful, you are the client number: " + ComC.numero);
                textW.Flush(); //Nettoyage du buffer et envoie

                string ipAdr = ComC.sock2.RemoteEndPoint.ToString(); //Adresse IP du client + port
                Console.WriteLine(" >>> Client's ip address: " + ipAdr);

                while (true)
                {
                    string request = textR.ReadLine(); //Attente et lecture message client
                    Console.WriteLine(" >>> Text from client number: " + ComC.numero);
                    Console.WriteLine(request);
                    textW.WriteLine(" >>> Echo: " + request);//Réponse au client
                    textW.Flush(); //Nettoyage du buffer et envoie
                }
            }

            catch
            {
                ComClient ComC = obj as ComClient; //Pioche dans la class ComClient
                Console.WriteLine(" >>> Client disconnected: " + ComC.numero);
                ComC.sock2.Close(); //Fermeture de la socket
                ComC.sock2.Dispose();
            }
        }

        static void Main(string[] args)
        {
            new Program(); //Instance de la classe
        }
    }
}

I’m a beginner with Unity, so I’m not sure what I need to do to make it compatible with Unity. I think I already have to remove the main because there are none in the Unity scripts, and also add “MonoBehavior” with my class, but i’m not sure.

At the moment my server socket just made echo of what is sent :
http://hpics.li/021fd36

I also have a boat which can float on the sea:
http://hpics.li/dfca26f

What I would like at the end is to be able to recover the position of the boat in the water, its axis, etc. And that its data be sent to my socket server
Is it possible? Because I know how to recover the position of the boat, but I do not know how to make my server compatible with Unity, can someone help me ?

Please use code tags!

You can just import the server’s namespace and create a server with “new Program();” from wherever. That should work. Unless you want your server to get sent messages by your Unity game? In that case you just run it in the normal manner as a different program and then write a small thing to send the boat’s coordinates to the same socket as your server is listening to.

Thanks for the answer, i’ll try this.

Ok, i have some news, i modified my code :

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

public class ServerSocket : MonoBehaviour {

    class ComClient //Class interne
    {
        public Socket sock2 { get; set; } //Recupère la socket du client
        public int numero { get; set; } //Et son numéro

        public ComClient(Socket sock, int num)
        {
            this.sock2 = sock;
            this.numero = num;
        }
    }

    private int nClients;

    // Use this for initialization
    void Start () {
     
        Thread th = new Thread(Listen);
        th.Start();
    }

    private void Listen()
    {
        Console.WriteLine(" >>> Setting up the server... ");

        for (int i = 0; i < 3; i++) //Waiting Time
        {
            Console.WriteLine("...");
            System.Threading.Thread.Sleep(1000); //Wait 1 Sec
        }

        Console.WriteLine(" >>> Server is up ! ");
        Debug.Log("Serveur en marche");

        Socket sock1 = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //Création de la socket type IpV4, Point à Point et Protocole Tcp.
        IPEndPoint ipEP = new IPEndPoint(IPAddress.Parse("192.168.20.222"), 2906); //Adresse et port d'écoute
        sock1.Bind(ipEP); //Bind la socket sur le IpEndPoint
        sock1.Listen(1); //Demande d'écoute
        Debug.Log("Socket en écoute");

        while (true) //Boucle infinie
        {
            Debug.Log("Attente de client");
            Socket sock = sock1.Accept(); //Attente et acceptation des clients
            Debug.Log("Client accepté");
            ++nClients;
            ComClient ComC = new ComClient(sock, nClients); //Donne la socket et le numero au client
            Thread th1 = new Thread(Comm); //Thread pour la communication client
            th1.Start(ComC); //Recupère les 2 paramètres grâce a la class ComClient
            Debug.Log("Thread créé pour le client");
        }
        Debug.Log("Sortie boucle");
    }

    private void Comm(object obj) //Se lance a chaque connexion d'un client
    {
        Debug.Log("Avant le try");
        try
        {
            Debug.Log("Entrée dans le try");
            ComClient ComC = obj as ComClient; //Pioche dans la class ComClient
            NetworkStream stream = new NetworkStream(ComC.sock2);
            TextReader textR = new StreamReader(stream); //Attente d'une chaine de caractères
            TextWriter textW = new StreamWriter(stream); //Envoie d'une chaine de caractères vers le client
            Console.WriteLine(" >>> Connection from client number: " + ComC.numero); //Affiche la connexion du client et son num
            textW.WriteLine(" >>> Connection in progress... ");
            textW.Flush(); //Nettoyage du buffer et envoie

            for (int i = 0; i < 1; i++) //Waiting Time
            {
                textW.WriteLine("...");
                textW.Flush(); //Nettoyage du buffer et envoie
                System.Threading.Thread.Sleep(1000); //Wait 1 Sec
            }

            textW.WriteLine(" >>> Connection successful, you are the client number: " + ComC.numero);
            textW.Flush(); //Nettoyage du buffer et envoie

            string ipAdr = ComC.sock2.RemoteEndPoint.ToString(); //Adresse IP du client + port
            Console.WriteLine(" >>> Client's ip address: " + ipAdr);
            Debug.Log("Adresse ip du client récupérée");

            while (true)
            {
                string request = textR.ReadLine(); //Attente et lecture message client
                Console.WriteLine(" >>> Text from client number: " + ComC.numero);
                Console.WriteLine(request);
                textW.WriteLine(" >>> Echo: " + request);//Réponse au client
                textW.Flush(); //Nettoyage du buffer et envoie
            }
        }

        catch
        {
            ComClient ComC = obj as ComClient; //Pioche dans la class ComClient
            Console.WriteLine(" >>> Client disconnected: " + ComC.numero);
            ComC.sock2.Close(); //Fermeture de la socket
            ComC.sock2.Dispose();
            Debug.Log("Client déconnecté");
        }
    }

    // Update is called once per frame
    void Update () {
     
    }

}

I attached it to an empty Gamobject named “Socket server” and i launched my game.

So…

When I connect to telnet, the connection is accepted, I get back into my while, and I go back to the beginning to wait for another client connection, that’s ok.

But my telnet client is empty, I can not type anything in it and it does not show me anything, not even the greeting “You are the client number …”

I get the impression that my thread running a thread does not work (line 58-59)

            Thread th1 = new Thread(Comm); //Thread pour la communication client
            th1.Start(ComC); //Recupère les 2 paramètres grâce a la class ComClient

Can someone explain me what is wrong ?
And also sorry for the tags, i edited this
Thanks :slight_smile:

I ran a quick test against my client implementation and I can connect, send and receive data.
That’s all completely byte[ ]-based though, it’s not using a TextReader to interpret the incoming data as text.

What you wanna make sure is, that - using the StreamReader’s ReadLine() - your incoming data needs to end with a newline \n. Otherwise it won’t trigger as it’s not interpreted as a line. Perhaps that’s already one of the issues that you struggle with.

I’d also like to point out that you shouldn’t create dedicated threads for everything. If you want to avoid blocking, either poll whether an action can complete (pending accept / bytes available to read) or just use the async versions of the methods, since these make use of the threadpool. The only thing to ensure would be that an Unity API access is done on the main thread (if necessary).

Thank you for your reply !

I modified a little my code, it works almost perfectly, the only worries remaining is to close the socket
My code remains locked in this while.

        while (true)
        {
            request = textR.ReadLine(); //Attente et lecture message client
            Console.WriteLine(" >>> Text from client number: " + ComC.numero);
            Console.WriteLine(request);
            textW.WriteLine(" >>> Echo: " + request);//Réponse au client
            textW.Flush(); //Nettoyage du buffer et envoie
        }

To close the socket, I wrote this code just below my while.

        //ComClient ComC = obj as ComClient; //Pioche dans la class ComClient
        Console.WriteLine(" >>> Client disconnected: " + ComC.numero);
        ComC.sock2.Close(); //Fermeture de la socket
        ComC.sock2.Dispose();
        Debug.Log("Client déconnecté");

I tried using a try and catch, but it does not work
So the remaining solution was to put in end condition of my while “If the client is no longer connected”
I tried this:

while (!ComC.sock2.Connected)

But that didnt work

Can you explain me how can i say (“Continue as long as the client is logged in”) ?

Thanks again for the help :3

One thing that may contribute to that is the fact that your threads run in the foreground. Any thread created using the constructor is a foreground-thread by default. There’s a property to change that behaviour, but like mentioned earlier, you should rather not create threads explicitly. Make use of the threadpool whenever you can. I’d highly recommend to just use the async methods.

Now with the above in mind, note that there are basically two situations:

  1. your server disconnects: since you’re currently running foreground threads, they won’t terminate when the parent process is terminated. They’d terminate whenever they’re done. A background thread would terminate with your application.

Either way, you wanna make sure that you disconnect and properly dispose all clients whenever the connection is no longer needed.

  1. your client disconnects: the code above should throw an exception as it’s going to attempt a read/write operation on a no-longer valid connection (most-likely a SocketException).
    You’d automatically break out of the loop if it’s wrapped within a try/catch, unless the operation to fail is wrapped in another try/catch within the loop.

The sole check for “am i connected” won’t work in probably 99,xx% of all cases with your current code. For this to work, your connection needed to terminate just between a successful write&flush operation and the evaluation of the loop’s condition. If it happens to either run into a race condition or terminates after the evaluation, you’ll be left off with an exception from textR.ReadLine() anyway.

It works ! the problem was the

ComC.sock2.Dispose();

i dont understand why, but when i simply comment this, my program works almost perfectly.

I also modified how I create my threads as advised

Thanks a lot :slight_smile: