Utilisation des sockets en C#

Dans cet article nous allons aborder les sockets. Les sockets sont des points de communication au sein d'un réseau ou d'une même machine permettant à des processus d'échanger des informations. Les chats sont généralement basés sur les sockets. Nous vous proposons donc d'étudier le chat développé pour ce tutoriel.

N'hésitez pas à commenter cet article ! Commentez Donner une note  l'article (5)

Article lu   fois.

L'auteur

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Introduction

Pour les besoins de ce tutoriel, nous avons développé une application de chat se basant sur les sockets. Cette application est composée d'un serveur et d'un client. Nous utiliserons la classe System.Net.Sockets. Il eut été possible d'utiliser aussi les classes TcpClient et TcpServer. Nous travaillerons en mode synchrone multithread et avec des connexions en mode « connecté ». Le mode connecté signifie que lorsque le client a établi une connexion avec le serveur, celle-ci reste ouverte jusqu'à ce que l'un des process décide de la fermer ou qu'un problème réseau survient. Je fais cette précision, car il est possible de travailler en mode « déconnecté », c'est-à-dire qu'un client se connecte sur un serveur, celui-ci traite l'info et renvoie un « ack » (acknowledgement) au client qui dès réception de cet ack ferme la connexion.

Le chat comprend les fonctionnalités suivantes :

  • pas de limitation au niveau du nombre de clients pouvant se connecter simultanément ;
  • notification des connectés/déconnectés ;
  • notification des messages reçus par un clignotement de fenêtre si celle-ci est réduite ;
  • formatage des messages en RTF à la volée. Nous aurions pu nous contenter du mode texte qui est beaucoup plus facile à gérer. Car outre sa relative complexité, le mode RTF est moins performant et est sensible à certains caractères spéciaux comme les accolades par exemple.

I-A. Les principales méthodes de la classe Sockets que nous avons utilisées pour réaliser le chat

Méthode

Description

Socket.Bind

Lie la socket à un point de communication, renvoie une erreur si le port spécifié par le point de communication est déjà utilisé par un autre processus

Socket.Listen

Place en file d'attente (queue) toutes les connexions entrantes

Socket.Accept

Lit la file d'attente et accepte les connexions entrantes. Accept est bloquant, l'exécution du code est bloquée jusqu'à ce qu'accept détecte une connexion entrante dans la file d'attente

Socket.Select

Permet entre autres de vérifier si une socket connectée tente d'écrire quelque chose. En passant un tableau de connexion en paramètre à select, après l'exécution de select, le tableau ne contient plus que les sockets ayant des données prêtes à être lues

Socket.Poll

Permet, un peu comme select, de détecter si une socket est prête à être lue ou écrite. À l'inverse de select, elle permet aussi de détecter si une connexion est terminée (voir commentaire dans le code).

Socket.Receive

Permet de réceptionner les données qui ont été écrites sur la socket

Socket.Send

Permet d'écrire des données sur une socket

Socket.Connect

Permet de se connecter à une socket

I-B. Architecture de notre chat

Image non disponible

Comme vous pouvez le constater, la communication entre le serveur et le(s) client(s) est basée sur le protocole TCP/IP. Cette communication peut avoir lieu au sein d'un réseau ou d'une même machine. Les connexions établies restent donc en mode « connecté » jusqu'à ce que l'un des processus décide de les fermer.

Le code du serveur est « divisé » en quatre parties.

  • Le thread principal : c'est lui qui crée la socket d'écoute sur le serveur, ensuite, dans une boucle infinie, il attend de nouvelles connexions entrantes (socket.accept). Il démarre préalablement le thread d'écoute et le thread vérifiant que les connexions clientes sont toujours actives. Le 2e et 3e thread sont démarrés avant même qu'une connexion cliente ait eu lieu. Étant donné qu'ils ne consomment quasiment aucune ressource, ce n'est pas préjudiciable. Il est en fait plus facile de les démarrer avant, car dans ce cas, on n’a pas à vérifier s'ils sont déjà démarrés ou pas lorsqu'on les démarre. J'aurais pu ne les démarrer que lorsqu'au moins une connexion cliente est effectuée, mais j'ai opté pour la facilité, d'autant que l'impact sur les performances est nul.
  • Le 2e thread se charge de lire en permanence les données écrites par les clients. Il démarre le thread d'écriture lorsqu'il reçoit des données.
  • Le 3e thread se charge de vérifier que les connexions clientes sont toujours actives.
  • Le 4e thread est démarré lorsque le 2e thread a lu des données sur une socket.

