On va maintenant coder la partie Serveur, sur laquelle les clients pourront envoyer leurs messages et duquel ils pourront recevoir les messages des autres clients.

Le Serveur va donc avoir une architecture similaire au client : il va écouter sur le port d’envoi des clients, et renvoyer les messages en Broadcast sur le port d’écoute des clients.

Voilà son apparence :

Il reprend des éléments du client. En revanche, on n’a plus la zone de saisie, ni d’adresse de destination puisque l’on envoie en broadcast. On conserve tout le reste.

Le Serveur va donc afficher dans la zone principale les messages reçus, et les renvoyer vers les clients.

  • Première modification à effectuer sur la classe Connectionpar rapport à celle du client : on passera à la méthode Connectde notre sender (de typeUdpClient) comme premier argument l’adresse de diffusion broadcast. On utilisera pour cela le champ adéquat de la classeIPAddress.

 

  • Ensuite, notre serveur va recevoir une trame UDP depuis un client, celle-ci étant modélisée par un objet de type UdpReceiveResult. Le champ Bufferva permettre de récupérer le contenu du message, le champRemoteEndPointdes informations sur celui qui a envoyé le message, notamment son adresse IP. On va construire un objet de type Messageà partir de ces informations pour les afficher sur la fenêtre principale, par l’intermédiaire d’unItemsControldont on settera l’ItemsSourceà uneObservableCollection<Message>qui sera donc un attribut du ViewModel. L’ObservableCollectionest une liste qui permet de notifier le GUI en cas d’ajout ou de suppression d’éléments. Mais attention, aucune notification n’est envoyée en cas de modification d’un élement existant.

 

  • En revanche, côté client, l’UdpReceiveResultaura commeRemoteEndPointles informations envoyés par le serveur. Hors nous ne voulons pas afficher l’IP du serveur, mais l’IP du client qui a envoyé le message. Pour cela, le serveur va sérialiser un objet Message contenant notamment l’IP du client, l’envoyer comme contenu de la trame UDP, et le client devra donc désérialiser le champ Buffer de l’objet de typeUdpReceiveResultpour l’afficher dans la fenêtre de chat.

 

  • Pour cela, on va ajouter à la classe Message un constructeur sans paramètre, condition nécessaire à une classe sérialisable.  On va ensuite coder une méthode string Serialize(), qui va permettre de sérialiser l’objet courant. On utilisera pour cela unXmlSerializeret un  StringWriter. Et on codera une méthodepublicstaticMessage DeSerialize(UdpReceiveResult udpFrame)qui permettra de générer un objet de typeMessage à partir de la trame UDP reçue. On utilisera ici le mêmeXmlSerializeret unStringReader. Le XmlSerializerpourra donc être déclaré comme attribut static de la classe comme ceci :

privatestaticXmlSerializer ser = newXmlSerializer(typeof(Message))

En effet, il permet de sérialiser / désérialiser des objets de type Message, et donc ne dépend pas de l'instance mais de la classe.

Vous mettrez un breakpoint dans votre code (raccourci F9) après l’appel à la méthode Serialize de votre XmlSerializer pour voir le contenu de votre StringWriter. Vous pouvez voir le contenu en faisant un clic droit sur votre objet, et en affichant « espion express »

 

Nous allons ensuite coder un petit module de recherche dans l’historique. Comme vous pouvez le voir sur le screenshot du serveur, on aura une TextBox pour le champ de recherche, une ComboBox permettant à l’utilisateur de chercher soit dans les IP/hostname , soit dans le contenu des messages, et un bouton permettant de déclencher la recherche.

  • On commencera par coder une classe SearchAttribute très simple. Elle contiendra cet enum :

publicenumeSearchAttribute

{

   eContent = 0,

   eSender

}

Et deux champs, Index, du type de cet enum, et Display de type string.

Elle sera utilisée pour gérer la ComboBox

 

  • On ajoutera un nouvel élément de type Window à notre projet, que l’on nommera SearchResult. Cette fenêtre sera appelée au clic sur le bouton Search In grâce à la méthode ShowDialog() et affichera les résultats de la recherche. Elle contiendra un ItemsControldont on définira l’ ItemsSourcesur la liste qui contiendra les résultats de la recherche.

 

  • On ajoutera ces 3 attributs à notre ViewModel :

publicObservableCollection<Message> HistorySearchResult { get; privateset; }

publicICollectionView SearchAttributes { get; privateset; }

publicSearchAttribute.eSearchAttribute SearchAttributeIndex { get; set; }

Le premier contiendra une liste de Message vérifiant les conditions de la recherche, le second sera la liste des méthodes de recherche disponibles, à savoir par Sender / par Contenu, et servira donc à remplir la ComboBox, le dernier sera l’index de l’élément sélectionné dans la ComboBox. Sur la ComboBox, on utilisera à bon escient les attributs ItemsSource,SelectedValue,SelectedValuePath et DisplayMemberPath.

 

  • Pour la recherche proprement dite, on pourra utiliser un sous ensemble du C# : le langage LINQ. Il permet de requêter « à la SQL » sur des collections, comme notre liste de Message par exemple. Par exemple, si MessageHistorycontient la liste des Message arrivés sur le serveur, ettextToSearchle texte à chercher entré par l’utilisateur,  on peut écrire :

MessageHistory.Where(x => x.Sender == textToSearch)

X représente un élément de la liste, donc un Message, et on applique un filtre Where dessus permettant de ne garder que les Message dont le Sender correspond au texte entré par l’utilisateur. On peut ensuite convertir le résultat en une nouvelle liste.

 

Enfin, on finira par afficher un graphique temps réel permettant de voir combien de message sont arrivés par Sender. On utilisera pour cela le package LiveCharts.

Dans Visual, allez dans Outils, puis dans Gestionnaire de package NuGet. Sélectionnez Gérer les packages NuGet pour la solution. Dans la catégorie En Ligne, chercher « LiveCharts », sélectionner LiveCharts.Wpf, installez le pour le projet Server. Vous pouvez voir que deux assemblies ont été ajoutées dans les références. Elles contiennent les classes LiveCharts que l’on va pouvoir manipuler pour créer notre graphique.

 

Partagez l’écran principal en deux. Le haut contiendra les messages qui arrivent, le bas le graphique :

Le graph sera un élément CartesianChart que l'on ajoutera au xaml. Un bon exemple de ce qu'il faut faire est disponible ici :https://lvcharts.net/App/examples/v1/wpf/Basic%20Column

Il faut fortement s'en inspirer pour notre besoin. Par contre, il ne faudra pas utiliser des intcomme valeurs du graph comme sur cet exemple, mais des ObservableValue car cette classe permet de notifier le GUI en cas de changement de valeur, ce qui permettra de rafraichir le graph au fur et à mesure de l'arrivée des messages.

Pour connaitre la posiiton d’un sender (représenté par son IP) sur l’axe des abscisses, on utilisera un attribut privateDictionary<string, int> ipIndexGraph, qui permettra pour une ip donnée (string) de connaitre son index (int) sur l’axe.