Créer un serveur TCP en C Sharp

Un article de LaPageDuJour.

Aller à : Navigation, Rechercher

Sommaire

[modifier] Préambule

[modifier] Les questions de bases

  • Pourquoi un serveur TCP ?

C'est utilisé partout

  • Pourquoi en C# .Net?

C'est très pratique pour faire toute sorte de traitement

  • Est-ce que le C# est rapide ?

Suffisament rapide pour les besoins classiques d'un serveur réseau

  • Est-ce que le C# .Net est portable ?

Oui cela tourne "partout", grâce à Mono.

[modifier] Choix de programmation

  • callbacks et évènements : C'est le BIEN en informatique. Ils permettent d'éviter la surutilisation des threads et garantissent un traitement immédiat et asynchrone de l'information.
  • Lignes textuelles : Notre serveur TCP ne fera que recevoir des lignes par readLine, ce qui va nous obliger à créer un thread de réception par client. Si nous recevions des données brutes, nous aurions pu nous en passer en n'utilisant que des callbacks.

[modifier] Code

Ici j'ai fait deux classes. Une classe pour gérer le réseau comme des éléments tous simples (un identifiant par client et une ligne si il y a réception de données), ainsi qu'une classe pour traiter les données.

A partir de code là, créer un petit serveur implémentant quelques commandes IRC minimales est extrêmement facile.

[modifier] Classe réseau

Voici une petite classe que j'ai concoctée pour recevoir les données depuis une multitude de clients. Si vous voulez l'utiliser, il vous faudra supprimer les références à Logging.Log( String line); ou recréer une classe de gestion des logs, ce qui n'est vraiment pas très compliqué.

namespace EventNetServer {
	public class TcpServer {
		/// <summary>
		/// Le client réseau
		/// </summary>
		public class Client {
			static private		Int32			_autoIncrement		= 0;
			private			Int32			_id			= ++_autoIncrement;
			private			TcpServer		_father;
			private			TcpClient		_tcpClient;
			private			StreamReader	        _tcpR;
			private			StreamWriter	        _tcpW;
			private			Thread			_thread;
			private			Boolean			_threadLoop	        = true;
 
			public override String ToString() {
				return "EventNetServer.Client@"+_id;
			}
 
			/// <summary>
			/// L'identifiant du client
			/// </summary>
			public Int32 Id {
				get {
					return _id;
				}
			}
 
			public void Log( String line ) {
				Logging.Log( this.ToString()+"."+line );
			}
 
			/// <summary>
			/// Le constructeur
			/// </summary>
			/// <param name="tcpClient">L'objet TcpClient</param>
			/// <param name="father">Le TcpServer auquel il est rattaché</param>
			public Client( TcpClient tcpClient, TcpServer father ) {
				Log( "Client( ... , ... );" );
				_tcpClient = tcpClient;
				_father = father;
 
				_tcpR = new StreamReader( _tcpClient.GetStream() );
				_tcpW = new StreamWriter( _tcpClient.GetStream() );
 
				_thread = new Thread( Thread_Reception );
				_thread.IsBackground = true;
				_thread.Name = "Client@"+_id+".Thread_Reception()";
 
				_father.LaunchClientConnectedEvent( _id );
 
				_thread.Start();
			}
 
			/// <summary>
			/// Déconnexion du client
			/// </summary>
			public void Disconnect() {
				Logging.Log( "Disconnect();" );
				try {
					_tcpR.Close();
					_tcpR = null;
					_tcpW.Close();
					_tcpW = null;
					_tcpClient.Close();
					_tcpClient = null;
				} catch { }
				try {
					_father.LaunchClientDisconnectedEvent( _id );
					_father._listeClients.Remove( _id );
					_threadLoop = false;
				} catch ( Exception ex ) {
					Log( "Disconnect : ex : "+ex );
				}
			}
 
