1. Introduction

S'il est vrai que la communication entre individus est un point commun à la quasi-totalité des espèces terrestres, l'Humain ne s'est pas restreint au seul usage de la parole, des phéromones ou des signes mais a continuellement cherché comment améliorer l'échange d'information. Effectivement, il est possible de remonter 15'000 ans av. JC pour observer d'anciennes apparitions d'"écriture" avec, par exemple, les fameuses peintures des grottes de Lascaux (Figure 1) [1]. De ces premiers essais émanent plusieurs nécessité, la première est le besoin d'archiver, d'enregistrer l'information de manière à la conserver mais aussi afin de la transmettre. C'est cet aspect qui nous intéresse, celui de l'échange d'information. Ainsi les outils de communications passeront de tablettes de pierre à papiers et de burins à plume. Ces évolutions ne sont pas dû au hasard car elles améliorent notre capacité à transmettre de l'information. Le papier est plus léger, plus compact, plus simple à produire, de plus l'encre permet une plus grande rapidité d'écriture et même si le format du courrier resta le moyen usuel de communication pendant des années, les 3 derniers siècles virent apparaître un grand nombre d'innovation.

Grotte-de-Lascaux.jpg

           Figure 1: Photo des grottes de Lascaux A

Premièrement, l'utilisation du télégraphe augmenta significativement le temps de transmission des messages car jusque-là, les courriers étaient transportés par des coursiers. Malheureusement, le télégraphe optique empêcha l'envoi de message trop complexe étant donné que le nombre de mots utilisables était limité [2]. Pour le télégraphe électrique il fût possible d'envoyer n'importe quelle lettres de l'alphabet mais il fallait traduire le code ce qui pouvait prendre beaucoup de temps [3]. Ces limitations mirent en évidence un enjeu décisif, comment communiquer des volumes plus importants de données ? Bien que le téléphone fût une véritable révolution de ce côté étant donné qu'il permettait la communication vocale direct entre deux individus, ce dispositif demande que les personnes en question soient disponibles. L'échange est direct. Mais l'installation des lignes téléphoniques permit en outre le fonctionnement du télécopieur (faxe) qui augmenta grandement la vitesse de transmission, la distance qui pouvait séparer les interlocuteurs ainsi que le volume de données transmissibles. Une fois de plus, une grande avancée qui ne dura que peu de temps car il fût vite dépassé par le courrier électronique (mail) qui remplaça progressivement le faxe. Cette fois, les avancées technologiques aidèrent à réduire le matériel nécessaire à l'échange d'un message mais surtout d'atteindre enfin le caractère instantané du message. Une fois que celui-ci est envoyé il suffit de quelques secondes pour que le destinataire le reçoive. Ainsi, la vitesse de transmission devient un paramètre mineur de même pour la précision étant donné que depuis le télégraphe électrique, il est possible de communiquer avec l'alphabet classique. Finalement, les dernières innovations adoptent des enjeux bien différents. L'apparition du SMS augmenta l'accessibilité à un service de messagerie et finalement les messageries instantanées permirent de conduire un dialogue interactif.

Ainsi, après des décennies d'évolution, les enjeux inhérents aux moyens de communication ont totalement changé car de nos jour l'objectif principal est d'augmenter le volume de données transmissibles mais pas seulement. Effectivement, une nouvelle problématique semble s'ajouter de plus en plus aux débats actuels et il concerne la sécurité De nos jours les conversations privées n'ont jamais été aussi publique. L'information passe par des centaines de serveurs à travers le monde et toutes nos conversations sont archivées et exploitées par tous les services dont nous sommes dépendants. C'est essentiellement cet aspect qui justifie mon travail. Ainsi les objectifs de ce projet sont de réduire les services utilisés au minimum, d'écrire les programmes exclusivement en python et d'obtenir une messagerie instantanée permettant à au moins 2 ordinateurs de communiquer par l'intermédiaire d'un serveur. Le but du projet sera donc que 2 personnes puissent échanger des messages instantanément avec leur ordinateur.

Afin de ne pas se heurter à des imprévus majeurs, il est impératif de prévoir les problèmes existants. Une première contrainte est d'assurer la sécurité des messages transmis. Selon le port qu'on attribue dans le programme il est possible d'utiliser l'application dans un réseau fermé ce qui réduit considérablement les risques d'interceptions des communications. En revanche, si on venait à utiliser le port 80 par exemple, celui du http, il serait judicieux d'utiliser un système de cryptage des données pour compliquer l'accès indésirable aux messages. L'application ayant premièrement pour but de fonctionner correctement, la question de l'encryptions est secondaire. De même, il serait possible d'archiver les conversations en utilisant une base de données cependant il est possible d'implémenter cette option ultérieurement. Cet aspect reste donc secondaire. Quant aux problèmes concernant le fonctionnement direct du programme, il y en a deux. Premièrement, python devrait traiter chaque connexion une par une ce qui ne pose pas forcément problèmes si le nombre de clients est faible mais en envisageant d'augmenter ce nombre cela pourrait devenir conflictuelle [4]. L'alternative est donc d'utiliser le module threading de python pour gérer plusieurs demandes en même temps. Deuxièmement, il est possible que l'envoie de paquets utilisant le protocole TCP provoque un ralentissement ou un arrêt de l'application dû à une surcharge de données [5]. Pour éviter cet inconvénient, la solution serait de limiter le nombre de messages transmissibles pendant un temps défini. La dernière problématique est l'impact énergétique de ce programme, en effet le script du serveur est implémenté comme "deamon", il tourne en arrière-plan comme un serveur web. Il consomme donc une quantité importante d'énergie alors qu'il n'est pas constamment utilisé. Un moyen de réduire cette consommation serait de l'éteindre lorsque aucun client n'est connecté. Toutefois, cela reste difficile d'automatiser le processus étant donné qu'il faudrait un autre "deamon" pour réguler l'activité du premier.

2. Matériel et méthodes

2.1 Matériel

x2 Ordinateur avec prérequis :
- python 3 installé
- modules python installés (socket, threading, tkinter)

2.2 Méthode

2.2.1 Cahier des charges

Avant de se lancer corps et âme dans l'élaboration du code, il est nécessaire de choisir l’esthétique générale de l'application et d'expliciter les "outils" nécessaires. L'utilisation finale du projet devra se dérouler de la manière suivante. Premièrement, il sera nécessaire de choisir quel port est utilisé ainsi que l'adresse ip de l'hôte. Puis, une fois que le serveur tourne en arrière-plan, les clients ont la possibilité de se connecter en exécutant leur script. Lors de l'exécution, une fenêtre générée grâce au module tkinter s'ouvre. Le client est alors invité à entrer le pseudonyme qu'il utilisera pendant sa session. Finalement, les messages devrait apparaître ainsi (pseudonyme): (message) (Figure 2). De plus, il serait possible de quitter le chat à tout moment et de se déconnecter du serveur en écrivant /exit. Bien entendu, le client doit comprendre ce qu'il doit faire à chaque instant, des messages d'instructions destinés aux humains sont donc nécessaires.

chat exemple.jpg

                   Figure 2: Exemple de chat

En sommes, les instructions ci-dessus indiquent les choix primaires toutefois, il n'est pas exclu de rajouter des options supplémentaires comme un format de messages interposés façon What's app ou des commandes spécifiques pour le client. Entre autre, Il pourrait être intéressant d'implémenter une commande /mute (pseudonyme) permettant au client de ne pas apercevoir les messages d'un utilisateur.

Pour ce qui est des "outils" exploité il s'agit principalement de modules. Les modules sont des fichiers qui regroupent des ensembles de fonctions. Les fonctions directement intégrées au langage python sont peu nombreuses si bien que l'ajout de modules est rapidement nécessaire pour l'élaboration d'un projet spécifique. Par conséquent, la réalisation de ce programme a nécessité 3 modules. Le premier est socket [6] qui permet d'exploiter facilement les services d'un protocole réseau. Il s'agit de l'élément principale du programme étant donné qu'il établit la communication entre le programme et le protocole réseau TCP/IP. Il a déjà été question du second module dans l'introduction, threading [7] a pour but de gérer plusieurs demandes de client en même temps. En effet, le nombre de clients connectés maximum est de 5 avec l'utilisation du module socket cependant, il est tout à fait envisageable d'augmenter cette limite ultérieurement. Il est donc préférable de prévoir cette éventualité. Le dernier module se nomme tkinter (toolkit interface) [8] et permet la création d'interfaces graphiques.

2.2.2 Principe client-serveur

Le principe initial derrière ce projet est relativement simple. Des machines distantes doivent communiquer entre elles. Cela requiert deux paramètres essentiels : une adresse IP et un numéro de port. L'adresse IP est le numéro d'identification attribué au branchement d'un réseau. Par complémentarité, le numéro de port définit quel "passage" devra être utilisé par les programmes pour émettre ou écouter des informations. Ainsi, le script basique d'un serveur aura pour but "d'écouter" les requêtes des clients qui se connecteront et de leur envoyer des informations si nécessaire. Le code suivant (Figure 3) est celui d'un serveur basique qui accepte les demandes de connexions jusqu'à un maximum de 5 simultanément. Puis il inscrit les messages envoyés par les clients accompagnés de leur adresse IP et envoie au destinataire la confirmation que son message a été transmis.

#!/usr/local/bin/env python3
# coding: utf-8
import socket
serveur = socket.socket(socket.AF_INET, socket.SOCKSTREAM)
serveur.bind
serveur.listen(5)

print("Ecoute sur:", serveur.getsockname())

while True:
\\client, adresse = serveur.accept()
\\print(">", adresse[0]+" says: ", client.recv(1024).decode('utf8'))
\\client.send("Message envoyé.".encode('utf8'))
\\print()

\\client.close()

(\\ indique une tabulation)
De son côté, le script client se connecte à l'hôte et envoie des messages en recevant à chaque fois une confirmation.

#!/usr/local/bin/env python3
# coding: utf-8
import socket

while True:
\\serveur = socket.socket(socket.AF_INET, socket.SOCKSTREAM)

\\message = input("Message > ")
\\serveur.connect
\\serveur.send(message.encode('utf8'))
\\print(serveur.recv(1024).decode('utf8'))
\\print()

\\serveur.close()

D'une manière plus détaillée :

  1. client, adresse = serveur.accept() Le script serveur attend une connexion.
  2. message = input("Message > ") Le script client attend un entrée.
  3. serveur.connect Lorsque le client obtient une entrée, la connexion au serveur est établie.
  4. serveur.send(message.encode('utf8')) Le client envoie l'entrée au serveur.
  5. print(">", adresse[0]+" says: ", client.recv(1024).decode('utf8')) Le serveur imprime l'entrée reçu par le client.
  6. client.send("Message envoyé.".encode('utf8')) Le serveur envoie la confirmation au client.
  7. print(serveur.recv(1024).decode('utf8')) Le client imprime la confirmation du serveur.
  8. print() Le client laisse une ligne d'espace.
  9. serveur.close() Le client coupe la connexion au serveur.
  10. client.close() Le serveur coupe la connexion avec le client.

2.2.3 Programmation du serveur

Pour que le but de ce projet soit atteint, les clients doivent recevoir les messages envoyés par les autres. Quelques modifications sont donc nécessaires:
#!/usr/local/bin/env python3
# coding: utf-8
import socket
serveur = socket.socket(socket.AF_INET, socket.SOCKSTREAM)
serveur.bind
serveur.listen(5)

clients = {}

print("Ecoute sur:", serveur.getsockname())