Le code du client est lui, un peu plus simple .

  • Le thread principal se charge de créer et de connecter la socket cliente au serveur et gère les évènements liés aux contrôles winform.
  • Le 2e thread se charge de lire les données entrantes que le serveur envoie au client.
  • Le 3e thread est démarré sur demande et se charge de faire clignoter la fenêtre.

Pour rappel, un thread est un ensemble d'instructions s'exécutant au sein d'un même processus en parallèle aux autres threads démarrés dans ce processus. Ceci permet donc d'avoir plusieurs « entités » de code plus ou moins autonomes. Cela permet donc d'accroître les performances. Pour illustrer ceci, prenons simplement le serveur, s'il n'y avait pas plusieurs threads, à chaque fois qu'il accepte une connexion, il devrait lire les données envoyées par celle-ci et renvoyer ces données aux autres clients. Pendant ce temps, il ne pourrait donc plus se remettre en attente de connexion. En mode multithread, dès qu'il reçoit une connexion, il se remet immédiatement en attente.

I-C. Copies d'écran

L'écran de connexion. Entrez successivement le nom du serveur sur lequel l'exécutable « serveur.exe » tourne suivi du nom du pseudo. Vous pouvez enregistrer les paramètres de connexion qui seront stockés dans un fichier pour ne plus devoir rentrer ultérieurement ces paramètres. L'option « Faire clignoter la fenêtre » permet d'activer le clignotement de la fenêtre lorsque celle-ci n'est pas active et que vous recevez un message.

Image non disponible

L'écran du chat. C'est ici que vous entrerez vos propres messages et que vous visualiserez les messages des autres utilisateurs. Les notifications de connexion/déconnexion d'autres utilisateurs seront affichées dans le RichTextBox principal. Le RichTextBox dans lequel vous saisissez vos messages est limité à 4 ko.

Image non disponible

I-D. Le serveur

Veuillez m'excuser pour l'indentation peu lisible du code. Ceci est principalement dû au fait que j'essaye que le code tienne dans une résolution 1024*768.

 
Sélectionnez
using System;
using System.Net;
using System.Net.Sockets;
using System.Collections;
using System.Threading;
using System.IO;

namespace DefaultNamespace
{
  public class Server:forwardToAll
  {
    ArrayList readList=new ArrayList(); //liste utilisée par socket.select 
    string msgString=null; //contiendra le message envoyé aux autres clients
    string msgDisconnected=null; //Notification connexion/déconnexion
    byte[] msg;//Message sous forme de bytes pour socket.send et socket.receive
    public bool useLogging=false; //booleen permettant de logger le processing dans un fichier log
    public bool readLock=false;//Flag aidant à la synchronisation
    private string rtfMsgEncStart="\pard\cf1\b0\f1 ";//Code RTF
    private string rtfMsgContent="\cf2 ";//code RTF
    private string rtfConnMsgStart="\pard\qc\b\f0\fs20 "; //Code RTF
    public void Start()
    {
    //réception de l'adresse IP locale
     IPHostEntry ipHostEntry = Dns.Resolve(Dns.GetHostName());
     IPAddress ipAddress = ipHostEntry.AddressList[0];
     Console.WriteLine("IP="+ipAddress.ToString());
     Socket CurrentClient=null;
     //Création de la socket
     Socket ServerSocket = new Socket(AddressFamily.InterNetwork,
       SocketType.Stream,
       ProtocolType.Tcp);
     try
     {
      //On lie la socket au point de communication
      ServerSocket.Bind(new IPEndPoint(ipAddress, 8000));
      //On la positionne en mode "écoute"
        ServerSocket.Listen(10);
      //Démarrage du thread avant la première connexion client
      Thread getReadClients = new Thread(new ThreadStart(getRead));
      getReadClients.Start();
      //Démarrage du thread vérifiant l'état des connexions clientes
      Thread pingPongThread = new Thread(new ThreadStart(CheckIfStillConnected));
      pingPongThread.Start();
      //Boucle infinie
      while(true){
       Console.WriteLine("Attente d'une nouvelle connexion...");
       //L'exécution du thread courant est bloquée jusqu'à ce qu'un
       //nouveau client se connecte
       CurrentClient=ServerSocket.Accept();
       Console.WriteLine("Nouveau client:"+CurrentClient.GetHashCode());
         //Stockage de la ressource dans l'arraylist acceptlist
       acceptList.Add(CurrentClient);
      }
     }
     catch(SocketException E)
     {
      Console.WriteLine(E.Message);
     }

    }

