Probleme de Socket C#

Dropsys Messages postés 2 Date d'inscription lundi 20 juin 2011 Statut Membre Dernière intervention 21 juin 2011 - 20 juin 2011 à 20:22
Dropsys Messages postés 2 Date d'inscription lundi 20 juin 2011 Statut Membre Dernière intervention 21 juin 2011 - 21 juin 2011 à 11:46
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 :

            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
        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 :

    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) :

                ...
                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 :$)

2 réponses

cs_louis14 Messages postés 793 Date d'inscription mardi 8 juillet 2003 Statut Membre Dernière intervention 10 février 2021 8
21 juin 2011 à 10:03
Bonjour,
je n'ai pas trop regardé ton code, mais une lecture de cet excellent article pourra surement t'aider :
http://www.codeproject.com/KB/IP/TCPServClntCommRMIFrmwrk.aspx

Bon codage


louis
0
Dropsys Messages postés 2 Date d'inscription lundi 20 juin 2011 Statut Membre Dernière intervention 21 juin 2011
21 juin 2011 à 11:46
Bonjour,

Merci beaucoup de ta réponse,

J'ai en effet déjà lu en grande partie cet article lors de mes précédentes versions, je vais cependant le relire afin d'essayer de trouver des améliorations à apporter.

Merci encore,
Dropsys.
0
Rejoignez-nous