Class - tcp client / serveur

Description

Cette source comprend un environement Client / Serveur complet utilisant le protocole TCP.
Elle se décompose en 2 DLL, tcpClient.dll et tcpServeur.dll ainsi que leur projet de test respectif.

Le serveur:
----------

Le serveur est démarrable, stoppable et redémmarable à tout moment. Il est entierement multithreadé pour eviter au maximum les charges
sur l'application qui va l'utiliser. Il n'est pas préformaté pour une utilisation particulière mais s'adaptera à tous vos projets.
Il est capable de gérer les multiconnexion (presque illimité, par defaut réglé à 99 clients mais peut etre augmenté jusqu'a la valeur maximale acceptée par un entier 32bits), la surveillance en temps réel des connexions clientes, la gestion des PING?PONG! (automatique et entièrement transparent pour l'utilisateur), envoi et réception de données, etc.
Pour reconnaitre les différente connexions, il utilise le HashCode des sockets utilisés ce qui lui confert une grande souplesse tout en ne se trompant pas sur le client interrogé.

Il fonctionne sur tous les ports disponible (par defaut: 11000). Pour détecter les déconnexions volontaires ou brutales d'un client, sa gestion est unique et utilise le système tres connu du jeu du PING?PONG!. Une latence, réglable par la propriété Timeout (par defaut: 10 secondes) est obligatoire. Meme si celle-ci est contraignante, elle permet de connaitre avec efficacité si un client s'est déconnecté ou non ! Je vous deconseil de réduire ce temps, mais plutot l'augmenter, tout dépend bien sur de la charge reseau que causeront vos transmissions.

Dans le projet test fourni, j'ai mis un petit système de reconnexion automatique sans pretention (mais qui fonctionne).

Le client:
---------

Le client a été développé pour se connecter uniquement sur ce serveur. Il répond automatique et de maniere transparente aux PING du serveur. Il est autonome et multithreadé. Il permet d'envoyer et recevoir des données.

Le projet d'exmple fourni avec montre comment l'utiliser facilement. Il ne restera plus qu'a gérer votre propre communication ;)

Source / Exemple :


// SOURCES DU PROJET DE TEST DU SERVEUR

using System;
using System.Drawing;
using System.Windows.Forms;

using tcpConnections;

namespace test
{

	public class MainForm : System.Windows.Forms.Form
	{
		private System.Windows.Forms.Label label1;
		private System.Windows.Forms.Button button1;
		private System.Windows.Forms.TextBox textBox1;
		private System.Windows.Forms.ListBox listBox2;
		private System.Windows.Forms.ListBox listBox1;
		
		// créer une nouvelle instance du serveur
		private tcpServeur serveur = new tcpServeur();
		
		
		public MainForm()
		{
			InitializeComponent();
		}
		
		[STAThread]
		public static void Main(string[] args)
		{
			Application.Run(new MainForm());
		}
		
		#region Windows Forms Designer generated code
		/// <summary>
		/// This method is required for Windows Forms designer support.
		/// Do not change the method contents inside the source code editor. The Forms designer might
		/// not be able to load this method if it was changed manually.
		/// </summary>
		private void InitializeComponent() {
			this.listBox1 = new System.Windows.Forms.ListBox();
			this.listBox2 = new System.Windows.Forms.ListBox();
			this.textBox1 = new System.Windows.Forms.TextBox();
			this.button1 = new System.Windows.Forms.Button();
			this.label1 = new System.Windows.Forms.Label();
			this.SuspendLayout();
			// 
			// listBox1
			// 
			this.listBox1.Location = new System.Drawing.Point(8, 40);
			this.listBox1.Name = "listBox1";
			this.listBox1.Size = new System.Drawing.Size(368, 329);
			this.listBox1.TabIndex = 2;
			// 
			// listBox2
			// 
			this.listBox2.Location = new System.Drawing.Point(384, 40);
			this.listBox2.Name = "listBox2";
			this.listBox2.Size = new System.Drawing.Size(80, 329);
			this.listBox2.TabIndex = 5;
			// 
			// textBox1
			// 
			this.textBox1.Enabled = false;
			this.textBox1.Location = new System.Drawing.Point(8, 376);
			this.textBox1.Name = "textBox1";
			this.textBox1.Size = new System.Drawing.Size(376, 20);
			this.textBox1.TabIndex = 3;
			this.textBox1.Text = "";
			// 
			// button1
			// 
			this.button1.Enabled = false;
			this.button1.Location = new System.Drawing.Point(392, 376);
			this.button1.Name = "button1";
			this.button1.TabIndex = 4;
			this.button1.Text = "envoyer";
			this.button1.Click += new System.EventHandler(this.Button1Click);
			// 
			// label1
			// 
			this.label1.Location = new System.Drawing.Point(8, 8);
			this.label1.Name = "label1";
			this.label1.Size = new System.Drawing.Size(456, 24);
			this.label1.TabIndex = 0;
			// 
			// MainForm
			// 
			this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
			this.ClientSize = new System.Drawing.Size(472, 405);
			this.Controls.Add(this.listBox2);
			this.Controls.Add(this.button1);
			this.Controls.Add(this.textBox1);
			this.Controls.Add(this.listBox1);
			this.Controls.Add(this.label1);
			this.Location = new System.Drawing.Point(10, 10);
			this.Name = "MainForm";
			this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
			this.Text = "serveur";
			this.Closing += new System.ComponentModel.CancelEventHandler(this.MainFormClosing);
			this.Load += new System.EventHandler(this.MainFormLoad);
			this.ResumeLayout(false);
		}
		#endregion
		
		void MainFormLoad(object sender, System.EventArgs e)
		{
			// port d'ecoute du serveur
			serveur.Port = 11000;
			
			// timeout pour les déconnexions des clients
			serveur.Timeout = 10000;
			
			// evenement produit lorsque l'etat de la connexion du serveur change
			serveur.ConnectionStateEvent += new tcpConnections.ConnectionStateHandler(ConnectionState);
			
			// evenement produit lorsque l'etat des connexions clientes change
			serveur.ClientsConnectionStateEvent += new tcpConnections.ClientsConnectionStateHandler(ClientsConnectionState);
			
			// evenement produit lorsque le serveur recoit des données
			serveur.ClientDataAvailable += new tcpConnections.DataAvailable(ClientDataAvailable);
			
			// evenement produit lorsque le serveur envoi un PING? (pas forcement utile)
			serveur.ClientPingPong += new tcpConnections.PingPong(ClientPingPong);
			
			// démarage du serveur
			serveur.Start();
		}
		
		void MainFormClosing(object sender, System.ComponentModel.CancelEventArgs e)
		{
			// arrete le serveur
			serveur.Stop();
		}
		
		// evenement produit lorsque l'etat de la connexion du serveur change
		private void ConnectionState(object sender, tcpConnections.eConnectionSate state)
		{
			// object sender : instance de la classe en cours
			// tcpConnections.eConnectionSate : enumération de l'etat de la connexion du serveur
			label1.Text = "Status du serveur: ";
			switch (state)
			{
				// le serveur est déconnecté
				case tcpConnections.eConnectionSate.Disconnected :
					label1.Text += "déconnecté";
					button1.Enabled = false;
					textBox1.Text = "";
					textBox1.Enabled = false;
					break;
				// le serveur à un problème
				case tcpConnections.eConnectionSate.Error :
					label1.Text += "une erreur est survenue";
					button1.Enabled = false;
					textBox1.Text = "";
					textBox1.Enabled = false;
					break;
				// le serveur est en écoute (etat normal)
				case tcpConnections.eConnectionSate.Listening :
					label1.Text += "en écoute";
					button1.Enabled = true;
					textBox1.Text = "";
					textBox1.Enabled = true;
					break;
			}
		}
		
		// evenement produit lorsque l'etat des connexions clientes change
		private void ClientsConnectionState(object sender, int hashcode, tcpConnections.eClientsConnectionSate state)
		{
			// object sender : instance de la classe en cours
			// int hashcode : identifiant unique du socket client
			// tcpConnections.eClientsConnectionSate : enumération de l'etat de la connexion du client
			string message = "-- " + DateTime.Now.ToLongTimeString() + " - ";
			switch (state)
			{
				// un client viend de se connecter
				case tcpConnections.eClientsConnectionSate.Connected :
					message += "Bonjour, client " + hashcode.ToString() + " !";
					listBox2.Items.Add(hashcode);
					break;
				// un client viend de se deconnecter
				case tcpConnections.eClientsConnectionSate.Disconnected :
					message += "Le client " + hashcode.ToString() + " nous dit aurevoir !";
					listBox2.Items.Remove(hashcode);
					break;					
			}
			listBox1.Items.Add(message);
			if (listBox2.Items.Count > 0)
			{
				listBox2.SelectedIndex = listBox2.Items.Count - 1;
			}
		}
		
		// evenement produit lorsque le serveur recoit des données
		private void ClientDataAvailable(object sender, int hashcode, int length, byte[] datas)
		{
			// object sender : instance de la classe en cours
			// int hashcode : identifiant unique du socket client
			// int length : taille des données recues
			// byte[] datas : données recues
			
			// les données recues sont sous forme d'un tableau d'octets pour pouvoir gérer des fichiers
			// pour savoir si une commande PONG! est recue (pour debuggage) on va tester si les 4 premiers octets correspondent au mot PONG
			string msgString = System.Text.ASCIIEncoding.ASCII.GetString(datas, 0, length);
			bool cmd = false;
			if (msgString.Length == 4)
			{
				switch (msgString.ToUpper())
				{
					case "PONG" :
						listBox1.Items.Add(">> " + DateTime.Now.ToLongTimeString() + " - " + hashcode.ToString() + " Pong!");
						listBox1.SelectedIndex = listBox1.Items.Count - 1;
						cmd = true;
						break;
				}
			}
			// sinon on affiche le message (pas de gestion de fichiers dans cet exemple)
			if (!cmd)
			{
				listBox1.Items.Add(">> " + DateTime.Now.ToLongTimeString() + " - Le client " + hashcode.ToString() + " a écrit : " + msgString);
				listBox1.SelectedIndex = listBox1.Items.Count - 1;
			}
		}
		
		// evenement produit lorsque le serveur envoi un PING? (pas forcement utile)
		private void ClientPingPong(object sender, int hashcode)
		{
			// object sender : instance de la classe en cours
			// int hashcode : identifiant unique du socket client
			listBox1.Items.Add("<< " + DateTime.Now.ToLongTimeString() + " - Ping? " + hashcode.ToString());
			listBox1.SelectedIndex = listBox1.Items.Count - 1;
		}
		
		void Button1Click(object sender, System.EventArgs e)
		{
			// sides clients sont connectés
			if (listBox2.SelectedItem != null)
			{
				// on récupere le hashcode du client cible
				int index = (int) listBox2.Items[listBox2.SelectedIndex];
				// on transforme notre message en un tableau d'octets
				byte[] msgByte = System.Text.ASCIIEncoding.ASCII.GetBytes(textBox1.Text);
				// et on l'envoi
				serveur.SendData(index, msgByte);
				textBox1.Clear();
			}
		}
		
	}
	
}

// SOURCES DU PROJET DE TEST DU CLIENT

using System;
using System.Drawing;
using System.Windows.Forms;

using tcpConnections;

namespace test
{

	public class MainForm : System.Windows.Forms.Form
	{
		private System.Windows.Forms.Button button3;
		private System.Windows.Forms.Button button2;
		private System.Windows.Forms.Button button1;
		private System.Windows.Forms.ListBox listBox1;
		private System.Windows.Forms.Label label1;
		private System.Windows.Forms.TextBox textBox1;
		private System.Windows.Forms.TextBox textBox2;
		private System.Windows.Forms.CheckBox checkBox1;
		
		// nouvelle instance du client
		private tcpClient client = new tcpClient();
		
		// flag pour la gestion de la reconnexion automatique
		private bool autorecon = false;
		
		public MainForm()
		{
			InitializeComponent();
		}
		
		[STAThread]
		public static void Main(string[] args)
		{
			Application.Run(new MainForm());
		}
		
		#region Windows Forms Designer generated code
		/// <summary>
		/// This method is required for Windows Forms designer support.
		/// Do not change the method contents inside the source code editor. The Forms designer might
		/// not be able to load this method if it was changed manually.
		/// </summary>
		private void InitializeComponent() {
			this.checkBox1 = new System.Windows.Forms.CheckBox();
			this.textBox2 = new System.Windows.Forms.TextBox();
			this.textBox1 = new System.Windows.Forms.TextBox();
			this.label1 = new System.Windows.Forms.Label();
			this.listBox1 = new System.Windows.Forms.ListBox();
			this.button1 = new System.Windows.Forms.Button();
			this.button2 = new System.Windows.Forms.Button();
			this.button3 = new System.Windows.Forms.Button();
			this.SuspendLayout();
			// 
			// checkBox1
			// 
			this.checkBox1.Location = new System.Drawing.Point(136, 32);
			this.checkBox1.Name = "checkBox1";
			this.checkBox1.Size = new System.Drawing.Size(120, 24);
			this.checkBox1.TabIndex = 7;
			this.checkBox1.Text = "auto reconnexion";
			this.checkBox1.CheckedChanged += new System.EventHandler(this.CheckBox1CheckedChanged);
			// 
			// textBox2
			// 
			this.textBox2.Enabled = false;
			this.textBox2.Location = new System.Drawing.Point(8, 272);
			this.textBox2.Name = "textBox2";
			this.textBox2.Size = new System.Drawing.Size(344, 20);
			this.textBox2.TabIndex = 4;
			this.textBox2.Text = "";
			// 
			// textBox1
			// 
			this.textBox1.Location = new System.Drawing.Point(8, 32);
			this.textBox1.Name = "textBox1";
			this.textBox1.TabIndex = 0;
			this.textBox1.Text = "192.168.0.2";
			this.textBox1.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
			// 
			// label1
			// 
			this.label1.Location = new System.Drawing.Point(8, 8);
			this.label1.Name = "label1";
			this.label1.Size = new System.Drawing.Size(424, 23);
			this.label1.TabIndex = 6;
			// 
			// listBox1
			// 
			this.listBox1.Location = new System.Drawing.Point(8, 64);
			this.listBox1.Name = "listBox1";
			this.listBox1.Size = new System.Drawing.Size(424, 199);
			this.listBox1.TabIndex = 3;
			// 
			// button1
			// 
			this.button1.Location = new System.Drawing.Point(360, 32);
			this.button1.Name = "button1";
			this.button1.TabIndex = 1;
			this.button1.Text = "connecter";
			this.button1.Click += new System.EventHandler(this.Button1Click);
			// 
			// button2
			// 
			this.button2.Enabled = false;
			this.button2.Location = new System.Drawing.Point(272, 32);
			this.button2.Name = "button2";
			this.button2.TabIndex = 2;
			this.button2.Text = "deconnecter";
			this.button2.Click += new System.EventHandler(this.Button2Click);
			// 
			// button3
			// 
			this.button3.Enabled = false;
			this.button3.Location = new System.Drawing.Point(360, 272);
			this.button3.Name = "button3";
			this.button3.TabIndex = 5;
			this.button3.Text = "envoyer";
			this.button3.Click += new System.EventHandler(this.Button3Click);
			// 
			// MainForm
			// 
			this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
			this.ClientSize = new System.Drawing.Size(440, 301);
			this.Controls.Add(this.checkBox1);
			this.Controls.Add(this.button3);
			this.Controls.Add(this.textBox2);
			this.Controls.Add(this.listBox1);
			this.Controls.Add(this.button2);
			this.Controls.Add(this.button1);
			this.Controls.Add(this.textBox1);
			this.Controls.Add(this.label1);
			this.Name = "MainForm";
			this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
			this.Text = "client";
			this.Closing += new System.ComponentModel.CancelEventHandler(this.MainFormClosing);
			this.Load += new System.EventHandler(this.MainFormLoad);
			this.ResumeLayout(false);
		}
		#endregion
		
		void MainFormLoad(object sender, System.EventArgs e)
		{
			// port par lequel le client va se connecter au serveur
			client.Port = 11000;
			
			// adresse IP du serveur.
			// ATTENTION: les serveur récupère tout seul l'adresse ip locale.
			// elle n'est pas forcement 127.0.0.1 !!!!! dans le cas d'un reseau local
			// l'ip locale est l'ip fourni soit par windows dans le cas d'une ip statique
			// soit l'ip fourni pas le serveur DHCP (machine spécialisée, routeur, etc.)
			client.Ip = textBox1.Text;
			
			// evenement produit lorsque la connexion du  client change
			client.ConnectionStateEvent += new tcpConnections.ConnectionStateHandler(ConnectionState);
			
			// evenement produit lorsque des données sont recues
			client.ClientDataAvailable += new tcpConnections.DataAvailable(ClientDataAvailable);
		}
		