    //Méthode permettant de générer du logging
    //dans un fichier log selon que le membre useLogging
    //soit à true ou false
    private void Logging(string message)
    {
     using (StreamWriter sw = File.AppendText("chatServer.log"))
     {
      sw.WriteLine(DateTime.Now+": "+message);      
     }
    }

    //Méthode démarrant l'écriture du message reçu par un client
    //vers tous les autres clients
    private void writeToAll()
    {
     base.sendMsg(msg);
    }
    private void infoToAll()
    {
     base.sendMsg(msgDisconnected);
    }

    private void CheckIfStillConnected()
    {
     /* Étant donné que la propriété .Connected d'une socket n'est pas
      * mise à jour lors de la déconnexion d'un client sans que l'on ait
      * préalablement essayé de lire ou d'écrire sur cette socket, cette méthode
      * parvient à déterminer si une socket cliente s'est déconnectée grâce à la méthode
      * poll. On effectue un poll en lecture sur la socket, si le poll retourne vrai et que
      * le nombre de bytes disponibles est 0, il s'agit d'une connexion terminée*/
     while(true)
     {
      for(int i=0;i<acceptList.Count;i++)
      {
       if(((Socket)acceptList[i]).Poll(10,SelectMode.SelectRead) && ((Socket)acceptList[i]).Available==0)
       {
        if(!readLock)
        {
        Console.WriteLine("Client "+((Socket)acceptList[i]).GetHashCode()+" déconnecté");
        removeNick(((Socket)acceptList[i]));
        ((Socket)acceptList[i]).Close();
        acceptList.Remove(((Socket)acceptList[i]));
        i--;
        }
       }
      }
      Thread.Sleep(5);
     }
    }
    //Vérifie que le pseudo n'est pas déjà attribué à un autre utilisateur
    //La Hashtable matchlist ne sert qu'à ça. Pour des développements ultérieurs, elle
    //pourrait aussi servir à envoyer la liste de tous les connectés aux utilisateurs
    private bool checkNick(string nick,Socket Resource)
    {
     if(MatchList.ContainsValue(nick))
     {
      //Le pseudo est déjà pris, on refuse la connexion.
      ((Socket)acceptList[acceptList.IndexOf(Resource)]).Shutdown(SocketShutdown.Both);
      ((Socket)acceptList[acceptList.IndexOf(Resource)]).Close();
      acceptList.Remove(Resource);
      Console.WriteLine("Pseudo déjà pris");
      return false;
     }
     else
     {
       MatchList.Add(Resource,nick);
       getConnected();
     }
     return true;
    }
    
    //Lorsqu'un client se déconnecte, il faut supprimer le pseudo associé à cette connexion
    private void removeNick(Socket Resource)
    {
      Console.Write("DECONNEXION DE:"+MatchList[Resource]);
      msgDisconnected=rtfConnMsgStart+((string)MatchList[Resource]).Trim()+" vient de se déconnecter!\par";
      Thread DiscInfoToAll=new Thread(new ThreadStart(infoToAll));
      DiscInfoToAll.Start();
      DiscInfoToAll.Join();
      MatchList.Remove(Resource);
    }
    
