Partage
  • Partager sur Facebook
  • Partager sur Twitter

C# : socket UDP

    26 avril 2011 à 10:53:28

    Bonjour à tous et à toutes !

    Je viens ici pour avoir quelques informations sur un code que je suis en train d'analyser, je ne comprends pas tout, et le principe des sockets est un peu flou pour moi même si je sens que ce n'est pas très compliqué ... :p
    J'ai un code qui fonctionne et qui me convient, mais j'ai besoin de plus de commentaires pour tout comprendre. Si une âme généreuse se sent de rajouter quelques commentaires dans ce code pour expliquer les lignes importantes, je serais ravi :-) Un petit résumé de ce que fait le programme séquence par séquence me convient aussi, c'est comme bon vous semble.

    Voilà les deux classes qui me perturbent, je ne comprends pas grand chose :S :

    public class UDP
        {
            Socket socket;
            Socket sendSocket;
            int port;
    
            private bool stopping = false;
    
            Thread receivingThread;
    
            List<Packet> buffer;
    
            public UDP()
            {
                port = 50000;
                IPEndPoint endpointReceive = new IPEndPoint(IPAddress.Any, port);
                socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                socket.Bind(endpointReceive);
    
                sendSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
    
                IPEndPoint endpointTransmit = new IPEndPoint(IPAddress.Parse("192.168.0.255"), port);
                sendSocket.Connect(endpointTransmit);
                
    
                buffer = new List<Packet>();
    
                receivingThread = new Thread(new ThreadStart(receiving));
                receivingThread.Name = "UDP receiving thread";
                receivingThread.Start();
    
               // Console.WriteLine("UDP opgestart");
            }
    
            ~UDP()
            {
                socket.Close();
                sendSocket.Close();
                receivingThread.Abort();
            }
    
    
            public void send(Packet package)
            {
                sendSocket.Send((byte[])System.Text.ASCIIEncoding.ASCII.GetBytes(package.toRaw().ToCharArray()));
            }
    
            private void receiving()
            {
                while (!stopping)
                {
                    byte[] data = new byte[1024];
                    int i = socket.Receive(data);
    
                    string temp = data.ToString();
                    Packet package = Packet.parse(data);
                    if (package != null)
                    {
    
                        buffer.Add(package);
    
                      }
                    //Thread.Sleep(1);
                }
            }
    
            public Packet receive()
            {
                if (buffer.Count > 0)
                {
                    Packet firstItem = buffer.ElementAt(0);
                    buffer.RemoveAt(0);
                    return firstItem;
                }
                return null;
            }
    
            public void stopUDP()
            {
                stopping = true;
            }
    
    
        }
    


    Et la seconde classe :

    public class Packet
        {
            public int senderID { get; set; }
            public int receiverID { get; set; }
            public string data { get; set; }
            public string command { get; set; }
            public static char separator = (char)02;
    
            public Packet(int from, int to, string command, string data)
            {
                this.senderID = from;
                this.receiverID = to;
                this.command = command;
                this.data = data;
            }
    
            public string toRaw(){
                
                return String.Format("{1}{0}{2}{0}{3}{0}{4}", separator, senderID, receiverID, command, data);
            }
    
            public static Packet parse(byte[] rawBytes)
            {
                
                //possibly buggy needed to change getstring...
                String raw = System.Text.ASCIIEncoding.ASCII.GetString(rawBytes, 0, rawBytes.GetLength(0));
                try
                {
                    String[] rawBlocks = raw.Split(separator);
                    Packet package = new Packet(int.Parse(rawBlocks[0]), int.Parse(rawBlocks[1]), rawBlocks[2], rawBlocks[3]);
                    return package;
                }
                catch
                {
                    return null;
                }
            }
    
            public override string ToString()
            {
                return String.Format("From: {0} To: {1} Command: {2} Data: {3}", senderID, receiverID, command, data);
            }
    
        }
    



    Merci d'avance pour vos conseils, et votre patience.

    Bonne journée à tous les lecteurs du site !

    Brygoth
    • Partager sur Facebook
    • Partager sur Twitter
      26 avril 2011 à 12:16:17

      Et si tu commençais par consulter la doc pour chacune des classes et méthodes utilisées ? :)
      Il suffit de placer le curseur sur l'élément qui t'intéresse, et d'appuyer sur F1.
      • Partager sur Facebook
      • Partager sur Twitter
        26 avril 2011 à 12:42:10

        //Cette classe semble juste être une encapsulation de Socket pour gérer plus facilement les transmissions UDP.
        public class UDP
            {
                //Déclaration des variables d'instance.
                Socket socket;
                Socket sendSocket;
                int port;
                private bool stopping = false;
                Thread receivingThread;
                List<Packet> buffer;
        
                //Constructeur
                public UDP()
                {
                    port = 50000;//En prévision d'une écoute sur le port 50000 surement.
                    IPEndPoint endpointReceive = new IPEndPoint(IPAddress.Any, port);//Crée un nouveau point d'écoute. Any indique que la socket va écouter n'importe quelle interface réseau sur le port indiqué.
                    socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);//Initialise une socket en IPv4, UDP.
                    socket.Bind(endpointReceive);//"Verrouille" l'écoute des communications entrantes sur les interfaces sur le port 50000 en UDP. Normalement, tout autre programme tentant d'écouter avec ces paramètres sur la même machine se fera refouler car aucun paramètre de partage d'écoute n'a été configuré.
        
                    sendSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);//Initialise une autre socket UDP IPv4
        
                    IPEndPoint endpointTransmit = new IPEndPoint(IPAddress.Parse("192.168.0.255"), port);//Configure le point de connexion. Cette socket enverra ses données en unicast vers l'adresse IP indiquée ici, sur le port 500000.
                    sendSocket.Connect(endpointTransmit);//On "établie" la connexion UDP. "établir" est un peu faux car UDP n'est pas un protocole orienté connexion comme le TCP.
        
                    //Toutes les configurations précédentes permettent de réaliser un client et un serveur communicant en UDP.
                    
        
                    buffer = new List<Packet>();
        
                    //Initialise puis démarre un thread permettant de lire en continu ce qui vient sur la socket serveur, sans bloquer le thread appelant.
                    receivingThread = new Thread(new ThreadStart(receiving));
                    receivingThread.Name = "UDP receiving thread";
                    receivingThread.Start();
        
                   // Console.WriteLine("UDP opgestart");
                }
        
                //Destructeur, comme en C++. Je sais pas d'où est tiré cette source...Mais utiliser le destructeur n'est jamais recommandé en .NET car on ne sait pas réellement à quel moment le garbage collector .NET va passer ici. Il se pourrait que, si tu instancie cette classe sans rien en faire et plein de fois, tu aies des exceptions car le destructeur des instances non-utilisées ne sera pas encore appelé. Implémenter l'interface IDisposable quand tu as réellement besoin de libérer des ressources. Ensuite, tu peux utiliser le mot clé using(UDP u = new UDP()) {} pour travailler avec un objet UDP dont la méthode Dispose sera appelée à la fin du bloc using.
                ~UDP()
                {
                    socket.Close();//Ferme la socket d'écoute
                    sendSocket.Close();//Ferme la socket d'envoit
                    receivingThread.Abort();//Là encore, énorme bourde de la part du concepteur de la source. La méthode Abort arrête le thread, quelque soit son état et quelque soit l'endroit où il se trouve. Ca peut générer des corruptions de données ou laisser des objets dans un état inconsistant.
                }
        
                //Principe d'encapsulation : Il a créé une méthode Send pour envoyer des trucs contenu dans Packet sur le réseau. Il convertit son objet Packet en une chaine de caractère qu'il transforme en tableau d'octet en utilisant l'encodage de texte ASCII. Façon de faire un peu particulière je trouve...Il aurait pu directement sérialiser son objet (capturer le contenu d'un objet à un instant T pour en retourner une trace en tableau d'octet. La désérialisation permet de récupérer l'objet en partant de la trace).
                public void send(Packet package)
                {
                    sendSocket.Send((byte[])System.Text.ASCIIEncoding.ASCII.GetBytes(package.toRaw().ToCharArray()));
                }
        
                //Méthode appelée par le thread de lecture des données
                private void receiving()
                {
                    while (!stopping)//Tant qu'aucune requête d'arrêt du thread n'est demandée, on boucle.
                    {
                        byte[] data = new byte[1024];//Initialise un buffer local pour récupérer les données
                        int i = socket.Receive(data);//Récupère des données depuis le réseau. Si aucune donnée n'est présente, cette méthode bloque le thread jusqu'à ce que des données soient disponibles dans le buffer de ta carte réseau.
        
                        string temp = data.ToString();//Il effectue une désérialisation maison en convertissant les octets reçus en chaine de caractère, avant de transformer cette chaine en objet Packet et de le placer dans la liste des paquets reçus. Il n'a pas utilisé le mot clé lock sur sa liste => Attention aux accès concurrentiel!!
                        Packet package = Packet.parse(data);
                        if (package != null)
                        {
        
                            buffer.Add(package);
        
                          }
                        //Thread.Sleep(1);
                    }
                }
        
                //Retourne le premier élément de données disponibles. Il aime se prendre la tête pour rien ^^. Il reproduit le comportement d'une file avec une liste. Ce code aurait été beaucoup plus simple en utilisant Queue<Packet> à la place de List<Packet>.
                public Packet receive()
                {
                    if (buffer.Count > 0)
                    {
                        Packet firstItem = buffer.ElementAt(0);
                        buffer.RemoveAt(0);
                        return firstItem;
                    }
                    return null;
                }
        
                //Permet de lancer une requête d'arrêt du thread. Problème : Si le thread est bloqué sur la méthode Receive de la socket-server, ce code n'aura aucun effet tant que la socket serveur n'aura pas reçu de données. Pour contourner ça, il y a la méthode Abort...Mais comme dit plus haut, à utiliser avec parcimonie!!! Par exemple, plutôt que d'utiliser Abort directement, on peut utiliser la méthode receivingThread.Join(2000); . Ceci va bloquer le thread appelant pendant un maximum de 2 secondes. Si le receivingThread se termine avant ce délai, c'est tout bon :D. Si receivingThread ne se finit pas avant ce délai, on aura une timeout quelque part...Et ce n'est qu'a ce moment là qu'on pourra appeler la méthode Abort car on saura que de toutes manières, receivingThread est bloqué. Tout ça...Pour éviter que le thread ne soit arrêté n'importe quand et que le programme fasse n'importe quoi.
                public void stopUDP()
                {
                    stopping = true;
                }
        
        
            }
        


        Je ne commenterais pas Packet parce que c'est simplement une classe contenant des informations sur les données qui transitent sur le réseau.

        Après, comme l'a dit Orwell, n'hésite pas à utiliser F1 quand tu veux de la doc sur une méthode, une classe ou autre chose...
        • Partager sur Facebook
        • Partager sur Twitter
          26 avril 2011 à 14:10:08

          Citation : Brygoth (26/04/2011)

          Merci pour les infos, je comprends un peu mieux tout ça, je vais me plonger là dedans et tenter de faire ça bien ! :p
          Merci bien, les commentaires sont très clairs, et bonne journée !

          Brygoth



          Edit 27/04/2011 :


          Place aux questions :)

          Je suis en train de reprendre (et modifier) le code. J'aimerai avant de pouvoir utiliser un truc complexe, créer une connexion qui fonctionne, et la plus simple possible. C'est à dire, déclarer les sockets, lire et afficher ce que m'envoie le client (en console au départ, la transition n'est pas compliquée pour le WFA). J'ai ce code, il me paraît simple, et compile, cependant j'ai une erreur persiste (socketexeption) :

          Citation : VS2008

          Une tentative d’accès à un socket de manière interdite par ses autorisations d’accès a été tentée



          using System;
          using System.Net;
          using System.Net.Sockets;
          using System.Text;
          
          class MainClass
          {
              public static void Main()
              {
                  int receivedDataLength;
                  byte[] data = new byte[1024];
                  IPEndPoint ip = new IPEndPoint(IPAddress.Parse("192.168.0.101"), 5000);
          
                  Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
          
                  socket.Bind(ip);
          
                  IPEndPoint sender = new IPEndPoint(IPAddress.Parse("192.168.0.101"), 5000);
                  EndPoint Remote = (EndPoint)(sender);
          
                  while (true)
                  {
                      data = new byte[1024];
                      receivedDataLength = socket.ReceiveFrom(data, ref Remote);
          
                      Console.WriteLine(Encoding.ASCII.GetString(data, 0, receivedDataLength));
                      socket.SendTo(data, receivedDataLength, SocketFlags.None, Remote);
                  }
          
              }
          }
          



          D'où peut provenir ce problème ? (vous l'aurez compris, je début dans le C#, et tout ça ne me paraît pas si simple. D'ailleurs, s'il existe des tutos sur le net correspondant exactement à ce type de connexion, je suis preneur, je ne trouve pas grand chose ... ^_^)

          Merci pour votre aide en tout cas :)
          • Partager sur Facebook
          • Partager sur Twitter
            27 avril 2011 à 14:21:04

            Up (après MAJ du dernier message)
            • Partager sur Facebook
            • Partager sur Twitter
              27 avril 2011 à 14:39:52

              Ton erreur est en relation avec "Normalement, tout autre programme tentant d'écouter avec ces paramètres sur la même machine se fera refouler car aucun paramètre de partage d'écoute n'a été configuré.". Ca va même plus loin que ce que je pensais : Quand tu bind une interface réseau, non seulement, tu ne peux plus ré-exécuter un bind pour écoute...Mais tu ne peux également pas réutiliser cette interface pour envoyer des données. Il y a surement de quoi configurer le partage d'interface en mode écoute ou en mode réception (pour expliquer le fait que sur un Windows 2008 R2, un serveur IIS qui écoute sur le port 80 n'empêche pas la navigation web. Le port aura été bindé et partagé en envoit mais non-partagé en écoute.)

              Malheureusement, il n'y a pas grand chose qui a été simplifié pour configurer les sockets. Tu dois passer par une des surcharges de Socket.SetSocketOption.
              • Partager sur Facebook
              • Partager sur Twitter
                27 avril 2011 à 15:23:54

                En remplaçant le bind par ...

                socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ExclusiveAddressUse, true);
                


                ... je continue jusqu'à tomber sur une nouvelle erreur, à la ligne ...

                receivedDataLength = socket.ReceiveFrom(data, ref Remote);
                


                ... dans la boucle while. Cette fonction ne doit être utilisé qu'après avoir utilisé bind :

                Citation : VS2008

                Vous devez appeler la méthode Bind avant d'effectuer cette opération.



                Peut être que je n'ai pas utilisé la bonne socketOption. Je suis un peu perdu ^_^ Merci :)

                Seconde question : d'où vient le fait que la méthode Bind fonctionne dans la première source, et dans ce code, elle empêche le bon déroulement de la compilation ?
                • Partager sur Facebook
                • Partager sur Twitter
                  27 avril 2011 à 23:04:54

                  Les options de socket permettent juste de modifier le comportement de la pile réseau du système d'exploitation pour les utilisations ultérieures. Autrement dit, c'est un "addon"...Si tu veux faire une socket d'écoute, tu dois quand même appeler "Bind" et si tu veux faire une socket d’envoi, tu dois quand même utiliser Connect, après avoir modifié les paramètres de la pile réseau avec SetSocketOption, bien sur :D

                  Citation : Brygoth

                  Seconde question : d'où vient le fait que la méthode Bind fonctionne dans la première source, et dans ce code, elle empêche le bon déroulement de la compilation ?


                  La différence entre la première et la 2eme version, c'est l'IP de l'interface qui écoute les connexions entrantes.
                  Dans le premier cas, tu spécifie "Any" et la pile réseau est peut-être configurée par défaut pour que, en cas d'écoute sur n'importe quelle interface, l'interface sélectionnée par le système ne soit pas verrouillée en connexion sortante.
                  Dans le second cas, tu indique l'IP de l'interface....Tu forces le système à utiliser une seule interface pour écouter les connexions entrantes et peut-être que la pile réseau système est configurée pour que, par défaut, une écoute sur interface forcée soit bloquée en sortant comme en entrant sur le port indiqué.

                  Il y aura peut-être des questions du type "Mais je capte pas : Quand je fais une écoute sur le port X et que je lance une autre application utilisant le même port X, ça marche...Pourquoi là pas?"....Ce pourrait être à cause de la segmentation des processus, liée au système d'exploitation encore une fois. Peut-être la pile réseau permet-elle de faire une configuration de partage aussi bien à l'échelle du processus qu'à l'échelle du système entier. Du coup, au sein d'un même processus, deux sockets ne pourront pas fonctionner comme on le voudrait....Alors qu'avec deux processus différents, cela fonctionnerait à merveille.

                  Je te dis tout ça...Mais ne le prend pas comme vérité première. Je ne fais que des spéculations issues d'expériences tirées de tests multiples et variés sur les systèmes Windows. Je ne sais pas précisément comment fonctionne la pile réseau Windows.
                  • Partager sur Facebook
                  • Partager sur Twitter
                    28 avril 2011 à 13:45:23

                    Si je remplace le "192.168.0.101" de l'interface qui écoute les connexions entrante par "Any", une erreur se produit aussi : "Une seule utilisation de chaque adresse de socket (protocole/adresse réseau/port) est habituellement autorisée".

                    public UDP()
                            {
                                port = 50000;
                                IPEndPoint endpointReceive = new IPEndPoint(IPAddress.Any, port);
                                socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                                socket.Bind(endpointReceive); // Bug ici ...
                    
                                sendSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                    
                                IPEndPoint endpointTransmit = new IPEndPoint(IPAddress.Parse("192.168.0.255"), port);
                                sendSocket.Connect(endpointTransmit);            
                    
                                buffer = new List<Packet>();
                    
                                receivingThread = new Thread(new ThreadStart(receiving));
                                receivingThread.Name = "UDP receiving thread";
                                receivingThread.Start();
                    
                             }
                    


                    Et c'est à ce moment là que je ne comprends pas trop ^_^ Il doit me manquer un détail pour pouvoir passer cette ligne sans générer d'erreur ... Une idée ?

                    Merci beaucoup pour tes réponses et ta patience en tout cas, j'en apprends tous les jours, et je sens qu'à force je vais y arriver (y'a pas de raison ! même si je galère :p)

                    Merci
                    • Partager sur Facebook
                    • Partager sur Twitter
                      2 mai 2011 à 12:53:18

                      Up (après MAJ du dernier message)
                      • Partager sur Facebook
                      • Partager sur Twitter

                      C# : socket UDP

                      × 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