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.

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

1. 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 multi-thread 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
  • Formattage 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.

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

MéthodeDescription
Socket.BindLie 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.ListenPlace en file d'attente (queue) toutes les connexions entrantes
Socket.AcceptLit 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.SelectPermet entre-autre, 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.PollPermet, un peu comme select, de détecter si une socket est prête à être lue ou écrite. A l'inverse de select, elle permet aussi de détecter si une connexion est terminée (voir commentaire dans le code).
Socket.ReceivePermet de réceptionner les données qui ont été écrites sur la socket
Socket.SendPermet d'écrire des données sur une socket
Socket.ConnectPermet de se connecter à une socket

1.2. 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 active. Le 2ème et 3ème thread sont démarrés avant même qu'une connexion cliente ait eu lieu. Etant 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 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 soit effectuée mais j'ai opté pour la facilité, d'autant que l'impact sur les performances est nul
  • Le 2ème 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 3ème thread se charge de vérifier que les connexions clientes sont toujours active
  • Le 4ème thread est démarré lorsque le 2ème 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 2ème thread se charge de lire les données entrantes que le serveur envoie au client
  • Le 3ème 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 parralèle aux autres thread 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'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 multi-thread, dès qu'il reçoit une connexion, il se remet immédiatement en attente.

1.3. 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 réentrer 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é à 4ko.

Image non disponible

1.4. 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 reu par un client
	//vers tous les autres clients
	private void writeToAll()
	{
	 base.sendMsg(msg);
	}
	private void infoToAll()
	{
	 base.sendMsg(msgDisconnected);
	}

	private void CheckIfStillConnected()
	{
	 /* Etant 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élablement essayé de lire ou d'écrire sur cette socket, cette méthode
	  * parvient à déterminer si une socket cliente s'est déconnectée grce à la méthode
	  * poll. On effectue un poll en lecture sur la socket, si le poll retourne vrai et que
	  * le nombre de bytes disponible 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(sequence==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();
	 }
	}
 }

1.5. 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 horizontale :)
	//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érement
	 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!="")
			{
				//Etant 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 constament 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é clturé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 reues(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 reues 
			  //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 arrté à 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 formatte 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 sequence
	  //Le numéro de séquence 1 servira à identifier le pseudo
	  //cté 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
	//entte 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'arrte
	 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();
	}


    }
}

2. 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 zips:

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 et 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.