    //Cette méthode est exécutée dans un thread à part
    //Elle lit en permanence l'état des sockets connectées et
    //vérifie si celles-ci tentent d'envoyer quelque chose
    //au serveur. Si tel est le cas, elle réceptionne les paquets
    //et appelle forwardToAll pour renvoyer ces paquets vers
    //les autres clients.
    private void getRead()
    {
      while(true)
      {
        readList.Clear();
        for(int i=0;i<acceptList.Count;i++){
        readList.Add((Socket)acceptList[i]);
        }
        if(readList.Count>0)
        {
        Socket.Select(readList, null, null, 1000);
        for(int i=0;i<readList.Count;i++)
        {
          if(((Socket)readList[i]).Available>0)
          {
            readLock=true;
              int paquetsReceived=0;
            long sequence=0;
            string Nick=null;
            string formattedMsg=null;
            while(((Socket)readList[i]).Available>0)
            {
              msg = new byte[((Socket)readList[i]).Available];
              ((Socket)readList[i]).Receive(msg,msg.Length,SocketFlags.None);
              msgString=System.Text.Encoding.UTF8.GetString(msg);
              if(paquetsReceived==0)
              {
                string seq=msgString.Substring(0,6);
                try
                {
                    sequence=Convert.ToInt64(seq);
                    Nick=msgString.Substring(6,15);
                    formattedMsg=rtfMsgEncStart+Nick.Trim()+" a écrit:"+rtfMsgContent+
                    msgString.Substring(20,(msgString.Length-20))+"\par";
                }
                catch
                {
                    //Ce cas de figure ne devrait normalement
                    //jamais se produire. Il peut se produire uniquement
                    //si un client développé par quelqu'un d'autre
                    //tente de se connecter sur le serveur.
                    Console.Write("Message non conforme");
                    acceptList.Remove(((Socket)readList[i]));
                    break;
                }
              }
              else
              {
                formattedMsg=rtfMsgContent+msgString+"\par";
              }
               msg=System.Text.Encoding.UTF8.GetBytes(formattedMsg);
              if(séquence==1){
                if(!checkNick(Nick,((Socket)readList[i])))
                {
                break;
                }
              else
              {
               string rtfMessage=rtfConnMsgStart+Nick.Trim()+" vient de se connecter\par";
                  msg=System.Text.Encoding.UTF8.GetBytes(rtfMessage);
              }
            }
            if(useLogging)
            {
             Logging(formattedMsg);
            }
            //Démarrage du thread renvoyant le message à tous les clients
            Thread forwardingThread=new Thread(new ThreadStart(writeToAll));
            forwardingThread.Start();
            forwardingThread.Join();
            paquetsReceived++;
             }
             readLock=false;
           }
        }
       }
       Thread.Sleep(10);
      }
     }
    }


    public class forwardToAll
    {
     public ArrayList acceptList=new ArrayList();
     public Hashtable MatchList=new Hashtable();
       public forwardToAll(){}
     public void sendMsg(byte[] msg)
     {
       for(int i=0;i<acceptList.Count;i++)
       {
        if(((Socket)acceptList[i]).Connected)
        {
        try
        {
            int bytesSent=((Socket)acceptList[i]).Send(msg,msg.Length,SocketFlags.None);
        }
        catch
        {
            Console.Write(((Socket)acceptList[i]).GetHashCode()+" déconnecté");
        }
        }
        else
        {
        acceptList.Remove((Socket)acceptList[i]);
        i--;
        }
       }
    }
    
    public void sendMsg(string message)
    {
     for(int i=0;i<acceptList.Count;i++)
     {
       if(((Socket)acceptList[i]).Connected)
       {
        try
        {
          byte[] msg=System.Text.Encoding.UTF8.GetBytes(message);
          int bytesSent=((Socket)acceptList[i]).Send(msg,msg.Length,SocketFlags.None);
          Console.WriteLine("Writing to:"+acceptList.Count.ToString());
        }
        catch
        {
          Console.Write(((Socket)acceptList[i]).GetHashCode()+" déconnecté");
        }
       }
       else
       {
        acceptList.Remove((Socket)acceptList[i]);
        i--;
       }
     }
    }
     }

     class MainClass
    {
     public static void Main(string[] args)
     {
        Server startIt=new Server();
        if(args.Length>0)
        {
          if(args[0]=="-log" && args[1]=="true")
          {
            startIt.useLogging=true;
          }
        }
        startIt.Start();
     }
    }
 }

I-E. Le client

 
Sélectionnez
using System;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.IO;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;

namespace chatClient
{
 public class MainForm : System.Windows.Forms.Form
 {
    private System.Windows.Forms.TabPage Chat;
    private System.Windows.Forms.TabControl tabControl1;
    private System.Windows.Forms.RichTextBox msgArea;
    private System.Windows.Forms.Button Save;
    private System.Windows.Forms.Label label1;
    private System.Windows.Forms.TextBox Nick;
    private System.Windows.Forms.TextBox ServerHost;
    private System.Windows.Forms.Button sendMsg;
    private System.Windows.Forms.RichTextBox chatBody;
    private System.Windows.Forms.Label label2;
    private System.Windows.Forms.TabPage Connexion;
    private System.Windows.Forms.Button Connect;
    private System.Windows.Forms.CheckBox Notif;
    public Socket ClientSocket=null;
    public Thread DataReceived=null;
    private Thread Flasht = null;
    public string NickName=null;
    public long sequence=0;
    public int numberMsg=0;
    private bool allowBlink=false;
    //Je mets cette déclaration sur 3 lignes pour ne pas avoir de scroll horizontal :)
    //C'est en fait la déclaration de début d'un document RTF (merci wordpad) 
    private const string rtfStart="{\\rtf1\\ansi\\ansicpg1252\\deff0\\
    deflang1033{\\fonttbl{\\f0\\fswiss\\fcharset0 Arial;}{\\f1\\fswiss\\fprq2\\
    fcharset0 Arial;}}{\\colortbl ;\\red0\\green0\\blue128;\\red0\\green128\\blue0;}\\viewkind4\\uc1";
    private string rtfContent=null;