while True:
\\client, adresse = serveur.accept()
\\clients[client] = adresse[0]
\\msg = client.recv(1024)
\\print(">", adresse[0]+" says: ", msg.decode('utf8'))
\\client.send("Message envoyé.".encode('utf8'))
\\print()
\\for sock in clients:
\\\\sock.send(bytes("/adresse[0]+": ", "utf8")+msg)
\\del clients[client]

\\client.close()

Premièrement, l'ajout d'un dictionnaire clients = {} va permettre d'enregistrer l'adresse des clients clients[client] = adresse[0]. De cette façon, il sera possible d'utiliser les adresse des clients connectés pour transmettre les messages d'un client aux autres. Le second ajout défini "msg" comme étant les messages reçus msg = client.recv(1024), il est donc substitué dans la ligne 18. Le dernier ajout permet d'envoyer, à tous les clients connectés for sock in clients:, les messages reçus sock.send(bytes("/adresse[0]+": ", "utf8")+msg). Puis, l'adresse de l'envoyeur est supprimée del clients[client].

Ce script fonctionne mais ne remplit pas le but du projet. Effectivement, avec le script client de base (Section 2.2.2) le fonctionnement est le suivant :

  1. Le client insère une entrée, il établit donc une connexion au serveur et envoie l'entrée.
  2. Le serveur accepte la connexion et enregistre l'adresse des clients dans le dictionnaire.
  3. Le serveur imprime l'entrée de l'envoyeur et lui envoie une confirmation.
  4. Le serveur transmet l'entrée de l'envoyeur à tous les clients connectés.
  5. Le serveur supprime l'adresse de l'envoyeur puis ferme la connexion avec l'envoyeur.

Dans ce cas de figure, le problème est le suivant : Si les clients utilisent tous le script de base (Section 2.2.2) alors ils ne recevront les messages que lorsqu'ils sont connectés et donc, seulement lorsqu'ils envoient un message. Ainsi, l'alternative serait de séparer le script en deux tâches distincts. D'une part, une boucle qui s'occupe d'accepter les connexions et les enregistrer dans le dictionnaire, puis qui transmet les messages entre les clients. D'autre part, une fonction qui reçoit les messages et envoie une confirmation. C'est lors de cette séparation que le module "threading" devient essentiel. Son utilisation permet de mener les deux tâches en même temps.

De plus, il serait possible de créer un dictionnaire noms = {} ainsi qu'une fonction qui accepterait la première entrée du client comme son "nom" lorsqu'il se connecte. Cette entrée serait alors stockée dans la bibliothèque "noms" et remplacerait alors l'adresse du client pour une meilleure lisibilité du chat.

2.2.4 Programmation du client

Pour que le script client fonctionne en adéquation avec le serveur, cela nécessiterait aussi de séparer les tâches "envoie" et "reçu". Ainsi, la première serait celle qui accepte l'entrée et l'envoie au serveur. La seconde aurait pour seul but d'imprimer les messages que le serveur lui envoie. Dans ce cas de figure, le script client devrait aussi gérer les deux tâches en même temps et l'utilisation du module "threading" est donc requise. Le script suivant reflète l'idée décrite ci-dessus mais n'est pas fonctionnel :

#!/usr/local/bin/env python3
# coding: utf-8
import socket
from threading import Thread

def ecoute():
\\while True:
\\\\serveur = socket.socket(socket.AF_INET, socket.SOCKSTREAM)
\\\\serveur.connect
\\\\print(serveur.recv(1024).decode('utf8'))

def envoie():
\\message = input("Message > ")
\\serveur.send(message.encode('utf8'))
\\print()

ecoute_thread = Thread(target=ecoute)
ecoute_thread.start()

Un objectif secondaire pour le script du client était celui de réaliser une interface graphique en utilisant "tkinter". Aucun test n'a été réalisé sur le code ci-dessus mais des aides [9] et des exemples [10] sont disponibles en ligne.

3. Résultats

Résultat serveur.jpg

      Figure 3: Résultat de l'exécution du script serveur

Résultat client.jpg

      Figure 4: Résultat de l'exécution du script client

4. Discussion

Concernant ce projet, 2 erreurs sont à retenir. Premièrement, cela est dû à une mauvaise gestion du temps à disposition (10 semaines). En effet, le temps à disposition était suffisant pour combiner études et travail pour le projet. Le temps accordé a ce travail était d'environ 4 semaines cependant la répartition des tâches n'était pas judicieuses. Au total, 3 semaines étaient dédiées à la recherche, cela englobe les recherches sur les modules pythons utilisables, la lecture des documentations (python et modules) et les apprentissages sur les forums (GitHub, stackoverflow, etc...). Les informations ainsi recueillies n'étaient pas inutiles mais il était possible de mettre en pratique ses recherches de manière plus efficiente. Effectivement, lire la documentation en appliquant directement les apprentissages auraient permis d'assimiler les concepts relatifs à python bien plus rapidement. Lors de la semaine accordée à l'écriture du code, il a fallu relire une grande partie des recherches faites aux préalables ce qui a considérablement ralenti le début de l'écriture du code. Lors de la fin de la rédaction du script, il était plus simple de tester progressivement des bouts de code simple en s'aidant de la documentation de bases (de python et des modules utilisés) puis de s'aider des forums par la suite. De nombreuses manière de réaliser ce projet était possible [11] [12] , les forums permettaient d'observer ces choix ainsi, le fait de me renseigner sur ces plateformes après avoir lu la documentation offrait une double lecture bien plus utile à la compréhension des concepts en jeux. En somme, la solution concrète à ce problème était principalement de se renseigner de manière cohérente. D'abord assimiler les notions principales d'un langage puis lire des programmes pour juger quelle éventualité serait la meilleure dans le cadre du projet.

Deuxièmement, la compréhension est un point essentiel. Ce commentaire peut sembler ridicule toutefois il m'est arrivé une vingtaine de fois de lire rapidement un script, de penser l'avoir compris puis d'écrire par dessus avant de me rendre compte que plus rien ne marche et finalement de revenir à la case départ. Ce raisonnement, m'a notamment mené à essayer des solutions dans le vide. Parfois elles étaient correctes mais mal rédigées. Python est un langage qui demande un minimum de rigueur, il ne faut donc pas se précipiter lors de l'écriture mais plutôt favoriser l'implémentation progressive de nouvelles fonctions mineures. De plus, réfléchir à des moyens de tester les nouvelles implémentations est primordiale, cela permet de vérifier si un bout de code fonctionne correctement et par extension de savoir si l'on peut continuer à écrire sans risque de bugs.

Malgré ces erreurs, une bonne gestion et organisation des fichiers a permis de ne pas perdre plus de temps. Une cinquantaine de sauvegardes ont été effectuées sur un git pendant toute la durée du projet. Ainsi, l'accès aux dernières modifications était simplifié et les risques de pertes de code ont été évités. De plus, ce projet a facilité l'apprentissage de concepts inhérents aux réseaux et au principe de client-serveur. Il m'a aussi permis d'augmenter ma compréhension de la logique d'un script.

5. Conclusion

En définitive, les objectifs sont remplis. Les services utilisés sont réduits aux minimum, les scripts sont entièrement rédigés en python et ils permettent à au moins deux ordinateurs de communiquer entre eux par l'intermédiaire d'un serveur. Cependant, le but était que de deux personnes puissent échanger des messages instantanément à travers un serveur et ce n'est que partiellement le cas. Effectivement, le code permet d'échanger des messages entre 2 clients sur des machines différentes mais sous des conditions très précises (Section 2.2.3) qui ne sont pas satisfaisantes.

Finalement, le script final est une base suffisante pour poursuivre l'apprentissage de nouveau aspect relatif au secteur du réseau mais aussi un outil réutilisable pour de futurs projets. Il serait envisageable de réutiliser une partie du script en changeant les modules utilisé pour découvrir des applications diverses.

Références

Images

A Lascaux II : chef d'oeuvre de l'art Pariétal grotte à Montignac en Dordogne | Date d'accès: 2018-05-11 | URLS: www.perigord.com/listings/sites-tou...

Notes

[1] Lascaux 4 : la grotte intégralement reconstituée | Date d'accès: 2018-05-11 | URLS: www.futura-sciences.com/sciences/ac...

[2] Télégraphe optique de Claude Chappe,1794 | Date d'accès: 2018-05-11 | URLS: www.arts-et-metiers.net/sites/arts-...

[3] Recommandation UIT-R M Code Morse international, 2009 | Date d'accès: 2018-05-11 | URLS: www.itu.int/dms_pubrec/itu-r/rec/m/...

[4] Utiliser les sockets en python - réseau - cours débutant | Date d'accès: 2018-05-12 | URLS: apprendre-python.com/page-reseaux-s...

[5] Effective Physical Security, Fennelly L, 2016 pp: 458 | ISBN: 0128044950 | Date d'accès: 2018-05-12 | URLS: books.google.ch/books?id=v0y0DAAAQB...

[6] 18.1. socket — Low-level networking interface — Python 3.6.5 documentation | Date d'accès: 2018-05-10 | URLS: docs.python.org/3/library/socket.ht...

[7] 17.1. threading — Thread-based parallelism — Python 3.6.5 documentation | Date d'accès: 2018-05-10 | URLS: docs.python.org/3/library/threading...

[8] 25. Graphical User Interfaces with Tk — Python 3.6.5 documentation | Date d'accès: 2018-05-11 | URLS: docs.python.org/3/library/tk.html

[9] Apprendre à créer des interfaces graphiques en python avec tkinter - cours tutoriel langage de programmation python | Date d'accès: 2018-05-12 | URLS: apprendre-python.com/page-tkinter-i...

[10] Let’s Write a Chat App in Python – The Startup – Medium | Date d'accès: 2018-05-10 | URLS: medium.com/swlh/lets-write-a-chat-a...

[11] Build a Real-Time Chat Application With Modulus and Python | Date d'accès: 2018-05-15 | URLS: code.tutsplus.com/tutorials/build-a...

[12] The Message Class | App Engine standard environment for Python | Google Cloud | Date d'accès: 2018-05-15 | URLS: cloud.google.com/appengine/docs/sta...