Partage
  • Partager sur Facebook
  • Partager sur Twitter

Serveur de discussion

C# Socket

    20 juin 2011 à 20:35:14

    Bonjour,

    depuis quelques jours, j'essaye de réaliser un salon de discussion un peu compliqué, avec la possibilité de changer de pseudo/image, avec plusieurs salons disponibles.
    Pour cela j'ai beaucoup cherché sur internet des exemples de code, et je me suis inspiré du très bon article de http://msdn.microsoft.com/en-us/magazine/cc300760.aspx (Pour la suite de mon post, je me suis inspiré en grande partie de la "Figure 7 Asynchronous Server" du lien précédent, mais j'ai également fait la "Figure 5 Simple Threaded Server" en désespoir de cause...).

    Bref, le problème étant que je n'ai jamais utilisé de buffer pour l'envoie des données, et cela me pose problème. En effet il arrive que le serveur passe en "WouldBlock" j'ai donc ajouté ce bout de code que j'ai trouvé sur internet :

    Code C# :

    catch (SocketException exc)
                {
                    if (exc.SocketErrorCode == SocketError.WouldBlock)
                    {
                        Thread.Sleep(30);
                    }
    




    Mais j'ai vraiment pas l'impression que cela aide. De plus, j'ai compris que l'envoie des données se fait en respectant l'algorithme de Nagle. Ce qui fait que si le serveur se fait "spammer", le client peut recevoir des données coupées du type :

    "Phrase1`Phrase2`Phrase3`Phra"

    Alors je ne sais pas du tout si je fait quelque chose de mal. Mais j'ai cru comprendre qu'il fallait utiliser des délimiteurs de "Lignes". Le fait est que du coup les données coté client arrivant en bout de buffer sont intraitables.
    La première solution serait de sauvegarder les données arrivant en fin, et de les juxtaposer avec celles arrivant en début de buffer suivant. Mais j'ai envie d'avoir votre avis avant de faire ça, car j'ai l'impression de faire fausse route.

    Partie traitement des données
    Code C# :

    private void ReceiveCallback(IAsyncResult result)
            {
                ClientConnectionInfo connection = (ClientConnectionInfo)result.AsyncState;
                try
                {
                    int bytesRead = connection.Socket.EndReceive(result);
                    if (bytesRead > 0)
                    {
                        String data = System.Text.Encoding.UTF8.GetString(connection.Buffer).TrimEnd('\0').Trim();
                        String[] allDatas = data.Split('`');
                        foreach (String l in allDatas)
                        {
                            if (l.Split('@').ElementAt(0).Equals("ConnectionAuSalon"))
                            {
                                // Traitement
                            }
                            else if (l.Split('@').ElementAt(0).Equals("DeconnectionDuSalon"))
                            {
                                //Traitement
                            }
                            else if (l.Equals("QuitteApplication"))
                            {
                                // Traitement
                            }
                            else if (l.Equals("ArriveApplication"))
                            {
                         connection.pseudo = generationAleatoirePseudo();                          connection.Socket.Send(Encoding.ASCII.GetBytes("TonPseudo@" + connection.pseudo + '`'), ("TonPseudo@" + connection.pseudo + '`').Length, SocketFlags.None);
                            }
                            else if (l.Split('@').ElementAt(0).Equals("VerifieDisponnibilitePseudo"))
                            {
                                if (isNicknameInUse(l.Split('@').ElementAt(1)))
                                {
                                    connection.Socket.Send(System.Text.ASCIIEncoding.ASCII.GetBytes("NonDisponnible" + '`'), ("NonDisponnible" + '`').Length, SocketFlags.None);
                                }
                                else
                                {
                                    connection.Socket.Send(System.Text.ASCIIEncoding.ASCII.GetBytes("Disponnible" + '`'), ("Disponnible" + '`').Length, SocketFlags.None);
                                }
                            }
                            else if (l.Split('@').ElementAt(0).Equals("Message"))
                            {
                                SendNewMessageToSpecificChat(connection.IDSalon, connection.pseudo, l.Substring(8, l.Length - 8));
                            }
                            else if (l.Split('@').ElementAt(0).Equals("ModificationPseudo"))
                            {
                                modifyClientPseudo(connection, l.Split('@').ElementAt(1));
                                connection.pseudo = l.Split('@').ElementAt(1);
                            }
                            else if (l.Split('@').ElementAt(0).Equals("ModificationImage") || l.Split('@').ElementAt(0).Equals("MonImage"))
                            {
                                modifyClientImage(connection, l.Split('@').ElementAt(1));
                                connection.image = l.Split('@').ElementAt(1);
                            }
                        }
                        connection.Socket.BeginReceive(connection.Buffer, 0, connection.Buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), connection);
                    }
                    else CloseConnection(connection);
                }
                catch (SocketException exc)
                {
                    if (exc.SocketErrorCode == SocketError.WouldBlock)
                    {
                        Thread.Sleep(30);
                        Console.WriteLine("Socket exception: [" + exc.SocketErrorCode + "] has been handled");
                    }
                    else
                    {
                        CloseConnection(connection);
                        Console.WriteLine("Socket exception: " + exc.SocketErrorCode);
                    }
                }
                catch (Exception exc)
                {
                    CloseConnection(connection);
                    Console.WriteLine("Exception: " + exc);
                }
            }
    



    L'essentiel du code du serveur (notamment pour la déclaration du buffer etc, correspond exactement au code de la Figure 7 du lien précédent). Je préfère cependant le rajouter ci-dessous si quelqu'un souhait y avoir accès :

    Code C# :

    public class AsynchronousIoServer
        {
            private Socket _serverSocket;
            private int _port;
            private String _ip;
            
            private class ClientConnectionInfo
            {
                public Socket Socket;
                public byte[] Buffer;
                public String image;
                public String pseudo;
                public String IDSalon = null;
            }
     
            private Thread _acceptThread;
            private List<ClientConnectionInfo> _allClients = new List<ClientConnectionInfo>();
     
            private UC.Chats _adminChat;
            private Dictionary<string, List<ClientConnectionInfo>> _inRoomsClients = new Dictionary<string, List<ClientConnectionInfo>>();
     
            public AsynchronousIoServer(int port, UC.Chats adminChat) { _port = port; _adminChat = adminChat; }
     
            private void SetupServerSocket()
            {
                // Resolving local machine information
                IPHostEntry localMachineInfo = Dns.GetHostEntry(Dns.GetHostName());
                IPEndPoint myEndpoint = new IPEndPoint(localMachineInfo.AddressList[1], _port);
     
                // Create the socket, bind it, and start listening
                _serverSocket = new Socket(myEndpoint.Address.AddressFamily,
                    SocketType.Stream, ProtocolType.Tcp);
                _serverSocket.Bind(myEndpoint);
                _serverSocket.Listen((int)SocketOptionName.MaxConnections);
            }
     
            public void Start()
            {
                SetupServerSocket();
                for (int i = 0; i < 10; i++)
                    _serverSocket.BeginAccept(new AsyncCallback(AcceptCallback), _serverSocket);
            }
     
            private void AcceptCallback(IAsyncResult result)
            {
                ClientConnectionInfo connection = new ClientConnectionInfo();
                try
                {
                    // Finish Accept
                    Socket s = (Socket)result.AsyncState;
                    connection.Socket = s.EndAccept(result);
                    connection.Buffer = new byte[255];
     
                    // Start Receive and a new Accept
                    connection.Socket.BeginReceive(connection.Buffer, 0, connection.Buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), connection);
                    _serverSocket.BeginAccept(new AsyncCallback(AcceptCallback), result.AsyncState);
                }
                catch (SocketException exc)
                {
                    CloseConnection(connection);
                    Console.WriteLine("Socket exception: " + exc.SocketErrorCode);
                }
                catch (Exception exc)
                {
                    CloseConnection(connection);
                    Console.WriteLine("Exception: " + exc);
                }
            }
            private void ReceiveCallback(IAsyncResult result)
            {
                ClientConnectionInfo connection = (ClientConnectionInfo)result.AsyncState;
                try
                {
                    int bytesRead = connection.Socket.EndReceive(result);
                    if (bytesRead > 0)
                    {
                        String data = System.Text.Encoding.UTF8.GetString(connection.Buffer).TrimEnd('\0').Trim();
                        String[] allDatas = data.Split('`');
                        _adminChat.addTextToTab("General", data + "[[[[[[" + bytesRead);
                        foreach (String l in allDatas)
                        {
                            if (l.Split('@').ElementAt(0).Equals("ConnectionAuSalon"))
                            {
                                connection.IDSalon = l.Split('@').ElementAt(1);
                                SendSysMsgToSpecificChat(connection.IDSalon, String.Format("{0:[HH:mm:ss]} ", DateTime.Now) + connection.pseudo + " nous à rejoins !");
                                _adminChat.addTextToTab(connection.IDSalon, String.Format("{0:[HH:mm:ss]} ", DateTime.Now) + connection.pseudo + " nous à rejoins !");
                                addToSpecificChat(connection.IDSalon, connection);
                            }
                            else if (l.Split('@').ElementAt(0).Equals("DeconnectionDuSalon"))
                            {
                                removeFromSpecificChat(connection.IDSalon, connection);
                                SendSysMsgToSpecificChat(connection.IDSalon, String.Format("{0:[HH:mm:ss]} ", DateTime.Now) + connection.pseudo + " est parti(e) !");
                                _adminChat.addTextToTab(connection.IDSalon, String.Format("{0:[HH:mm:ss]} ", DateTime.Now) + connection.pseudo + " est parti(e) !");
                                connection.IDSalon = null;
                            }
                            else if (l.Equals("QuitteApplication"))
                            {
                                _adminChat.addTextToTab("INFORMATION", String.Format("{0:[HH:mm:ss}", DateTime.Now) + "] {" + connection.Socket.RemoteEndPoint + "} (" + connection.pseudo + ") a quitté l'appli");
     
                                removeClientFromAllClients(connection);
                                CloseConnection(connection);
                            }
                            else if (l.Equals("ArriveApplication"))
                            {
                                connection.pseudo = generationAleatoirePseudo();
                                addClientToAllClients(connection);
     
                                connection.Socket.Send(Encoding.ASCII.GetBytes("Pseudo@" + connection.pseudo + '`'), ("Pseudo@" + connection.pseudo + '`').Length, SocketFlags.None);
                                //connection.Socket.Send(System.Text.ASCIIEncoding.ASCII.GetBytes("Pseudo@" + connection.pseudo), ("Pseudo@" + connection.pseudo).Length, SocketFlags.None);
                                sendChatRoomsToClient(connection);
     
                                _adminChat.addTextToTab("INFORMATION", String.Format("{0:[HH:mm:ss}", DateTime.Now) + "] {" + connection.Socket.RemoteEndPoint + "} (" + connection.pseudo + ") a rejoint l'appli");
                            }
                            else if (l.Equals("IFORCEDQUITTED"))
                            {
                                _adminChat.addTextToTab("INFORMATION", String.Format("{0:[HH:mm:ss}", DateTime.Now) + "] {" + connection.Socket.RemoteEndPoint + "} (" + connection.pseudo + ") SUCCESSFULLY FORCED QUIT");
                            }
                            else if (l.Split('@').ElementAt(0).Equals("VerifieDisponnibilitePseudo"))
                            {
                                if (isNicknameInUse(l.Split('@').ElementAt(1)))
                                {
                                    connection.Socket.Send(System.Text.ASCIIEncoding.ASCII.GetBytes("NonDisponnible" + '`'), ("NonDisponnible" + '`').Length, SocketFlags.None);
                                }
                                else
                                {
                                    connection.Socket.Send(System.Text.ASCIIEncoding.ASCII.GetBytes("Disponnible" + '`'), ("Disponnible" + '`').Length, SocketFlags.None);
                                }
                            }
                            else if (l.Split('@').ElementAt(0).Equals("Message"))
                            {
                                _adminChat.addTextToTab(connection.IDSalon, String.Format("{0:[HH:mm:ss}", DateTime.Now) + "] {" + connection.Socket.RemoteEndPoint + "} (" + connection.pseudo + ") a écrit " + l.Substring(8, l.Length - 8));
                                SendNewMessageToSpecificChat(connection.IDSalon, connection.pseudo, l.Substring(8, l.Length - 8));
                            }
                            else if (l.Split('@').ElementAt(0).Equals("ModificationPseudo"))
                            {
                                _adminChat.addTextToTab("INFORMATION", String.Format("{0:[HH:mm:ss}", DateTime.Now) + "] {" + connection.Socket.RemoteEndPoint + "} (" + connection.pseudo + ") a changé son pseudo en " + (l.Split('@').ElementAt(1)));
     
                                modifyClientPseudo(connection, l.Split('@').ElementAt(1));
                                connection.pseudo = l.Split('@').ElementAt(1);
                            }
                            else if (l.Split('@').ElementAt(0).Equals("ModificationImage") || l.Split('@').ElementAt(0).Equals("MonImage"))
                            {
                                modifyClientImage(connection, l.Split('@').ElementAt(1));
                                connection.image = l.Split('@').ElementAt(1);
                            }
                        }
                        connection.Socket.BeginReceive(connection.Buffer, 0, connection.Buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), connection);
                    }
                    else CloseConnection(connection);
                }
                catch (SocketException exc)
                {
                    if (exc.SocketErrorCode == SocketError.WouldBlock)
                    {
                        Thread.Sleep(30);
                        Console.WriteLine("Socket exception: [" + exc.SocketErrorCode + "] has been handled");
                    }
                    else
                    {
                        CloseConnection(connection);
                        Console.WriteLine("Socket exception: " + exc.SocketErrorCode);
                    }
                }
                catch (Exception exc)
                {
                    CloseConnection(connection);
                    Console.WriteLine("Exception: " + exc);
                }
            }
     
            private void CloseConnection(ClientConnectionInfo ci)
            {
                ci.Socket.Close();
                lock (_allClients) _allClients.Remove(ci);
            }
          
            private void addToSpecificChat(String ID, ClientConnectionInfo infos)
            {
                lock (_inRoomsClients) 
                    _inRoomsClients[ID].Add(infos);
     
                lock (_inRoomsClients[ID])
                {
                    foreach (ClientConnectionInfo c in _inRoomsClients[ID].ToArray())
                    {
                        infos.Socket.Send(System.Text.ASCIIEncoding.ASCII.GetBytes("Connection@" + c.pseudo + ";" + c.image + '`'), ("Connection@" + c.pseudo + ";" + c.image + '`').Length, SocketFlags.None);
                    }
                }
                lock (_inRoomsClients[ID])
                {
                    foreach (ClientConnectionInfo c in _inRoomsClients[ID].ToArray())
                    {
                        if (c != infos)
                        {
                            c.Socket.Send(System.Text.ASCIIEncoding.ASCII.GetBytes("Connection@" + infos.pseudo + ";" + infos.image + '`'), ("Connection@" + infos.pseudo + ";" + infos.image + '`').Length, SocketFlags.None);
                        }
                    }
                }
            }
            private void removeFromSpecificChat(String ID, ClientConnectionInfo infos)
            {
                lock (_inRoomsClients)
                    _inRoomsClients[ID].Remove(infos);
     
                lock (_inRoomsClients[ID])
                {
                    foreach (ClientConnectionInfo c in _inRoomsClients[ID].ToArray())
                    {
                        c.Socket.Send(System.Text.ASCIIEncoding.ASCII.GetBytes("Deconnection@" + infos.pseudo + '`'), ("Deconnection@" + infos.pseudo + '`').Length, SocketFlags.None);
                    }
                }
            }
            private void removeClientFromAllClients(ClientConnectionInfo infos)
            {
                lock (_allClients)
                    _allClients.Remove(infos);
     
                lock (_inRoomsClients)
                {
                    foreach (String IDliste in _inRoomsClients.Keys)
                        if (_inRoomsClients[IDliste].Contains(infos))
                        {
                            _inRoomsClients[IDliste].Remove(infos);
                            removeFromSpecificChat(IDliste, infos);
                            //.SendSysMsgToSpecificChat(IDliste, String.Format("{0:[HH:mm:ss]} ", DateTime.Now) + infos.pseudo + " est parti(e) de manière INEDITE !");
                        }
                }
                infos.Socket.Close();
            }
            private void addClientToAllClients(ClientConnectionInfo infos)
            {
                lock (_allClients)
                    _allClients.Add(infos);
            }
            private String generationAleatoirePseudo()
            {
                String pseud = "";
                Boolean isFound = true;
     
                if (_allClients != null)
                {
                    while (isFound)
                    {
                        isFound = false;
                        pseud = "Pseudonyme-" + new Random().Next(1, 1000);
                        lock (_allClients)
                        {
                            foreach (ClientConnectionInfo c in _allClients.ToArray())
                            {
                                if (c.pseudo.Equals(pseud))
                                    isFound = true;
                            }
                        }
                    }
                    return pseud;
                }
                else
                {
                    return "Pseudonyme-" + new Random().Next(1, 1000);
                }
     
            }
            private void sendChatRoomsToClient(ClientConnectionInfo infos)
            {
                lock (_inRoomsClients)
                {
                    foreach (String nom in _inRoomsClients.Keys.ToArray())
                    {
                        /*byte[] buffer = System.Text.Encoding.ASCII.GetBytes("NameOfChatRoom@" + nom);
                        infos.Socket.Send(buffer, buffer.Length, SocketFlags.None);*/
                        infos.Socket.Send(Encoding.ASCII.GetBytes("NameOfChatRoom@" + nom + "`"), ("NameOfChatRoom@" + nom + "`").Length, SocketFlags.None);
                    }
                }
            }
            private Boolean isNicknameInUse(String nickname)
            {
                lock (_allClients)
                {
                    foreach (ClientConnectionInfo c in _allClients.ToArray())
                    {
                        if (c.pseudo.Equals(nickname))
                        {
                            return true;
                        }
                    }
                }
                return false;
            }
            private void SendNewMessageToSpecificChat(String ID, string name, string msg)
            {
                lock (_inRoomsClients[ID])
                    foreach (ClientConnectionInfo c in _inRoomsClients[ID].ToArray())
                    {
                        c.Socket.Send(System.Text.ASCIIEncoding.ASCII.GetBytes(ID + "@Message@[" + String.Format("{0:HH:mm:ss}", DateTime.Now) + "] " + name + " : " + msg + '`'), (ID + "@Message@[" + String.Format("{0:HH:mm:ss}", DateTime.Now) + "] " + name + " : " + msg + '`').Length, SocketFlags.None);
                    }
            }
            private void SendSysMsgToSpecificChat(String ID, String msg)
            {
                if (msg != "")
                {
                    lock (_inRoomsClients[ID])
                    {
                        foreach (ClientConnectionInfo c in _inRoomsClients[ID].ToArray())
                        {
                            c.Socket.Send(System.Text.ASCIIEncoding.ASCII.GetBytes(ID + "@Information@" + msg + '`'), (ID + "@Information@" + msg + '`').Length, SocketFlags.None);
                        }
                    }
                }
            }
            private void modifyClientImage(ClientConnectionInfo infos, String image)
            {
                lock(_allClients)
                    _allClients[_allClients.IndexOf(infos)].image = image;
            }
            private void modifyClientPseudo(ClientConnectionInfo infos, String pseudo)
            {
                lock(_allClients)
                    _allClients[_allClients.IndexOf(infos)].pseudo = pseudo;
            }
    }
    




    Voila pour la plus grosse partie du code.
    J'ai pas vraiment besoin de le signaler, mais c'est vraiment codé très salement.
    Je peux ajouter les parties manquantes si nécessaire ainsi que la partie traitement coté client, mais elle ressemble beaucoup a cette partie.



    Quoi qu'il en soit concrètement la j'ai deux soucis :
    Soit le client reçoit des données incomplète en fin de buffer, donc traite n'importe comment.
    Soit le serveur déclanche une socketException WouldBlock.

    Voila j'espère ne rien avoir oublié, n'hésitez pas à me demander des compléments.
    Toute critique sera appréciée à sa juste valeur, n'hésitez pas, même si cela ne concerne pas les Socket :)
    Merci d'avance à ceux qui sauront me guider.

    EDIT 1 :

    J'ai décidé de réaliser un petit stockage du contenu de la fin du buffer si celui-ci ne contient pas un délimiteur de fin de ligne à la fin.
    Cela règle le soucis coté Client/Serveur du buffer.

    Voila le code (bien que très moche) :

    Code C# :

    ...
                    String endOfBuff = "";
                    while (true)
                    {
                        byte[] buffer = new byte[255];
                        int bytesRead = connection.Socket.Receive(buffer);
                        String data;
                        if (bytesRead > 0)
                        {
                            String[] allDatas;
                            [B]data = endOfBuff;
                            data += System.Text.Encoding.UTF8.GetString(buffer).TrimEnd('\0').Trim();
                            endOfBuff = "";
                            if (!data.EndsWith("`"))
                            {
                                endOfBuff = data.Split('`').ElementAt(data.Split('`').Count() - 1);
                                allDatas = data.Substring(0, data.LastIndexOf('`')).Split('`');
                            }
                            else
                            {
                                allDatas = data.Split('`');
                            }[B]
    
                            foreach (String l in allDatas)
                            {
                                       ....
    



    Cependant, au bout d'un moment (environ après 2 minutes avec 25 clients envoyant des messages chaque seconde chacun), le serveur s'arrête. Et ce sans prévenir ni erreur etc... Rien n'est à signaler ni coté client, ni coté serveur. Bref, je suis vraiment perdu, quelqu'un à une idée, ou des améliorations à proposer ?

    Merci beaucoup, encore une fois, toute aide est appréciée, même minime (S.V.P :$)
    • Partager sur Facebook
    • Partager sur Twitter

    Serveur de discussion

    × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
    × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
    • Editeur
    • Markdown