    public MainForm()
    {
        InitializeComponent();
    }

    public static void Main(string[] args)
    {
        Application.Run(new MainForm());
    }

    //Tout le code ci-dessous est automatiquement créé par l'IDE
    #region Windows Forms Designer generated code
    private void InitializeComponent() 
    {
     System.Resources.ResourceManager resources = new System.Resources.ResourceManager(typeof(MainForm));
     this.Notif = new System.Windows.Forms.CheckBox();
     this.Connect = new System.Windows.Forms.Button();
     this.Connexion = new System.Windows.Forms.TabPage();
     this.label2 = new System.Windows.Forms.Label();
     this.chatBody = new System.Windows.Forms.RichTextBox();
     this.sendMsg = new System.Windows.Forms.Button();
     this.ServerHost = new System.Windows.Forms.TextBox();
     this.Nick = new System.Windows.Forms.TextBox();
     this.label1 = new System.Windows.Forms.Label();
     this.Save = new System.Windows.Forms.Button();
     this.msgArea = new System.Windows.Forms.RichTextBox();
     this.tabControl1 = new System.Windows.Forms.TabControl();
     this.Chat = new System.Windows.Forms.TabPage();
     this.Connexion.SuspendLayout();
     this.tabControl1.SuspendLayout();
     this.Chat.SuspendLayout();
     this.SuspendLayout();
     this.Notif.BackColor = System.Drawing.Color.IndianRed;
     this.Notif.ForeColor = System.Drawing.SystemColors.ControlLightLight;
     this.Notif.Location = new System.Drawing.Point(16, 144);
     this.Notif.Name = "Notif";
     this.Notif.Size = new System.Drawing.Size(152, 24);
     this.Notif.TabIndex = 12;
     this.Notif.Text = "Faire clignoter la fenêtre";
     this.Connect.BackColor = System.Drawing.Color.IndianRed;
     this.Connect.ForeColor = System.Drawing.Color.WhiteSmoke;
     this.Connect.Location = new System.Drawing.Point(16, 96);
     this.Connect.Name = "Connect";
     this.Connect.Size = new System.Drawing.Size(104, 23);
     this.Connect.TabIndex = 6;
     this.Connect.Text = "Se connecter";
     this.Connect.Click += new System.EventHandler(this.Button2Click);
     this.Connexion.BackgroundImage = ((System.Drawing.Image)(resources.GetObject("Connexion.BackgroundImage")));
     this.Connexion.Controls.Add(this.Notif);
     this.Connexion.Controls.Add(this.Save);
     this.Connexion.Controls.Add(this.Nick);
     this.Connexion.Controls.Add(this.label2);
     this.Connexion.Controls.Add(this.label1);
     this.Connexion.Controls.Add(this.ServerHost);
     this.Connexion.Controls.Add(this.Connect);
     this.Connexion.Location = new System.Drawing.Point(4, 22);
     this.Connexion.Name = "Connexion";
     this.Connexion.Size = new System.Drawing.Size(528, 310);
     this.Connexion.TabIndex = 1;
     this.Connexion.Text = "Connexion";
     this.label2.AutoSize = true;
     this.label2.Location = new System.Drawing.Point(16, 48);
     this.label2.Name = "label2";
     this.label2.Size = new System.Drawing.Size(71, 16);
     this.label2.TabIndex = 9;
     this.label2.Text = "Votre pseudo";
     this.chatBody.Font = new System.Drawing.Font("Tahoma", 8.25F, System.Drawing.FontStyle.Regular, 
     System.Drawing.GraphicsUnit.Point, ((System.Byte)(0)));
     this.chatBody.Location = new System.Drawing.Point(0, 8);
     this.chatBody.Name = "chatBody";
     this.chatBody.ReadOnly = true;
     this.chatBody.Size = new System.Drawing.Size(512, 192);
     this.chatBody.TabIndex = 8;
     this.chatBody.Text = "";
     this.chatBody.TextChanged += new System.EventHandler(this.HandleAutoScroll);
     this.sendMsg.BackColor = System.Drawing.Color.IndianRed;
     this.sendMsg.ForeColor = System.Drawing.Color.WhiteSmoke;
     this.sendMsg.Location = new System.Drawing.Point(208, 272);
     this.sendMsg.Name = "sendMsg";
     this.sendMsg.TabIndex = 5;
     this.sendMsg.Text = "Envoyer";
     this.sendMsg.Click += new System.EventHandler(this.SendMessage);
      this.ServerHost.BackColor = System.Drawing.Color.LightGray;
     this.ServerHost.Location = new System.Drawing.Point(160, 16);
     this.ServerHost.Name = "ServerHost";
     this.ServerHost.Size = new System.Drawing.Size(128, 20);
     this.ServerHost.TabIndex = 7;
     this.ServerHost.Text = "";
     this.Nick.BackColor = System.Drawing.Color.LightGray;
     this.Nick.Location = new System.Drawing.Point(160, 48);
     this.Nick.Name = "Nick";
     this.Nick.Size = new System.Drawing.Size(128, 20);
     this.Nick.TabIndex = 10;
     this.Nick.Text = "";
     this.label1.AutoSize = true;
     this.label1.Location = new System.Drawing.Point(16, 16);
     this.label1.Name = "label1";
     this.label1.Size = new System.Drawing.Size(130, 16);
     this.label1.TabIndex = 8;
     this.label1.Text = "Entrez le nom du serveur";
     this.Save.BackColor = System.Drawing.Color.IndianRed;
     this.Save.ForeColor = System.Drawing.Color.WhiteSmoke;
     this.Save.Location = new System.Drawing.Point(160, 96);
     this.Save.Name = "Save";
     this.Save.Size = new System.Drawing.Size(216, 23);
     this.Save.TabIndex = 11;
     this.Save.Text = "Enregistrer les paramètres de connexion";
     this.Save.Click += new System.EventHandler(this.SaveClick);
     this.msgArea.Location = new System.Drawing.Point(0, 200);
     this.msgArea.MaxLength = 2048;
     this.msgArea.Name = "msgArea";
     this.msgArea.Size = new System.Drawing.Size(512, 64);
     this.msgArea.TabIndex = 9;
     this.msgArea.Text = "";
     this.tabControl1.Controls.Add(this.Chat);
     this.tabControl1.Controls.Add(this.Connexion);
     this.tabControl1.Location = new System.Drawing.Point(16, 16);
     this.tabControl1.Name = "tabControl1";
     this.tabControl1.SelectedIndex = 0;
     this.tabControl1.Size = new System.Drawing.Size(536, 336);
     this.tabControl1.TabIndex = 6;
     this.Chat.BackgroundImage = ((System.Drawing.Image)(resources.GetObject("Chat.BackgroundImage")));
     this.Chat.Controls.Add(this.msgArea);
     this.Chat.Controls.Add(this.chatBody);
     this.Chat.Controls.Add(this.sendMsg);
     this.Chat.Location = new System.Drawing.Point(4, 22);
     this.Chat.Name = "Chat";
     this.Chat.Size = new System.Drawing.Size(528, 310);
     this.Chat.TabIndex = 0;
     this.Chat.Text = "Chat";
     this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
     this.BackgroundImage = ((System.Drawing.Image)(resources.GetObject("$this.BackgroundImage")));
     this.ClientSize = new System.Drawing.Size(576, 389);
     this.Controls.Add(this.tabControl1);
     this.Name = "MainForm";
     this.Text = "Chat";
     this.Load += new System.EventHandler(this.MainFormLoad);
     this.Activated += new System.EventHandler(this.StopBlink);
     this.Deactivate += new System.EventHandler(this.StartBlink);
     //Tenez cette instruction à l'oeil si vous passez en mode design avec
     //Sharpdevelop, car il l'enlève automatiquement, il faudra donc la remettre ou gérer l'évènement
     //différemment
     this.Closing += new System.ComponentModel.CancelEventHandler(OnClosing);
     this.Connexion.ResumeLayout(false);
     this.tabControl1.ResumeLayout(false);
     this.Chat.ResumeLayout(false);
     this.ResumeLayout(false);
    }
    #endregion

