C# Language Exemple de socket asynchrone (client / serveur).


Exemple

Exemple côté serveur

Créer un écouteur pour le serveur

Commencez par créer un serveur qui gérera les clients qui se connectent et les requêtes qui seront envoyées. Donc, créez une classe d'écoute qui gérera cela.

class Listener
{
    public Socket ListenerSocket; //This is the socket that will listen to any incoming connections
    public short Port = 1234; // on this port we will listen

    public Listener()
    {
        ListenerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    }
 }

Nous devons d'abord initialiser le socket Listener sur lequel nous pouvons écouter toutes les connexions. Nous allons utiliser un socket TCP, c'est pourquoi nous utilisons SocketType.Stream. Aussi, nous spécifions à quel port le serveur doit écouter

Ensuite, nous commençons à écouter toutes les connexions entrantes.

Les méthodes d'arborescence que nous utilisons ici sont:

  1. ListenerSocket.Bind ();

    Cette méthode lie le socket à un IPEndPoint . Cette classe contient les informations d'hôte et de port local ou distant nécessaires à une application pour se connecter à un service sur un hôte.

  2. ListenerSocket.Listen (10);

    Le paramètre backlog spécifie le nombre de connexions entrantes pouvant être mises en file d'attente pour acceptation.

  3. ListenerSocket.BeginAccept ();

    Le serveur commence à écouter les connexions entrantes et continue avec une autre logique. Lorsqu'il y a une connexion, le serveur revient à cette méthode et exécute la méthode AcceptCallBack

    public void StartListening()
    {
        try
        {                
                MessageBox.Show($"Listening started port:{Port} protocol type: {ProtocolType.Tcp}");                    
                ListenerSocket.Bind(new IPEndPoint(IPAddress.Any, Port));
                ListenerSocket.Listen(10);
                ListenerSocket.BeginAccept(AcceptCallback, ListenerSocket);                
        }
        catch(Exception ex)
        {
            throw new Exception("listening error" + ex);
        }
    }

Ainsi, lorsqu'un client se connecte, nous pouvons les accepter par cette méthode:

Trois méthodes que nous utilisons ici sont:

  1. ListenerSocket.EndAccept ()

    Nous avons commencé le rappel avec Listener.BeginAccept() maintenant nous devons terminer cet appel. The EndAccept() méthode The EndAccept() accepte un paramètre IAsyncResult, cela stockera l'état de la méthode asynchrone. À partir de cet état, nous pouvons extraire le socket d'où provient la connexion entrante.

  2. ClientController.AddClient()

    Avec le socket obtenu à partir d' EndAccept() nous créons un client avec une méthode propre (code ClientController sous exemple de serveur) .

  3. ListenerSocket.BeginAccept ()

    Nous devons recommencer à écouter lorsque le socket est terminé en gérant la nouvelle connexion. Transmettez la méthode qui interceptera ce rappel. Et aussi passer dans le socket Listener afin que nous puissions réutiliser ce socket pour les prochaines connexions.

    public void AcceptCallback(IAsyncResult ar)
    {
        try
        {
            Console.WriteLine($"Accept CallBack port:{Port} protocol type: {ProtocolType.Tcp}");
            Socket acceptedSocket = ListenerSocket.EndAccept(ar);               
            ClientController.AddClient(acceptedSocket);

            ListenerSocket.BeginAccept(AcceptCallback, ListenerSocket);
        }
        catch (Exception ex)
        {
            throw new Exception("Base Accept error"+ ex);
        }
    }

Maintenant, nous avons un socket d'écoute, mais comment recevons-nous les données envoyées par le client, comme le montre le code suivant.

Créer un récepteur de serveur pour chaque client

Tout d'abord créer une classe de réception avec un constructeur qui prend en paramètre Socket:

    public class ReceivePacket
    {
        private byte[] _buffer;
        private Socket _receiveSocket;

        public ReceivePacket(Socket receiveSocket)
        {
           _receiveSocket = receiveSocket;
        }
    }

Dans la méthode suivante, nous commençons par donner au tampon une taille de 4 octets (Int32) ou le paquet contient des parties {longueur, données réelles}. Donc, les 4 premiers octets, nous réservons pour la longueur des données le reste pour les données réelles.

Ensuite, nous utilisons la méthode BeginReceive () . Cette méthode est utilisée pour commencer à recevoir des clients connectés et lorsqu'elle recevra des données, elle exécutera la fonction ReceiveCallback .

    public void StartReceiving()
    {
        try
        {
            _buffer = new byte[4];
            _receiveSocket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, ReceiveCallback, null);
        }
        catch {}
    }

    private void ReceiveCallback(IAsyncResult AR)
    {
        try
        {
            // if bytes are less than 1 takes place when a client disconnect from the server.
            // So we run the Disconnect function on the current client
            if (_receiveSocket.EndReceive(AR) > 1)
            {
                // Convert the first 4 bytes (int 32) that we received and convert it to an Int32 (this is the size for the coming data).
                _buffer = new byte[BitConverter.ToInt32(_buffer, 0)];  
                // Next receive this data into the buffer with size that we did receive before
                _receiveSocket.Receive(_buffer, _buffer.Length, SocketFlags.None); 
                // When we received everything its onto you to convert it into the data that you've send.
                // For example string, int etc... in this example I only use the implementation for sending and receiving a string.

                // Convert the bytes to string and output it in a message box
                string data = Encoding.Default.GetString(_buffer);
                MessageBox.Show(data);
                // Now we have to start all over again with waiting for a data to come from the socket.
                StartReceiving();
            }
            else
            {
                Disconnect();
            }
        }
        catch
        {
            // if exeption is throw check if socket is connected because than you can startreive again else Dissconect
            if (!_receiveSocket.Connected)
            {
                Disconnect();
            }
            else
            {
                StartReceiving();
            }
        }
    }

    private void Disconnect()
    {
        // Close connection
        _receiveSocket.Disconnect(true);
        // Next line only apply for the server side receive
        ClientController.RemoveClient(_clientId);
        // Next line only apply on the Client Side receive
        Here you want to run the method TryToConnect()
    }

