Cours d'informatique pour le lycée

Projet ICQ

Prérequis

Ce projet fonctionne mais il doit être possible de le simplifier !

Présentation

ICQ est l'un des premiers logiciel de messagerie apparu en 1996. Après un immense succès au début des années 2000 et même s'il existe toujours aujourd'hui, il n'a pas résisté à la progression de MSN Messenger et Skype.

ICQ permettait d'avoir des discussions intantanées avec les autres utilisateurs. Voici à quoi ressemblait son interface :

Nous allons donc essayer de créer un programme de discussion instantanée en Python.

Première communication

Pour établir une communication entre deux ordinateurs, il faut généralement utiliser des sokets. Un socket est un lien entre deux ordinateurs, un server qui attend des connections entrantes et un client qui demande à établir une connection. La plupart des programme qui échangent des informations sur internet (appli, navigateur…) utilisennt des sockets.

Voici donc deux scripts pouvant s'échanger des messages. Éxécutez-les tous les deux sur votre machine dans un premier temps pour en comprendre le fonctionnement :

# Script du serveur

import socket

def main():
    # On récupère le nom de la machine locale
    host = socket.gethostname()
    # On choisi un port entre 1024 et 65535
    port = 4000

    # On crée le socket utilisant IPv4 et TCP
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    # On attche le socket au serveur avec le port pour attendre les messages
    s.bind((host, port))

    print("Server démarré")
    while True:
        # On attend un message, quand il arrive on récupère aussi l'adresse de l'expéditeur
        data, addr = s.recvfrom(1024)
        # On décode le message
        data = data.decode('utf-8')
        # Affochage du message
        print("Message de " + str(addr), end=' : ')
        print(data)
        # On confirme la réception
        retour = "Bien reçu"
        s.sendto(retour.encode('utf-8'), addr)
    c.close()

if __name__ == '__main__':
    main()
# Script du client

import socket

def main():
    # On récupère le nom de la machine locale (à remplacer par l'adresse du serveur distant plus tard)
    host = socket.gethostname()
    # On choisit le même port que le serveur
    port = 4000

    # On crée le socket utilisant IPv4 et TCP
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    # On demande le message à envoyer
    message = input("-> ")
    while message != 'q':
        # On envoie le message
        s.sendto(message.encode('utf-8'), (host, port))
        # On attend la réponse du serveur
        data, addr = s.recvfrom(1024)
        data = data.decode('utf-8')
        # On affiche la réponse
        print("Reçu du serveur : " + data)
        # On attend un nouveau message
        message = input("-> ")
    s.close()


if __name__ == '__main__':
    main()

Une fois que vous avez réussi à échanger des messages vous pouvez tester ou proposer les améliorations suivantes :

Pour les plus rapide, si vous aimez les défis, essayez de refaire le même programme décentralisé. C'est à dire sans serveur, avec des clients identiques qui s'envoient des messages entre eux.

Utilisation des threads

Il est possible que vous ayez besoin d'utiliser des threads (ça n'est pas certain). Voici donc un code minimal pour pouvoir créer des threads :

class myThread (Thread):

    def __init__(self, …):
        Thread.__init__(self)
        …

    def run(self):
		# Méthode exécutée lorsqu'on appelle start()


thread = myThread(…)
thread.start()

Code d'aide pour avoir les messages sur tous les clients

Voici un code basique permettant d'afficher les messages envoyés par tous les clients à tous les clients. Si on utilise une seul machine, il faut un port différent en réception pour chaque client. Il devrait être possible de mettre le même port si les clients sont sur des machines différentes. La seule différence entre client et clientbis est le port de réception.

# Script du serveur

import socket

def send_all(socket, data, liste_clients):
    for addr in liste_clients:
        socket.sendto(data, addr)


def main():
    # On récupère le nom de la machine locale
    host = socket.gethostname()
    # On choisi un port entre 1024 et 65535
    port_recept = 4000
    #port_envoi = 4001

    # On crée le socket utilisant IPv4 et TCP
    s_recept = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    # On attche le socket au serveur avec le port pour attendre les messages
    s_recept.bind((host, port_recept))

    liste_clients = set()

    print("Server démarré")
    while True:
        # On attend un message, quand il arrive on récupère aussi l'adresse de l'expéditeur
        data, addr = s_recept.recvfrom(1024)
        # On ajoute le client à la liste
        liste_clients.add(addr)
        print("liste_client", liste_clients)
        # On décode le message
        message = data.decode('utf-8')
        # Affichage du message
        print("Message de " + str(addr), end=' : ')
        print(message)

        #s_recept.sendto(data, addr)
        send_all(s_recept, data, liste_clients)
    c.close()

