Adloun

programmation Java (école d'ingénieur) · Chapitre 16 · Application directe du cours

Serveur de discussion

Réseau : sockets client/serveur · niveau 3 (difficile)

Énoncé

On veut un serveur de discussion (port ) : le premier message d'un client est son pseudonyme ; ensuite, chaque ligne qu'il envoie est transmise à tous les clients connectés, sous la forme « pseudo : message » ; les arrivées et départs sont annoncés ; la ligne /quitter termine la session.

  • Quelle donnée est partagée entre les fils du serveur ? Comment la protéger ?
  • Écrire le serveur.
  • Pourquoi un client de discussion a-t-il besoin de deux fils ?
  • Tester avec deux clients, Alice et Bob.

Corrigé

  • La liste des flux de sortie des participants est partagée : tous les fils la parcourent pour diffuser, et chacun y ajoute ou retire son client. Tout accès se fait sous synchronized sur la liste. -

public class ServeurDiscussion {
    private static final List<PrintWriter> participants = new ArrayList<>();   // partagée

    static void diffuser(String message) {
        synchronized (participants) {                       // la liste est partagée par les fils
            for (PrintWriter p : participants) p.println(message);
        }
    }

    public static void main(String[] args) throws IOException {
        try (ServerSocket serveur = new ServerSocket(5005)) {
            while (true) {
                Socket s = serveur.accept();
                new Thread(() -> servir(s)).start();
            }
        }
    }

    static void servir(Socket s) {
        PrintWriter out = null;
        String pseudo = "?";
        try (s; BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream(), StandardCharsets.UTF_8))) {
            out = new PrintWriter(new OutputStreamWriter(s.getOutputStream(), StandardCharsets.UTF_8), true);
            pseudo = in.readLine();
            synchronized (participants) { participants.add(out); }
            diffuser("* " + pseudo + " entre");
            String ligne;
            while ((ligne = in.readLine()) != null && !ligne.equals("/quitter"))
                diffuser(pseudo + " : " + ligne);
        } catch (IOException e) {
            // client parti brutalement : on le retire comme les autres
        } finally {
            if (out != null) synchronized (participants) { participants.remove(out); }
            diffuser("* " + pseudo + " sort");
        }
    }
}

Le finally retire le client de la liste quelle que soit la façon dont il part.

  • Un client de discussion doit à la fois lire le clavier de l'utilisateur et afficher les messages des autres, qui arrivent à tout moment : les deux lectures sont bloquantes, il faut un fil pour chacune.
  • Avec deux clients scriptés (Alice se connecte, puis Bob 0,2 s plus tard ; chacun envoie deux messages espacés, puis /quitter), Bob voit :

[Bob voit] * Bob entre
[Bob voit] Alice : Bonjour !
[Bob voit] Bob : Salut Alice
[Bob voit] Alice : Quelqu'un a fini l'exercice ?
[Bob voit] * Alice sort
[Bob voit] Bob : Oui, presque

et Alice, partie la première :


[Alice voit] * Alice entre
[Alice voit] * Bob entre
[Alice voit] Alice : Bonjour !
[Alice voit] Bob : Salut Alice
[Alice voit] Alice : Quelqu'un a fini l'exercice ?

Chacun reçoit aussi ses propres messages : c'est la confirmation que le serveur les a bien diffusés.

100 % gratuit

La suite du chapitre est sur Adloun

Exercice suivant et tuteur

Sans compte : le cours et le premier QCM. Exercices et tuteur : inscription gratuite en 30 secondes, sans carte bancaire.

Le cours du chapitre · Tous les exercices du chapitre

Exercice suivant et tuteur →