    //Appel à user32.dll, sert à faire clignoter la fenêtre
    [ DllImport("user32.dll") ]
    static extern int FlashWindow
    (
        int hwnd,
        int bInvert
    );

    //Sert à faire clignoter la fenêtre
    private void Flash()
    {
        while(true)
        {
            if(allowBlink)
            {
                FlashWindow((int)this.Handle, 1);
                Thread.Sleep(500);
            }
            else
            {
                Thread.CurrentThread.Abort();
            }
        }
    }
    //La fenêtre ne peut clignoter que si elle n'est pas active
    void StartBlink(object sender, System.EventArgs e)
    {
        allowBlink=true;
    }
    void StopBlink(object sender, System.EventArgs e)
    {
        allowBlink=false;
    }
    //Cette méthode traite le message à envoyer sur le serveur
    void SendMessage(object sender, System.EventArgs e)
    {
        //On vérifie que le client est bien connecté
        if(ClientSocket==null || !ClientSocket.Connected)
        {
            MessageBox.Show("Vous n'êtes pas connecté");
            return;
        }
        try
        {
            if(msgArea.Text!="")
            {
                //Étant donné qu'on travaille en RTF, on échappe les caractères
                //spéciaux avant de les envoyer sur le serveur qui nous renverra
                //le message ainsi qu'aux autres clients connectés
                //Si vous travaillez en mode texte, vous n'aurez pas à vous soucier
                //de tout cela
                string reformattedBuffer=msgArea.Text.Replace("}","\\}");
                reformattedBuffer=reformattedBuffer.Replace("\n","\\par\r\n");
                //Chaque message est précédé d'un numéro de séquence, ce qui permet
                //de vérifier si le client vient de se connecter ou non
                SendMsg(GetSequence()+NickName+reformattedBuffer.Replace("{","\\{"));
                msgArea.Clear();
            }

        }
        catch(Exception E)
        {
            MessageBox.Show("SendMessage:"+E.Message);
        }
    }