Nous avons donc configuré un serveur capable de recevoir et d'écouter les connexions entrantes. Lorsqu'un client se connecte, il sera ajouté à une liste de clients et chaque client a sa propre classe de réception. Pour que le serveur écoute:

Listener listener = new Listener();
listener.StartListening();

Quelques classes que j'utilise dans cet exemple

    class Client
    {
        public Socket _socket { get; set; }
        public ReceivePacket Receive { get; set; }
        public int Id { get; set; }

        public Client(Socket socket, int id)
        {
            Receive = new ReceivePacket(socket, id);
            Receive.StartReceiving();
            _socket = socket;
            Id = id;
        }
    }

     static class ClientController
     {
          public static List<Client> Clients = new List<Client>();

          public static void AddClient(Socket socket)
          {
              Clients.Add(new Client(socket,Clients.Count));
          }

          public static void RemoveClient(int id)
          {
              Clients.RemoveAt(Clients.FindIndex(x => x.Id == id));
          }
      }

Exemple côté client

Connexion au serveur

Tout d'abord, nous voulons créer une classe qui se connecte au serveur avec le nom que nous lui donnons: Connecteur:

class Connector
{
    private Socket _connectingSocket;
}

La méthode suivante pour cette classe est TryToConnect ()

Cette méthode suscite quelques intérêts:

  1. Créer le socket;

  2. Ensuite je boucle jusqu'à ce que le socket soit connecté

  3. Chaque boucle, il ne fait que tenir le fil pendant 1 seconde, nous ne voulons pas DOS le serveur XD

  4. Avec Connect (), il essaiera de se connecter au serveur. Si cela échoue, une exception sera lancée, mais le serveur gardera le programme connecté au serveur. Vous pouvez utiliser une méthode Connect CallBack pour cela, mais je vais simplement appeler une méthode lorsque le socket est connecté.

  5. Notez que le client essaie maintenant de se connecter à votre PC local sur le port 1234.

     public void TryToConnect()
     {
         _connectingSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
         
          while (!_connectingSocket.Connected)
          {
              Thread.Sleep(1000);
    
              try
              {
                  _connectingSocket.Connect(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 1234));
              }
              catch { }
          }
          SetupForReceiveing();
         }
     }
    
     private void SetupForReceiveing()
     {
        // View Client Class bottom of Client Example
         Client.SetClient(_connectingSocket);
         Client.StartReceiving();
     }
    

Envoi d'un message au serveur

Nous avons donc maintenant une application presque terminée ou Socket. La seule chose que nous n'avons pas jet est une classe pour envoyer un message au serveur.

public class SendPacket
{
    private Socket _sendSocked;

    public SendPacket(Socket sendSocket)
    {
        _sendSocked = sendSocket;
    }

    public void Send(string data)
    {
        try
        {         
            /* what hapends here:
                 1. Create a list of bytes
                 2. Add the length of the string to the list.
                    So if this message arrives at the server we can easily read the length of the coming message.
                 3. Add the message(string) bytes
            */
  
            var fullPacket = new List<byte>();
            fullPacket.AddRange(BitConverter.GetBytes(data.Length));
            fullPacket.AddRange(Encoding.Default.GetBytes(data));

            /* Send the message to the server we are currently connected to.
            Or package stucture is {length of data 4 bytes (int32), actual data}*/
            _sendSocked.Send(fullPacket.ToArray());
        }
        catch (Exception ex)
        {
            throw new Exception();
        }
    }

Enfin, créez deux boutons l'un pour vous connecter et l'autre pour envoyer un message:

    private void ConnectClick(object sender, EventArgs e)
    {
        Connector tpp = new Connector();
        tpp.TryToConnect();
    }

    private void SendClick(object sender, EventArgs e)
    {
        Client.SendString("Test data from client");
    }

La classe de client que j'ai utilisée dans cet exemple

    public static void SetClient(Socket socket)
    {
        Id = 1;
        Socket = socket;
        Receive = new ReceivePacket(socket, Id);
        SendPacket = new SendPacket(socket);
    }

Remarquer

La classe de réception du serveur est identique à la classe de réception du client.

Conclusion

Vous avez maintenant un serveur et un client. Vous pouvez utiliser cet exemple de base. Par exemple, faites en sorte que le serveur puisse également recevoir des fichiers ou d'autres éléments. Ou envoyez un message au client. Dans le serveur, vous avez une liste de clients, donc lorsque vous recevez quelque chose que vous connaissez avec le client, il vient.

Résultat final: entrer la description de l'image ici