Utilisation d'un socket de serveur asynchrone
Les sockets de serveur asynchrones utilisent le modèle de programmation asynchrone de .NET Framework pour traiter les demandes de services réseau. La classe Socket suit le modèle d'affectation de noms asynchrone standard de .NET Framework ; par exemple, la méthode synchrone Accept correspond aux méthodes asynchrones BeginAccept et EndAccept.
Un socket de serveur asynchrone nécessite une méthode pour commencer à accepter des demandes de connexion émanant du réseau, une méthode de rappel pour traiter les demandes de connexion et commencer à recevoir des données du réseau, et une méthode de rappel pour mettre fin à la réception de données. Toutes ces méthodes sont décrites plus en détail dans cette section.
Dans le code exemple qui suit, pour commencer à accepter des demandes de connexion émanant du réseau, la méthode StartListening initialise la classe Socket, puis utilise la méthode BeginAccept pour commencer à accepter de nouvelles connexions. La méthode de rappel d'acceptation est appelée lorsqu'une nouvelle demande de connexion est reçue sur le socket. Elle est chargée d'obtenir l'instance Socket qui gérera la connexion et de remettre cette instance Socket au thread qui traitera la demande. La méthode de rappel d'acceptation implémente le délégué AsyncCallback ; elle retourne void et prend un paramètre unique de type IAsyncResult. Le code exemple suivant est le shell d'une méthode de rappel d'acceptation.
La méthode BeginAccept prend deux paramètres, un délégué AsyncCallback qui pointe sur la méthode de rappel d'acceptation et un objet qui est utilisé pour passer des informations d'état à la méthode de rappel. Dans le code exemple qui suit, le Socket à l'écoute est passé à la méthode de rappel via le paramètre state. Ce code exemple crée un délégué AsyncCallback et démarre l'acceptation des connexions à partir du réseau.
Les sockets asynchrones utilisent des threads du pool de threads système pour traiter les connexions entrantes. Un thread est chargé d'accepter les connexions, un autre est utilisé pour traiter chaque connexion entrante et un autre encore est responsable de la réception des données issues de la connexion. Il pourrait s'agir du même thread, selon le thread assigné par le pool de threads. Dans le code exemple qui suit, la classe System.Threading.ManualResetEvent suspend l'exécution du thread principal et indique quand l'exécution peut reprendre.
Le code exemple suivant illustre une méthode asynchrone qui crée un socket TCP/IP asynchrone sur l'ordinateur local et démarre l'acceptation des connexions. Il suppose qu'il existe un ManualResetEvent global nommé allDone, que la méthode est un membre d'une classe nommée SocketListener et qu'une méthode de rappel nommée acceptCallback est définie.
public void StartListening() { IPHostEntry ipHostInfo = Dns.Resolve(Dns.GetHostName()); IPEndPoint localEP = new IPEndPoint(ipHostInfo.AddressList[0],11000); Console.WriteLine("Local address and port : {0}",localEP.ToString()); Socket listener = new Socket( localEP.Address.AddressFamily, SocketType.Stream, ProtocolType.Tcp ); try { listener.Bind(localEP); listener.Listen(10); while (true) { allDone.Reset(); Console.WriteLine("Waiting for a connection..."); listener.BeginAccept( new AsyncCallback(SocketListener.acceptCallback), listener ); allDone.WaitOne(); } } catch (Exception e) { Console.WriteLine(e.ToString()); } Console.WriteLine( "Closing the listener..."); }
La méthode de rappel d'acceptation (acceptCallback dans l'exemple précédent) est chargée d'indiquer au thread principal de l'application de reprendre le traitement, d'établir la connexion avec le client et de commencer la lecture asynchrone des données à partir du client. Le code exemple suivant correspond à la première partie d'une implémentation de la méthode acceptCallback. Cette section de la méthode indique au thread principal de l'application de reprendre le traitement et d'établir la connexion avec le client. Il suppose un ManualResetEvent global appelé allDone.
public void acceptCallback(IAsyncResult ar) { allDone.Set(); Socket listener = (Socket) ar.AsyncState; Socket handler = listener.EndAccept(ar); // Additional code to read data goes here. }
La lecture de données à partir d'un socket client exige un objet d'état qui passe des valeurs entre les appels asynchrones. Le code exemple suivant implémente un objet d'état pour la réception d'une chaîne émanant du client distant. Il contient des champs pour le socket client, une mémoire tampon pour la réception de données et un StringBuilder pour la création d'une chaîne de données envoyée par le client. Le fait de placer ces champs dans l'objet d'état permet de conserver leurs valeurs entre plusieurs appels de lecture de données à partir du socket client.
public class StateObject { public Socket workSocket = null; public const int BufferSize = 1024; public byte[] buffer = new byte[BufferSize]; public StringBuilder sb = new StringBuilder(); }
La section de la méthode acceptCallback qui démarre la réception des données à partir du socket client initialise d'abord une instance de la classe StateObject, puis appelle la méthode BeginReceive pour commencer à lire les données à partir du socket client en mode asynchrone.
Le code exemple suivant illustre la méthode acceptCallback complète. Il suppose qu'un ManualResetEvent global nommé allDone, existe, que la classe StateObject est définie et que la méthode readCallback est définie dans une classe nommée SocketListener.
public static void acceptCallback(IAsyncResult ar) {
// Get the socket that handles the client request.
Socket listener = (Socket) ar.AsyncState;
Socket handler = listener.EndAccept(ar);
// Signal the main thread to continue.
allDone.Set();
// Create the state object.
StateObject state = new StateObject();
state.workSocket = handler;
handler.BeginReceive( state.buffer, 0, StateObject.BufferSize, 0,
new AsyncCallback(AsynchronousSocketListener.readCallback), state);
}
La dernière méthode qui doit être implémentée pour le serveur socket asynchrone est la méthode de rappel de lecture qui retourne les données envoyées par le client. À l'instar de la méthode de rappel d'acceptation, la méthode de rappel de lecture est un délégué AsyncCallback. Cette méthode copie par une opération de lecture un ou plusieurs octets à partir du socket client vers la mémoire tampon de données, puis appelle de nouveau la méthode BeginReceive jusqu'à la réception complète des données envoyées par le client. Une fois que le message entier a été lu à partir du client, la chaîne est affichée sur la console et le socket de serveur qui traite la connexion avec le client est fermé.
Le code exemple suivant implémente la méthode readCallback. Il suppose que la classe StateObject est définie.
public static void readCallback(IAsyncResult ar) { StateObject state = (StateObject) ar.AsyncState; Socket handler = state.WorkSocket; // Read data from the client socket. int read = handler.EndReceive(ar); // Data was read from the client socket. if (read > 0) { state.sb.Append(Encoding.ASCII.GetString(state.buffer,0,read)); handler.BeginReceive(state.buffer,0,StateObject.BufferSize, 0, new AsyncCallback(readCallback), state); } else { if (state.sb.Length > 1) { // All the data has been read from the client; // display it on the console. string content = state.sb.ToString(); Console.WriteLine("Read {0} bytes from socket.\n Data : {1}", content.Length, content); } handler.Close(); } }