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