			/// <summary>
			/// Thread de réception du client
			/// </summary>
			private void Thread_Reception() {
				Log( "Thread_Reception();" );
				while ( _threadLoop ) {
					try {
						String line = _tcpR.ReadLine();
						if ( line == null ) {
							Disconnect();
							break;
						}
						Log( "Thread_Reception : line = \""+line+"\"" );
						_father.LaunchLineReceivedFromClient( _id, line );
					} catch ( Exception ex ) {
						Log( "Thread_Reception : ex : "+ex );
						Disconnect();
					}
				}
			}
 
			internal Boolean WriteLine( String t ) {
				try {
					_tcpW.WriteLine( t );
					return true;
				} catch ( Exception ex ) {
					Logging.Log( this.ToString()+"@"+_id+".WriteLine : ex : "+ex );
					return false;
				}
			}
 
		}
 
 
		Int32								_port				= 9000;
		TcpListener							_tcpListener		= null;
		internal Dictionary<Int32,Client>	_listeClients		= new Dictionary<Int32,Client>();
 
		/// <summary>
		/// Constructeur
		/// </summary>
		public TcpServer() {
			Logging.Log(this.ToString()+"@"+_port+".EventTcpNetServer();");
		}
 
		/// <summary>
		/// Constructeur avec spécification du port d'écoute
		/// </summary>
		/// <param name="port">Port d'écoute</param>
		public TcpServer( Int32 port ) {
			Logging.Log( this.ToString()+"@"+_port+".EventTcpNetServer( "+port+" );" );
			_port = port;
		}
 
		/// <summary>
		/// Lancement de l'écoute des clients sur le serveur
		/// </summary>
		/// <returns>Si l'écoute a pu être lancé</returns>
		public Boolean StartListening() {
			Logging.Log( this.ToString()+"@"+_port+".StartListening();" );
 
			_tcpListener = new TcpListener( System.Net.IPAddress.Any, _port );
			_tcpListener.Start();
 
			_tcpListener.BeginAcceptTcpClient( new AsyncCallback( DoAcceptTcpClientCallback ), _tcpListener );
 
			return true;
		}
 
		/// <summary>
		/// Callback de traitement d'une connexion accepté
		/// </summary>
		/// <param name="ar">Données transmises au callback</param>
		private void DoAcceptTcpClientCallback( IAsyncResult ar ) {
 
			Logging.Log( this.ToString()+"@"+_port+".DoAcceptTcpClientCallback( ... );" );
 
			// Get the listener that handles the client request.
			TcpListener listener = (TcpListener) ar.AsyncState;
			TcpClient tcpClient = (TcpClient) listener.EndAcceptTcpClient( ar );
 
			Client client = new Client( tcpClient, this );
 
			_listeClients.Add( client.Id, client );
 
			_tcpListener.BeginAcceptTcpClient( new AsyncCallback( DoAcceptTcpClientCallback ), _tcpListener );
 
		}
 
		/// <summary>
		/// Arrêt de l'écoute des clients sur le serveur
		/// </summary>
		/// <returns></returns>
		public Boolean StopListening() {
			Logging.Log( this.ToString()+"@"+_port+".StopListening();" );
			if ( _tcpListener != null ) {
				_tcpListener.Stop();
				_tcpListener = null;
			}
 
			foreach( KeyValuePair<Int32,Client> kvp in _listeClients ) {
				kvp.Value.Disconnect();
				_listeClients.Remove( kvp.Key );
			}
 
			return true;
		}
 
		/// <summary>
		/// Pour envoyer une ligne à un client
		/// </summary>
		/// <param name="clientId">Client à qui envoyer une ligne</param>
		/// <param name="line">Ligne à envoyer</param>
		/// <returns>Si la ligne pu être envoyée</returns>
		public Boolean WriteLine( Int32 clientId, String line ) {
			return _listeClients[ clientId ].WriteLine( line );
		}
 
		/// <summary>
		/// Port sur lequel le serveur écoute
		/// </summary>
		public Int32 Port {
			get {
				return _port;
			}
			set {
				Logging.Log( this.ToString()+"@"+_port+".Port="+value );
				_port = value;
			}
		}
 
