[WP7] Utilisation des sockets dans une application Wake-on-Wan
Une des fonctionnalités les plus attendues dans Mango est probablement l’arrivée des Sockets. En effet, sur NoDo le seul protocole supporté est HTTP, ce qui oblige à passer par un serveur tiers dès qu’on veut gérer un scénario demandant un autre protocole (messagerie instantanée par exemple). Pour palier à ce manque, Mango apporte donc une classe Socket, permettant de transmettre et recevoir des données en utilisant les protocoles TCP et UDP. De quoi rendre possible toute une panoplie de nouveaux scénarios.
Je me propose ici d’illustrer le fonctionnement de cette classe au travers d’une application de Wake-on-Wan.
Wake-on-Wan
Qu’est-ce que Wake-on-Wan ? La plupart des carte mères récentes proposent d’allumer le PC lorsqu’un paquet de données spécifique est reçu sur le réseau (communément appelé “Magic Packet”). L’intérêt étant de pouvoir démarrer un ou plusieurs ordinateurs à distance. Cette fonctionnalité est à l’origine prévue pour fonctionner sur un réseau local (Wake-on-LAN), mais si votre routeur le permet vous pouvez également déclencher l’allumage à distance via Internet (Wake-on-WAN). Très pratique si vous couplez cela à un logiciel d’accès à distance, comme Weezo ou Orb, pour accéder à vos fichiers d’où vous voulez sans pour autant laisser votre ordinateur allumé toute la journée.
Configurer le Wake-On-Wan peut être complexe, et peut demander d’intervenir au niveau du BIOS, de l’OS, et du routeur. Je ne m’attarderai donc pas sur ce point, sans compter que d’excellents tutoriaux existent. Nous allons donc partir du principe que vous disposez d’une machine déjà configurée.
Pour simplifier les tests, je recommande l’utilisation de l’application “Wake On Lan Monitor”, qui se charge de détecter l’arrivée du Magic Packet et d’afficher son contenu. Cela évite d’avoir un ordinateur à allumer/éteindre en permanence.
Implémentation
Revenons à notre application. Pour transmettre le magic packet, nous avons besoin de connaître l’adresse ip de l’ordinateur cible, l’adresse MAC de son adaptateur réseau, et le numéro du port écouté par son routeur. Nous faisons donc une interface permettant de saisir toutes ces informations, et nous ajoutons un bouton “Send” :
1: <phone:PhoneApplicationPage
2: x:Class="WakeOnWan.MainPage"
3: xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
4: xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
5: xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
6: xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
7: xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
8: xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
9: mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
10: FontFamily="{StaticResource PhoneFontFamilyNormal}"
11: FontSize="{StaticResource PhoneFontSizeNormal}"
12: Foreground="{StaticResource PhoneForegroundBrush}"
13: SupportedOrientations="Portrait" Orientation="Portrait"
14: shell:SystemTray.IsVisible="True">
15:
16: <!--LayoutRoot is the root grid where all page content is placed-->
17: <Grid x:Name="LayoutRoot" Background="Transparent">
18: <Grid.RowDefinitions>
19: <RowDefinition Height="Auto"/>
20: <RowDefinition Height="*"/>
21: </Grid.RowDefinitions>
22:
23: <!--TitlePanel contains the name of the application and page title-->
24: <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
25: <TextBlock x:Name="ApplicationTitle" Text="Wake-on-WAN" Style="{StaticResource PhoneTextNormalStyle}"/>
26: </StackPanel>
27:
28: <!--ContentPanel - place additional content here-->
29: <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
30: <StackPanel>
31: <TextBlock Text="IP: " />
32: <TextBox x:Name="TextIp" Text="82.226.215.135" />
33:
34: <TextBlock Text="MAC Address:" />
35: <TextBox x:Name="TextMac" Text="00-26-18-3A-EF-C6" />
36:
37: <TextBlock Text="Port:" />
38: <TextBox x:Name="TextPort" Text="9" />
39:
40: <Button Content="Send" x:Name="ButtonSend" Click="ButtonSend_Click" />
41: </StackPanel>
42: </Grid>
43: </Grid>
44:
45: </phone:PhoneApplicationPage>
Côté code, nous allons considérer que l’utilisateur entre des données correctes, et sauter toute l’étape de validation (chose bien entendu à ne pas faire sur une application commerciale).
1: private void ButtonSend_Click(object sender, RoutedEventArgs e)
2: {
3: string ip = this.TextIp.Text;
4: string mac = this.TextMac.Text;
5: int port = int.Parse(this.TextPort.Text);
Le magic packet est composé de 102 octets : six fois “0xFF”, suivi de seize fois l’adresse MAC. Nous commençons donc par transformer l’adresse MAC en un tableau de bytes :
1: var macAddress = mac.Split('-')
2: .Select(b => byte.Parse(b, System.Globalization.NumberStyles.AllowHexSpecifier))
3: .ToArray();
Puis nous construisons le paquet de données :
1: var magicPacket = new List<Byte>(6 + 16 * 6);
2:
3: for (int i = 0; i < 6; i++)
4: {
5: magicPacket.Add(0xFF);
6: }
7:
8: for (int i = 0; i < 16; i++)
9: {
10: magicPacket.AddRange(macAddress);
11: }
Ceci fait, nous pouvons nous occuper de l’envoi proprement dit. Nous commençons par instancier un Socket, en indiquant que nous allons utiliser une adresse ipv4 et le protocole UDP :
1: var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
Puis nous allons utiliser la méthode “SendToAsync” pour envoyer les données. Celle-ci est parfaite pour les scénarios simples de ce genre, car elle permet de s’affranchir de l’étape de connexion. Pour les scénarios plus complexes, il faudra utiliser les méthodes “ConnectAsync”, “SendAsync”, et “ReceiveAsync”.
La méthode “SendToAsync” prend en paramètre un object “SocketAsyncEventArgs”, contenant les données à envoyer, la destination, et exposant un évènement “Completed” qui sera appelé lorsque l’envoi sera terminé.
1: var eventArgs = new SocketAsyncEventArgs();
2: eventArgs.RemoteEndPoint = new IPEndPoint(IPAddress.Parse(ip), port);
3: eventArgs.Completed += this.SocketCompleted;
4:
5: eventArgs.SetBuffer(magicPacket.ToArray(), 0, magicPacket.Count);
Nous pouvons maintenant appeler la méthode. Elle renvoie un booléen indiquant si l’opération sera exécutée de manière synchrone ou asynchrone. Si l’opération est exécutée de manière synchrone, l’évènement “Completed” ne sera pas déclenché, et nous appelons donc la méthode correspondante à la main :
1: bool isAsync = socket.SendToAsync(eventArgs);
2:
3: if (!isAsync)
4: {
5: this.SocketCompleted(socket, eventArgs);
6: }
Enfin, dans SocketCompleted, nous vérifions que l’opération s’est bien déroulée, et nous affichons un message en conséquence. Nous n’oublions également pas de libérer les ressources du Socket :
1: private void SocketCompleted(object sender, SocketAsyncEventArgs e)
2: {
3: if (e.SocketError != SocketError.Success)
4: {
5: this.Dispatcher.BeginInvoke(() => MessageBox.Show("An error occured while sending the magic packet: " + e.SocketError));
6: }
7: else
8: {
9: this.Dispatcher.BeginInvoke(() => MessageBox.Show("Sending successful!"));
10: }
11:
12: var socket = (Socket)sender;
13:
14: socket.Close();
15: socket.Dispose();
16: }
Et c’est tout ! Il ne reste plus qu’à compiler, lancer, et tester. A noter que j’ai dû déployer l’application sur mon téléphone pour que le magic packet soit bien reçu. Je ne sais pas si c’est causé par un mauvais réglage de mon firewall, de l’émulateur, ou une raison plus obscure liée au fonctionnement du Wake-on-LAN (l’ordinateur envoyant et recevant le paquet étant le même dans mon cas).

Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :