Créer un serveur TCP en C Sharp
Un article de LaPageDuJour.
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 ) ); } } } }