		public delegate void clientConnectedEventHandler( Int32 clientId );
 
		/// <summary>
		/// Evènement de connexion d'un client
		/// </summary>
		public event clientConnectedEventHandler clientConnectedEvent;
		internal void LaunchClientConnectedEvent( Int32 clientId ) {
			Logging.Log( this.ToString()+"@"+_port+".LaunchClientConnectedEvent( "+clientId+");" );
			if ( clientConnectedEvent != null )
				clientConnectedEvent( clientId );
		}
 
		public delegate void clientDisconnectedEventHandler( Int32 clientId );
 
		/// <summary>
		/// Evènement de déconnexion d'un client
		/// </summary>
		public event clientDisconnectedEventHandler clientDisconnectedEvent;
		internal void LaunchClientDisconnectedEvent( Int32 clientId ) {
			Logging.Log( this.ToString()+"@"+_port+".LaunchClientDisconnectedEvent( "+clientId+");" );
			if ( clientDisconnectedEvent != null )
				clientDisconnectedEvent( clientId );
		}
 
		public delegate void lineReceivedFromClientHandler( Int32 clientId, String line );
 
		/// <summary>
		/// Evènement de réception d'un message depuis un client
		/// </summary>
		public event lineReceivedFromClientHandler lineReceivedFromClient;
		internal void LaunchLineReceivedFromClient( Int32 clientId, String line ) {
			Logging.Log( this.ToString()+"@"+_port+".LaunchLineReceivedFromClient( "+clientId+", \""+line+"\" );" );
			if ( lineReceivedFromClient != null )
				lineReceivedFromClient( clientId, line );
		}
	}
}

[modifier] Serveur réseau

namespace Projet {
    class Program {
          static void Main(String[] args) {
              Serveur.Serveur = new Serveur.Serveur( 3000 );
              while ( true ) Thread.Sleep( 1000 );
          }
    }
 
}
 
namespace Serveur
{
    class Serveur
    {
        class Client {
             int _id;
             Client( int id ) {
                 _id = id;
             }
 
             public int Id {
                 get {
                     return _id;
                 }
             }
        }
 
        EventNetServer.TcpServer    _serveurTcp
        Dictionary<Int32,MBBalise>	_clients
        public Serveur( Int32 port )
        {
            _serveurTcp   = new EventNetServer.TcpServer( port );
            _clients      = new Dictionary<Int32,Client>();
 
            _serveurTcp.clientConnectedEvent    += new EventNetServer.TcpServer.clientConnectedEventHandler( ClientConnected );
            _serveurTcp.clientDisconnectedEvent += new EventNetServer.TcpServer.clientDisconnectedEventHandler( ClientDisconnected );
            _serveurTcp.lineReceivedFromClient  += new EventNetServer.TcpServer.lineReceivedFromClientHandler( ClientSent );
            _serveurTcp.StartListening();
        }
 
        // Connexion d'un client
        void ClientConnected( Int32 clientId ) {
            _clients.Add( cliendId, new Client( clientId ) );
        }
 
        // Déconnexion d'un client
        void ClientDisconnected( Int32 clientId ) {
            _clients.Remove( clientId );
        }
 
        // Message d'un client
        void ClientSent( Int32 clientId, String line ) {
            // Petite faille, si un client tape "SAY ALL SAY ALL SAY ALL SAY ALL SAY ALL", il va y avoir un sacré écho
            if ( line.substring(0, "SAY ALL ".Length) == "SAY ALL " ) {
                String whatToSay = line.substring("SAY ALL ".Length);
                foreach( KeyValuePair<Int32,Client> kvp in _clients ) {
                    _serveur.WriteLine( kvp.Key, whatToSay );
                }
            }
            else if ( line.substring(0, "PING ".Length) == "PING " ) {
                 _serveur.WriteLine( clientId, "PONG "+line.substring( "PING ".Length ) );
            }
        }
    }
 
 
}