if __name__ == '__main__':
    main()
# Script du client

import socket
from threading import Thread

class myThread (Thread):

    def __init__(self, socket, addr):
        Thread.__init__(self)
        self.socket = socket
        self.addr = addr

    def run(self):
        self.socket.bind(self.addr)
        while True:
            # On attend un message, quand il arrive on récupère aussi l'adresse de l'expéditeur
            data, addr = self.socket.recvfrom(1024)

            # On décode le message
            message = data.decode('utf-8')
            # Affichage du message
            print("Message de " + str(addr), end=' : ')
            print(message)
        c.close()



def main():
    # On récupère le nom de la machine locale (à remplacer par l'IP locale plus tard)
    host = socket.gethostname()
    # On donne l'IP du serveur (à remplacer par l'IP du serveur distant plus tard)
    host_serveur = socket.gethostname()
    # On choisit le même port que le serveur
    port_serveur = 4000
    # On crée l'adresse
    addr_serveur = (host_serveur, port_serveur)
    # On crée le socket utilisant IPv4 et TCP
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

    # On choisit un port différent car on est sur la même machine (rien n'empêche de choisir le même que le serveur plus tard)
    port_recept = 4001
    addr_recept = (host, port_recept)
    # On lance un thread pour gérer les messages entrant
    thread = myThread(s, addr_recept)
    thread.start()

    # On demande le message à envoyer
    message = input("-> ")
    while message != 'q':
        # On envoie le message
        s.sendto(message.encode('utf-8'), addr_serveur)
        # On attend un nouveau message
        message = input("-> ")
    s.close()


if __name__ == '__main__':
    main()
# Script du clientbis

import socket
from threading import Thread

class myThread (Thread):

    def __init__(self, socket, addr):
        Thread.__init__(self)
        self.socket = socket
        self.addr = addr

    def run(self):
        self.socket.bind(self.addr)
        while True:
            # On attend un message, quand il arrive on récupère aussi l'adresse de l'expéditeur
            data, addr = self.socket.recvfrom(1024)

            # On décode le message
            message = data.decode('utf-8')
            # Affichage du message
            print("Message de " + str(addr), end=' : ')
            print(message)
        c.close()



def main():
    # On récupère le nom de la machine locale (à remplacer par l'IP locale plus tard)
    host = socket.gethostname()
    # On donne l'IP du serveur (à remplacer par l'IP du serveur distant plus tard)
    host_serveur = socket.gethostname()
    # On choisit le même port que le serveur
    port_serveur = 4000
    # On crée l'adresse
    addr_serveur = (host_serveur, port_serveur)
    # On crée le socket utilisant IPv4 et TCP
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

    # On choisit un port différent car on est sur la même machine (rien n'empêche de choisir le même que le serveur plus tard)
    port_recept = 4002
    addr_recept = (host, port_recept)
    # On lance un thread pour gérer les messages entrant
    thread = myThread(s, addr_recept)
    thread.start()

    # On demande le message à envoyer
    message = input("-> ")
    while message != 'q':
        # On envoie le message
        s.sendto(message.encode('utf-8'), addr_serveur)
        # On attend un nouveau message
        message = input("-> ")
    s.close()


if __name__ == '__main__':
    main()

Tableau du barème

Voilà le barème complet sur 20 pour ce projet.

Tâche Barème
Comunication avec plusieurs clients 1 points
/exit sur le client 1 points
/exit enlève le client de la liste des clients 1 points
Choix du serveur avec le nom 1 point
Affichage des messages à tout le monde 2 point
Affichage de l'heure de chaque message 1 points
Afficher le nom de l'expéditeur chez tous les clients 1 point
Permettre au serveur d'envoyer des messages à tout le monde 2 points
Émettre un son quand un message arrive 1 points
Empêcher le flood 1 points
Afficher la liste des clients connectés sur le serveur avec la commande /list 1 points
Gérer une liste d'IP bannies avec un fichier rempli à la main 1 points
Ajouter des IP bannies avec la commande /ban (elles doivent alors être ajoutées au fichier) 1 points
/exit sur le serveur 1 points
Code propre 1.5 points
Code optimisé 1 point
Commentaires 1.5 points
Toute autre amélioration 0.5 point bonus
Total 20