Class - tcp client / serveur

Soyez le premier à donner votre avis sur cette source.

Vue 32 955 fois - Téléchargée 3 753 fois

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

Ajouter un commentaire

Commentaires

Messages postés
10
Date d'inscription
mardi 17 juin 2008
Statut
Membre
Dernière intervention
8 juillet 2008

Salut,

je cherche a realiser un projet de type serveur client. Ce code est tres bien, mais pas encore adapter a mes besoins.
Mon serveur doit recevoir et lire tous les messages, mais aussi pouvoir ecrire a un client en particulier grace a son IP.
De plus je dois effectuer tout ca pour l'integrer a des .dll pour l'utiliser facilement dans plusieurs projets futur.

Je sais que ce code est vieux, mais je n'arrive pas a faire les modification necessaire tout seul. Alors de l'aide serai vraiment la bienvenu.

Merci.
Utilisateur anonyme
Je sais que ce source est ici depuis plus de deux ans et n'a pas été commenté depuis, mais l'ayant parcouru et analysé pour faire moi-même un serveur TCP, je poste mes remarques qui peuvent toujours servir à quelqu'un qui voudrait utiliser ce source (qui reste de très bonne facture).

- je confirme évidemment la remarque de MR_LAPINOU concernant l'emplacement du Thread.Sleep

- la modification du 31 juillet 2005 17:30:07 n'est pas intégrée dans les sources téléchargeables

- le thread de lecture de données qui fait tourner la méthode ReceiveData pose problème lorsqu'une donnée de taille importante est envoyée sur par un client TCP (image, fichier, etc.) : tant qu'il reste des données à lire, le thread continue de lire sur la même socket client sans même essayer de lire les autres ; il y a risque de perte de données si le tampon d'entrée se remplit pendant ce temps pour les autres sockets...

- cette-même méthode ReceiveData ne possède pas de mécanisme pour rattrapper les exceptions (la méthode jumelle présente dans le client TCP le fait)

- les verrous de lecture et d'écriture auraient tout intérêt à ne pas être globaux au serveur mais spécifiques à chacun des clients : les déconnexions de certains clients risquent de ne pas pouvoir être vues (car pas testées) si l'activité du serveur est importante

- le ping/pong implémenté étant spécifique à ce client/serveur (le "vrai" ping/pong correspond à deux messages ICMP), il devrait pouvoir être désactivé pour que le serveur puisse être utilisé avec un client TCP dont on ne maitrise pas le code (tiers, externe, fermé, etc.). Par exemple, une valeur de 0 en timeout peut indiquer qu'on ne lance pas le thread qui effectue ces pings/pongs

- en tout état de cause, ce serveur TCP est une base qui peut servir dans des cas où la communication est légère : peu de données, peu de clients ; un pool de threads serait bien meilleur si de nombreux clients doivent se connecter, pour éviter trop de latence.
Messages postés
564
Date d'inscription
jeudi 21 février 2002
Statut
Membre
Dernière intervention
7 novembre 2008

Oui ce petit sleep est tres sensible. Il permet de faire mieux fonctionner la source sur un réseau lent !
Messages postés
2
Date d'inscription
mercredi 26 janvier 2005
Statut
Membre
Dernière intervention
12 novembre 2005

Bonjour les gens,

Pour ceux que ca interessent, sur ce code il y a un petit bug qui surcharge le serveur.
Dans la classe Server, et la méthode ReceiveData le Thread.Sleep(10) est placé une } trop basse, remontez le et ca ira mieux pour votre processeur.

A+
Messages postés
2
Date d'inscription
mercredi 26 janvier 2005
Statut
Membre
Dernière intervention
12 novembre 2005

Je viens de regarder ta lib qui est ma fois sympathique et pratique, mais j'ai un petit soucis dont je me suis appercu apres implementation, ta classe serveur bouffe toute la ressource CPU.
Connais tu ce problème ? As tu une solution ?
Sinon je vais regarder de mon coté ...
Afficher les 6 commentaires

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.