    //Si les paramètres de connexion ont été enregistrés, on les récupère
    //via cette méthode
    void getParams()
    {
        if(File.Exists("params.ini"))
        {
            using(StreamReader SR=new StreamReader("params.ini"))
            {
                ServerHost.Text=SR.ReadLine();
                Nick.Text=SR.ReadLine();
                SR.Close();
            }
        }
    }
    //Cette méthode envoie le message sur le serveur
    void SendMsg(string message)
    {
        byte[] msg = System.Text.Encoding.UTF8.GetBytes(message);
        int DtSent=ClientSocket.Send(msg,msg.Length,SocketFlags.None);
        if(DtSent == 0)
        {
            MessageBox.Show("Aucune donnée n'a été envoyée");
        }
    }
  
    //Cette méthode permet de récupérer l'adresse ip du serveur sur lequel
    //on désire se connecter
    private String GetAdr()
    {
          try
           {
               IPHostEntry iphostentry = Dns.GetHostByName(ServerHost.Text);
               String IPStr = "";
               foreach(IPAddress ipaddress in iphostentry.AddressList){
                    IPStr = ipaddress.ToString();
                return IPStr;
            }    
           }
        catch(SocketException E)
        {
            MessageBox.Show(E.Message);
        }

        return "";
    }

        //Cette méthode est appelée par un thread à part qui lit constamment la socket
        //pour voir si le serveur essaye d'écrire dessus
    private void CheckData()
    {
     try
     {
        while(true)
        {
         if(ClientSocket.Connected)
         {
          if(ClientSocket.Poll(10,SelectMode.SelectRead) && ClientSocket.Available==0)
          {
            //La connexion a été clôturée par le serveur ou bien un problème
            //réseau est apparu
            MessageBox.Show("La connexion au serveur est interrompue. Essayez avec un autre pseudo");
            Connect.Enabled=true;
            Thread.CurrentThread.Abort();
          }
          //Si la socket a des données à lire
          if(ClientSocket.Available>0)
          {
            string messageReceived=null;
            if(Flasht==null)
            {
            if(allowBlink && Notif.Checked)
            {
              Flasht = new Thread(new ThreadStart(Flash));
              Flasht.Start();
            }
            }
            else
            {
            if(allowBlink && Notif.Checked && Flasht.IsAlive==false)
             {
               Flasht = new Thread(new ThreadStart(Flash));
               Flasht.Start();
             }
            }
            while(ClientSocket.Available>0)
            {
               try
               {
                byte[] msg=new Byte[ClientSocket.Available];
                //Réception des données
                ClientSocket.Receive(msg,0,ClientSocket.Available,SocketFlags.None);
                messageReceived=System.Text.Encoding.UTF8.GetString(msg).Trim();
                //On concatène les données reçues(max 4ko) dans
                //une variable de la classe
                rtfContent+=messageReceived;
               }
               catch(SocketException E)
               {
                 MessageBox.Show("CheckData read"+E.Message);
               }
            }
            try
            {
              //On remplit le richtextbox avec les données reçues 
              //lorsqu'on a tout réceptionné
              chatBody.Rtf=rtfStart+rtfContent;
              this.BringToFront();
            }
            catch(Exception E)
            {
              MessageBox.Show(E.Message);
            }

           }
         }
        //On temporise pendant 10 millisecondes, ceci pour éviter
        //que le micro processeur s'emballe
        Thread.Sleep(10);
        }
     }
     catch
     {
        //Ce thread étant susceptible d'être arrêté à tout moment
        //on catch l'exception afin de ne pas afficher un message à l'utilisateur
        Thread.ResetAbort();
     }
       }
        //Cette méthode enregistre les paramètres de connexion dans le fichier
    void SaveClick(object sender,System.EventArgs e)
    {
     if(Nick.Text != "" && ServerHost.Text != "")
     {
        using(StreamWriter SW=new StreamWriter("params.ini"))
        {
            SW.WriteLine(ServerHost.Text);
            SW.WriteLine(Nick.Text);
            SW.Close();
        }
     }
     else
     {
        MessageBox.Show("Veuillez saisir le nom du serveur et le pseudo");
     }


    }
    void Button2Click(object sender, System.EventArgs e)
    {
      if(Nick.Text=="")
      {
        MessageBox.Show("Le pseudo ne peut tre null");
        return;
      }
      if(ServerHost.Text=="")
      {
         MessageBox.Show("Le nom du serveur ne peut être null");
        return;
      }
      //On formate le pseudo sur une longueur de 15 caractères
      NickName=Nick.Text.Trim();
      if(NickName.Length<15)
      {
        char pad=Convert.ToChar(" ");
        NickName=NickName.PadRight(15,pad);
      }
      else if(NickName.Length > 15)
      {
        MessageBox.Show("Le pseudo doit être de 15 caractères maximum");
        return;
        }
      //Chaque message sera précédé d'un numéro de séquence
      //Le numéro de séquence 1 servira à identifier le pseudo
      //côté serveur.
      sequence=0;
       IPAddress ip = IPAddress.Parse (GetAdr());
      IPEndPoint ipEnd = new IPEndPoint (ip,8000);
      ClientSocket=new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp );
      try
      {
        ClientSocket.Connect(ipEnd);
        if(ClientSocket.Connected)
        {
            SendMsg(GetSequence()+NickName);
            Connect.Enabled=false;
        }


      }
      catch(SocketException E)
      {
         MessageBox.Show("Connection"+E.Message);
      }
      try
      {
        DataReceived = new Thread(new ThreadStart(CheckData));
         DataReceived.Start();
      }
      catch(Exception E)
      {
        MessageBox.Show("Démarrage Thread"+E.Message);
      }
    }
    //Cette méthode génère le numéro de séquence collé en
    //entête du message envoyé au serveur
    string GetSequence()
    {
        sequence++;
        string msgSeq=Convert.ToString(sequence);
        char pad=Convert.ToChar("0");
        msgSeq=msgSeq.PadLeft(6,pad);
        return msgSeq;
    }

