none
c# - TCP/IP multiple client not multi threaded RRS feed

  • Question

  • Hello,

    I programmed a server applicatio nand a client application. Everything works fine, I can send messages from the client, receive them on the server and send other messages back to the client.

    But here is what I'm looking for :

    The whole application will be some sort of a game. Each game has multiple players (12 in total) and I'd like to know how can the server "keep" the connections of the different clients for a new game. So during the whole game the server will either send messages to all clients at once or to only one of them depending on the received messages.
    At the end of the game, the server removes all the clients then a new game can start.

    And I don't know how to do that. My actual server gets one message from a client, whoever the client is, eventually sends back a message then closes the connection. If I keep the connection, other clients can't send messages.

    Here's the code of the server :

    /*
     * Programme serveur réalisé par StaliiX pour le jeu "Robert Spreading"
     * 
     * 
     * 
     * 
     * Points à réaliser : 
     * -Adapter pour le jeu
     * -Etablir les différentes requêtes client
     * -Trouver le moyen de rendre accessible le serveur via l'IP publique (si ce n'est pas déjà le cas, à tester sur un réseau domestique)
     * 
     * 
     */
    
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Net.Sockets;
    using System.Net;
    using System.Threading;
    using System.IO;
    
    namespace ServeurRobert
    {
        class Program
        {
            const int PORT_NO = 5000;
            static string logPath = "..\\..\\..\\Resources\\Logs.txt";
            static StreamWriter Log = File.AppendText(logPath);
    
            static void Main(string[] args)
            {
                //---On écoute les éventuelles connexions sur le port & l'IP---
                TcpListener listener = new TcpListener(IPAddress.Any, PORT_NO);
                listener.Start();
                Console.WriteLine("Serveur initialise\n");
    
                while (true)
                {
                    try
                    {
                        //---Un client se connecte---
                        TcpClient client = listener.AcceptTcpClient();
    
                        //---On récupère les données arrivantes à travers un flux---
                        NetworkStream nwStream = client.GetStream();
                        byte[] buffer = new byte[client.ReceiveBufferSize];
    
                        //---On lit les données du flux---
                        int bytesRead = nwStream.Read(buffer, 0, client.ReceiveBufferSize);
    
                        //---On converti les données reçues en une chaîne de caractères---
                        string dataReceived = Encoding.ASCII.GetString(buffer, 0, bytesRead);
                        Console.WriteLine("\nDonnees recues : " + dataReceived);
    
                        string[] DonneesRecup = traitementDonnee(dataReceived);
                        Console.WriteLine("Donnee a traiter : " + DonneesRecup[0]);
    
                        if (DonneesRecup[0] == "STOP")
                        {
                            traitementMessage(DonneesRecup[0], nwStream, client, DonneesRecup[1]);
                            break;
                        }
                        traitementMessage(DonneesRecup[0], nwStream, client, DonneesRecup[1]);
                    }
                    catch(InvalidCastException e)
                    {
                        Console.WriteLine(e.Message);
                        Log.WriteLine("Erreur du serveur : " + e.Message + " le " + DateTime.Now);
                        break;
                    }
                    catch(Exception exception)
                    {
                        Console.WriteLine(exception.Message);
                        Log.WriteLine("Erreur du serveur : " + exception.Message + " le " + DateTime.Now);
                        break;
                    }
                }
                listener.Stop();
                Console.WriteLine("Arret du serveur dans 3 secondes");
                Thread.Sleep(3000);
                Log.Close();                                            // NE PAS OUBLIER D'ETEINDRE CORRECTEMENT LE SERVEUR (AVEC LA COMMANDE STOP) SINON LES LOGS NE SONT PAS SAUVEGARDES
                Environment.Exit(0);
            }
    
            static void traitementMessage(String donneeRecue, NetworkStream flux, TcpClient client, String IPClient)
            {
                //---On renvoie les données au client en fonction de ce qu'on a reçu du client ---
                if (donneeRecue == "Attaque")
                    renvoiMessage("Vous avez effectue une attaque", flux, donneeRecue, IPClient);
    
                else if (donneeRecue == "Defense")
                    renvoiMessage("Vous avez effectue une defense", flux, donneeRecue, IPClient);
    
                else if (donneeRecue == "STOP")
                    renvoiMessage("ArretServ", flux, donneeRecue, IPClient);
    
                else if (donneeRecue == "Connexion")
                    renvoiMessage("ConnServ", flux, donneeRecue, IPClient);
    
                else if (donneeRecue == "EraseLocalLogs")
                    renvoiMessage("ErLoLog", flux, donneeRecue, IPClient);
    
                else
                    renvoiMessage("Aucune action ne correspond a ce message", flux, donneeRecue, IPClient);
                
                client.Close();
            }
    
            static void renvoiMessage(String donneeAEnvoyer, NetworkStream flux, String donneeRecue, string IPClient)
            { //--- Envoi de la réponse au client + affichage de ce qui est renvoyé ---
                Console.WriteLine("Renvoi : " + donneeAEnvoyer);
                Log.WriteLine("Le serveur a reçu : " + donneeRecue + " --//-- et a répondu : " + donneeAEnvoyer + " le " + DateTime.Now + " par l'IP : " + IPClient);
                flux.Write(ASCIIEncoding.ASCII.GetBytes(donneeAEnvoyer), 0, donneeAEnvoyer.Length);
            }
    
            static String[] traitementDonnee(String Data)
            { //--- Transformation de la chaîne reçue par le client pour ne récupérer qu'une chaine contenant les données utiles dont on enlève les espaces et les sauts de ligne---
                String[] tabData = Data.Split("/");
                tabData[0] = tabData[0].Replace(" ", "").Replace(Environment.NewLine, "");
                return tabData;
            }
        }
    }

    Thank you in advance.

    Friday, November 8, 2019 2:40 PM