		// evenement produit lorsque la connexion du  client change
		private void ConnectionState(object sender, tcpConnections.eConnectionSate state)
		{
			// object sender : instance de la classe en cours
			// tcpConnections.eConnectionSate state : énumeration de l'etat de la connexion
			label1.Text = "Status du serveur: ";
			switch (state)
			{
				// le client est déconnecté
				case tcpConnections.eConnectionSate.Disconnected :
					label1.Text += "déconnecté";
					button1.Enabled = true;
					button2.Enabled = false;
					button3.Enabled = false;
					textBox2.Text = "";
					textBox2.Enabled = false;
					listBox1.Items.Add("-- " + DateTime.Now.ToLongTimeString() + " - déconnecté !");
					listBox1.SelectedIndex = listBox1.Items.Count - 1;
					// si l'auto reconnexion est activée
					if (autorecon == true)
					{
						// alors on attend 10 secondes
						System.Threading.Thread.Sleep(10000);
						// et on relance la connexion
						Button1Click(null, null);
					}
					break;
				// le client percoit une erreur dans la connexion
				case tcpConnections.eConnectionSate.Error :
					label1.Text += "une erreur est survenue";
					button1.Enabled = true;
					button2.Enabled = false;
					button3.Enabled = false;
					textBox2.Text = "";
					textBox2.Enabled = false;
					listBox1.Items.Add("-- " + DateTime.Now.ToLongTimeString() + " - une erreur est survenue !");
					listBox1.SelectedIndex = listBox1.Items.Count - 1;
					// si l'auto reconnexion est activée
					if (autorecon == true)
					{
						// alors on attend 10 secondes
						System.Threading.Thread.Sleep(10000);
						// et on relance la connexion
						Button1Click(null, null);
					}
					break;
				// le client est connecté, tous va bien
				case tcpConnections.eConnectionSate.Connected :
					label1.Text += "connecté";
					button1.Enabled = false;
					button2.Enabled = true;
					button3.Enabled = true;
					textBox2.Text = "";
					textBox2.Enabled = true;
					listBox1.Items.Add("-- " + DateTime.Now.ToLongTimeString() + " - connecté au serveur.");
					listBox1.SelectedIndex = listBox1.Items.Count - 1;
					break;
			}
		}
		
		void Button1Click(object sender, System.EventArgs e)
		{
			listBox1.Items.Add("-- " + DateTime.Now.ToLongTimeString() + " - Tentative de connexion au serveur ...");
			listBox1.SelectedIndex = listBox1.Items.Count - 1;
			// changement du flag d'auto reconnexion suivant le choix de l'utilisateur
			autorecon = checkBox1.Checked;
			// on connecte le client
			client.Start();
		}
		
		void Button2Click(object sender, System.EventArgs e)
		{
			// pour eviter qu'apres une deco manuelle, le script relance la connexion
			autorecon = false;
			// on arrete le client
			client.Stop();
		}
		
		void Button3Click(object sender, System.EventArgs e)
		{
			// on transforme le texte en tableau d'octets
			byte[] datas = System.Text.ASCIIEncoding.ASCII.GetBytes(textBox2.Text);
			// et on l'envoi au serveur
			client.SendData(datas);
			textBox2.Clear();
		}
		
		// evenement produit lorsque des données sont recues
		private void ClientDataAvailable(object sender, int length, byte[] datas)
		{
			// object sender : instance de la classe en cours
			// int length : taille des données recues
			// byte[] datas : données recues
			
			// on transforme les données recue en chaine de caractères
			string msgString = System.Text.ASCIIEncoding.ASCII.GetString(datas, 0, length);
			// et on l'affiche
			listBox1.Items.Add(">> " + DateTime.Now.ToLongTimeString() + " - " + msgString);
			listBox1.SelectedIndex = listBox1.Items.Count - 1;
		}
		
		void MainFormClosing(object sender, System.ComponentModel.CancelEventArgs e)
		{
			// a la fermeture du formulaire, ne jamais oublier de couper le client ! 
			// et oui, a cause du multuthreading, si on ne lui dit pas de fermer la connexion
			// celle ci restera indefiniment jusqu'a plantage
			
			// pour eviter qu'apres une deco manuelle, le script relance la connexion
			autorecon = false;
			// on arrete le client
			client.Stop();
		}
		
		void CheckBox1CheckedChanged(object sender, System.EventArgs e)
		{
			// on change le flag d'auto reconnexion au choix de l'utilisateur
			autorecon = checkBox1.Checked;
		}
		
	}
	
}

Conclusion :


le serveur et le client ne sont pas commentés (encore un projet non destiné à etre ici) mais j'ai fait l'effort de commenter les projets de tests.

Codes Sources

A voir également

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.