    //Cette méthode provoque l'auto-scroll du richtextbox dès

    void HandleAutoScroll(object sender, System.EventArgs e)
    {
        chatBody.SelectionStart = chatBody.Rtf.Length;
           chatBody.Focus();
        msgArea.Focus();
    }

    //Cette méthode est exécutée lorsque l'on quitte l'application.
    private void OnClosing(object sender, System.ComponentModel.CancelEventArgs e)
    {
     //Si le thread recevant les données a été démarré, on l'arrête
     if(DataReceived!=null)
     {
        try
        {
            DataReceived.Abort();
            DataReceived.Join();
        }
        catch(Exception E)
        {
            MessageBox.Show("Arrt Thread"+E.Message);
        }
     }
     if(ClientSocket != null && ClientSocket.Connected)
     {
      try
      {
        ClientSocket.Shutdown(SocketShutdown.Both);
        ClientSocket.Close();
        if (ClientSocket.Connected) {
          MessageBox.Show("Erreur: " + Convert.ToString(System.Runtime.InteropServices.Marshal.GetLastWin32Error()));
        }

      }
      catch(SocketException SE)
      {
       MessageBox.Show("SE"+SE.Message);
      }
     }
    }

    void MainFormLoad(object sender, System.EventArgs e)
    {
        getParams();
    }


    }
}

II. Téléchargement

Le projet a été développé avec SharpDevelop, si vous désirez le modifier, le plus simple est de le faire avec SharpDevelop, mais rien ne vous empêche bien sûr de l'importer dans Visual Studio .NET. Téléchargez donc les deux zip :

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Copyright © 2004 Developpez. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.