ICQ - Terminale

Projet ICQ

Image sans description

Prérequis

  • structures de contrôle ;
  • boucles ;
  • fonctions ;
  • modules ;
  • programmation orientée objet ;
  • cours : ordonnancement.

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 :

Image sans description

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 :

  • Communiquer avec plusieurs clients ;
  • Faire en sorte qu’on puisse quitter le serveur et le client en saisissant la commande “/exit” ;
  • Lorsqu’un client quitte le serveur, il envoie un message spécial au serveur pour se faire désinscrire de la liste des clients ;
  • Permettre de choisir le serveur avec le nom de l’utilisateur ;
  • Afficher les messages de tous les clients à tous les clients ;
  • Afficher l’heure du message devant chaque message ;
  • Bien afficher ne nom de l’expéditeur de chaque message sur tous les clients
  • Permettre au serveur d’envoyer des messages à tout le monde (pas seulement des “Bien reçu”) ;
  • Émettre un petit son quand un message arrive ;
  • Empêcher le flood (pas plus de deux messages par seconde) ;
  • Afficher la liste des clients connectés avec la commande “/list” sur le serveur ;
  • Gérer une liste d’IP bannies dans un fichier rempli à la main ;
  • Permettre au propriétaire du serveur de bannir une ip avec la commande “/ban IP” (elle est alors ajoutée au fichier d’IP bannies)
  • Toute amélioration qui vous semble utile !

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âcheBarème
Comunication avec plusieurs clients1 points
/exit sur le client1 points
/exit enlève le client de la liste des clients1 points
Choix du serveur avec le nom1 point
Affichage des messages à tout le monde2 point
Affichage de l’heure de chaque message1 points
Afficher le nom de l’expéditeur chez tous les clients1 point
Permettre au serveur d’envoyer des messages à tout le monde2 points
Émettre un son quand un message arrive1 points
Empêcher le flood1 points
Afficher la liste des clients connectés sur le serveur avec la commande /list1 points
Gérer une liste d’IP bannies avec un fichier rempli à la main1 points
Ajouter des IP bannies avec la commande /ban (elles doivent alors être ajoutées au fichier)1 points
/exit sur le serveur1 points
Code propre1.5 points
Code optimisé1 point
Commentaires1.5 points
Toute autre amélioration0.5 point bonus
Total20
Retour