All replies

  • You can refer to any reference architecture for TcpListener and get the sample code on how to do this. But the basic gist is that each time you get a connection (TcpClient) you need to move the handling of that client to a separate thread. Then you can accept any # of simultaneous connections (up to thread limits). Each thread will need to keep running (e.g. infinite loop) until either the client drops (in which case an exception is thrown) or you get a "termination" message from the client. Once this happens you can clean up the client and close the thread.

    If you need to be able to manage the clients that are connected then part of the connection logic needs to include storing metadata associated with the client. At a minimum you'll likely capture the client IP and thread it is running on along with the actual client. A simple concurrency list should be sufficient. When clients drop off you'll need to remove the entry from the list as well.

    To send messages to everyone then enumerate the list and send to each one. For a single client you already have a dedicated thread so it will send-receive messages from its client automatically.

    If you plan to have 100s or 1000s of clients connected at once then creating threads for each one is no longer sufficient. Instead you'll need to create a thread pool (not the Thread Pool) from which you can process requests. As clients come in you'll add them to a master concurrent list of clients. As you need to interact with that client (either sending or receiving) the code will need to request a thread from your pool, do the client work on that thread and then return the thread to the pool when done. This requires more work but allows your server to scale up to the limits of the thread pool you define. This code can also be seen in various examples provided online. It is too much to post in a forum thread.


    Michael Taylor http://www.michaeltaylorp3.net

    Friday, November 8, 2019 6:23 PM
    Moderator
  • You are not using any asynchronous methods, such as TcpClient.ConnectAsync and NetworkStream.ReadAsync, correct?

    It would be reasonable to have a separate thread for each of 12 connections, especially if you use a thread pool as previously described.

    Another possible solution I think would be to use asynchronous methods with a list of connections with related status information for each connection. I do not know the design of your game but you could have a work queue for specific things to be done that requires interaction between or among connections and threads for those specific tasks. You might also need a thread pool if there is a significant amount of that and/or they require waiting.

    So the design of the game might be complicated but I think that the low-level communication level can be done relatively easily using asynchronous methods.



    Sam Hobbs
    SimpleSamples.Info

    Saturday, November 9, 2019 6:36 PM