none
Probleme de Socket C# RRS feed

  • Discussion générale

  • 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 :$)
    lundi 20 juin 2011 18:24

Toutes les réponses

  • Bonjour,

    C'est ce que j'aurais aussi tendance à faire sans être un expert en "socket" cad que comme je vois les choses le principe est de récupérer les données comme le serveur les envoie (éventuellement partiellement) et de les traiter lorsqu'un "message" est complet.

    Pour le blocage c'est généralement soit une exception qui est ignorée soit sans doute ici un problème de lock ? Ajouter peut-être une petite trace pour voir comment l'application progresse et éventuellement si elle reste scotchée sur un "lock".

     


    Please always mark whatever response solved your issue so that the thread is properly marked as "Answered".

    lundi 25 juillet 2011 18:31
    Modérateur