Chat en java

Fermé
Chapi - 12 déc. 2014 à 09:18
KX Messages postés 16733 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 31 janvier 2024 - 13 déc. 2014 à 22:20
Bonjour,

Je dois réaliser un chat pour un TP mais je suis bloqué. Lorsqu'un client se déconnecte, plus rien ne fonctionne et je n'arrive pas à savoir d'où cela provient.

Voici les messages d'erreur qui s'affiche pour le Client lorsqu'il se déconnecte :
Erreur au run : java.net.SocketException: Socket closed

Et voici celui du Serveur :
Erreur au run : java.net.SocketException: Software caused connection abort: socket write error

Voici le code du Client :
package chat;

/*
 **** Chat multiclient - M1-Informatique *****
 *	Client.java
 * 
 */
// Packages a inclure
import java.io.*;
import java.net.*;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Client implements Runnable {

// Declaration des attributs de la classe 
//
    private int port;
    private InetAddress inetAddress;
    Thread _t;
    Socket s;
    String login;

// Les methodes
    /**
     * Constructeur de la classe Client
     */
    public Client() {
    }

    /**
     * Methode declenchee automatiquement apres l'execution de '_t.start()', son
     * role est de lire et afficher les messages envoyes par le serveur
     */
    //Il s'agit des messages envoyes par les autres utilisateurs (clients) */
    public void run() {
        try {
            while (true) {
                // 1. Recuperer le flux d'entree du socket s (methode getInputStream() de la classe Socket)
                InputStream is = s.getInputStream();
                ObjectInputStream ois = new ObjectInputStream(is);
                // 2. Lire le message envoye par le serveur (Il faut utiliser une methode bloquante, par exemple : readObject())    
                Object obj = ois.readObject();
                // 3. Convertir l'objet lu ('Object') en une chaine de caracteres ('String')
                String msg = (String) obj;
                // 4. Afficher la chaine de caracteres
                System.out.println(msg);
            }
        } catch (Exception e) {
            System.out.println("Erreur au run : " + e);
        }
    }

    /**
     * Methode permettant de lire une chaine de caracteres depuis la ligne de
     * commandes
     */
    public String readMessage() throws IOException {
        String s = "";
        char c;
        while ((c = (char) System.in.read()) != '\n') {
            if (c != '\r') {
                s = s + c;
            }
        }
        return s;
    }

    /**
     * Methode permettant de se connecter au serveur, de lire les chaines de
     * caracteres depuis une ligne de commandes, puis de les envoyer au serveur.
     * La chaine 'Bye' doit mettre fin a la connexion
     */
    public void launch() {
        try {
            String msg = "";
            // 1. Creer le socket
            System.out.println("Connexion au serveur...");
            s = new Socket(getInetAddress(), getPort());

            // 2. Lancer le Thread (methode 'start') permettant de recevoir les reponses du serveur
            _t = new Thread(this);
            _t.start();

            OutputStream os = s.getOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(os);

            oos.writeObject((Object) login);

            while (!(msg.equals("Bye"))) {
                // 3. Recuperer le flux de sortie du socket (methode getOutputStream() de la classe Socket)

                Scanner sc = new Scanner(System.in);
                msg = sc.nextLine();

                // 4. Ecrire le message sur le flux de sortie du socket (methode writeObject());
                oos.writeObject((Object) msg);
                
            }
            // 5. Mettre fin a la connexion si le message envoye est 'Bye'
            s.close();
        } catch (IOException e) {
            System.out.println("Erreur de sérialisation " + e);
        }
    }

    /**
     * Methode permettant d'introduire le numero de port utilise par le serveur
     */
    public void setPort(int _port) {
        port = _port;
    }

    /**
     * Methode permettant de retourner le numero de port du serveur
     */
    public int getPort() {
        return port;
    }

    /**
     * Methode permettant d'introduire le nom ou l'IP du serveur
     */
    public void setInetAddress(String _machineName) {
        try {
            inetAddress = InetAddress.getByName(_machineName);
        } catch (UnknownHostException ex) {
            System.out.println("Impossible de lire le nom ou l'adresse IP du serveur.");
        }
    }

    /**
     * Methode permettant de retourner l'adresse IP du serveur
     */
    public InetAddress getInetAddress() {
        return inetAddress;
    }

    /**
     * Methode permettant d'introduire le login de l'utilisateur
     */
    public void setLogin() {
        Scanner sc_login = new Scanner(System.in);
        System.out.println("Entrez votre login : ");
        login = sc_login.nextLine();
    }

//////////////////////////////////////////////////////////////////////////////////////////////
// Methode principale : 'main'
//
// Le programme prendra 2 parametres : une adresse ou un nom de serveur et un numero de port.
// 
//////////////////////////////////////////////////////////////////////////////////////////////
    public static void main(String arg[]) {
        Client em = new Client();
        em.setInetAddress(arg[0]);
        em.setPort(Integer.parseInt(arg[1]));
        em.setLogin();
        em.launch();
    }
}


Et voici celui du Serveur :
package chat;

/*
 **** Chat multiclient - M1-Informatique *****
 *	Server.java
 * 
 */
// Packages a inclure
import java.net.*;
import java.io.*;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Server implements Runnable {
// Declaration des attributs de la classe 

    private int listenPort;
    private ServerSocket ss;
    private Thread _t;
    Socket _s;
    Vector clients = new Vector();

// Declaration des methodes
    /**
     * Constructeur de la classe Server
     */
    public Server() throws IOException {
    }

    /**
     * Methode permettant d'introduire le numero de port du serveur
     */
    private void setListenPort(int _listenPort) {
        listenPort = _listenPort;
    }

    /**
     * Methode permettant de retourner le numero de port du serveur
     */
    public int getPort() {
        return listenPort;
    }

    /**
     * Methode permettant de lancer le serveur
     */
    private void launch() {
        try {
            // 1. Creer le ServerSocket : ServerSocket(int _port)
            ss = new ServerSocket(listenPort);
        } catch (IOException ex) {
            System.out.println("Erreur : " + ex);
        }

        System.out.println("Serveur en execution...");

        try {
            while (true) {
                // 2. Appeler la methode 'accept()' du ServerSocket creee en 1.
                _s = ss.accept();
                // 3. Ajouter la nouvelle connexion dans un vecteur (ou tableau) contenant toutes les connexions.
                clients.add(_s);
                // 4. Appeler la methode _t.start() permettant de lancer run() (run() : permet de traiter les connexions des clients) 
                _t = new Thread(this);
                _t.start();
            }
        } catch (Exception e) {
            System.out.println("Erreur socket ...." + e);
            System.exit(0);
        }
    }

    public void run() {
        try {
            // 1. Recuperer le dernier socket du vecteur (ou tableau)
            Socket st = (Socket) clients.lastElement();
            // 2. Recuperer le flux d'entree du socket (methode getInputStream() de la classe Socket)
            InputStream is = st.getInputStream();
            // 3. Lire le premier message envoye par le client (en  utilisant une methode bloquante, par exemple : readObject()). Il s'agit du login de l'utilisateur.       
            ObjectInputStream ois = new ObjectInputStream(is);
            String login = (String) ois.readObject();
            // 4. Imprimer le message   
            System.out.println("Connexion de " + login + ".");
            while (true) {
                // 5. Lire le message envoye par le client (methode bloquante)
                String message = (String) ois.readObject();
                // 6. Imprimer le message
                System.out.println(login + " : " + message);

                // 7. Envoyer le message lu a tous les autres clients, le preceder par le login de l'utilisateur concerne
                if (!clients.isEmpty()) {
                    for (Object client : clients) {
                        Socket sck = (Socket) client;
                        OutputStream os = sck.getOutputStream();
                        // Utilisation de getOutputStream() et writeObject.
                        ObjectOutputStream oos = new ObjectOutputStream(os);
                        oos.writeObject(login + " : " + message);
                    }
                }
                if(message.equals("Bye")) {
                    clients.remove(st);
                }
            }
        } catch (IOException | ClassNotFoundException e) {
            System.out.println("Erreur au run : " + e);
        }
    }

//////////////////////////////////////////////////////////////////////////////////////////////
// Methode principale : 'main'
//
// Le programme prendra 1 parametre : le numero de port a surveiller
// 
//////////////////////////////////////////////////////////////////////////////////////////////
    public static void main(String arg[]) {
        try {
            Server server = new Server();
            int port = Integer.parseInt(arg[0]);
            server.setListenPort(port);
            server.launch();
        } catch (IOException ex) {
            Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}


Merci d'avance pour votre aide !
A voir également:

1 réponse

KX Messages postés 16733 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 31 janvier 2024 3 015
12 déc. 2014 à 22:47
Bonsoir,

Pour avoir plus de détails sur l'exception tu dois afficher la trace complète de l'exception et non te limiter à son message. De plus évites le System.exit(0), surtout pour un erreur, à la rigueur un System.exit(1), mais en pratique on préférera propager l'exception comme ceci par exemple :

} catch (Exception e) {
    throw new RuntimeException("Erreur au run", e);
}

Ça te permettra de savoir d'où vient l'erreur, en l'occurrence ici :

// 5. Lire le message envoye par le client (methode bloquante)
String message = (String) ois.readObject();

En effet, comme tu l'as indiqué : la méthode est bloquante, donc si le client s'arrête abruptement le serveur ne peut plus le traiter. Mais dans ce cas il ne faudra pas que le serveur plante, c'est un comportement attendu que le client se déconnecte.
0
Merci énormément pour le RuntimeException, je ne connaissais pas ça et c'est vraiment pratique.

Malheureusement, je ne vois toujours pas comment résoudre la situation.
0
KX Messages postés 16733 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 31 janvier 2024 3 015
Modifié par KX le 13/12/2014 à 09:24
Et bien dans le cas où tu as une exception à ce niveau là, le serveur devrait abandonner le client, mais en aucun cas il ne devrait planter.

Exemple :

while (true) 
{
    String message;
    
    try {
        message = (String) ois.readObject();
    }
    catch (Exception e) {
        message = null;
    }
    
    if (message==null) {
        // abandon du client
    }
    else {
        // traitement normal
    }
}

Remarque : je trouve le choix de l'ObjectInputStream maladroit. Ce que tu lis ici ce sont des String, donc à la rigueur on utilise OIS, mais avec un readUTF8 à la place de readObject, mais je pense qu'il est préférable d'utiliser un flux texte de type Scanner, ce qui te permettrait de détecter la fin du flux et ne pas avoir d'erreur dans ce cas, par exemple en faisant un
while (scanner.hasNextLine) { String message = scanner.nextLine(); }


Remarque : les RuntimeException et toutes ses classes filles ont l'avantage de ne pas avoir à être try/catché, mais cela implique aussi qu'un code qui n'a pas besoin d'être try/catché peut quand même lever une RuntimeException. C'est pour ça qu'il est préférable de catcher assez large, j'ai donc remplacé
IOException | ClassNotFoundException e
par
Exception e
mais on aurait pu faire aussi
IOException | ClassNotFoundException | RuntimeException e
. Quelques exemples de RuntimeException bien connus : NullPointerException ou IndexOutOfBoundException...
0
Bonjour, encore merci pour votre aide. Je suis malheureusement trop nouveau en java pour comprendre exactement tout ce que vous me proposez de faire.

En essayant votre exemple, le terminal me retourne une erreur "NullPointerException". Voilà ce que j'ai fait dans le code pour obtenir ce résultat :

while (true) {
                String message;
                try {
                    // 5. Lire le message envoye par le client (methode bloquante)
                    message = (String) ois.readObject();
                } catch (Exception e) {
                    message = null;
                }
                
                if(message.equals("Bye") || message == null) {
                    clients.remove(st);
                }
                else {
                    // 6. Imprimer le message
                    System.out.println(login + " : " + message);

                    // 7. Envoyer le message lu a tous les autres clients, le preceder par le login de l'utilisateur concerne
                    if (!clients.isEmpty()) {
                        for (Object client : clients) {
                            Socket sck = (Socket) client;
                            OutputStream os = sck.getOutputStream();
                            // Utilisation de getOutputStream() et writeObject.
                            ObjectOutputStream oos = new ObjectOutputStream(os);
                            oos.writeObject(login + " : " + message);
                        }
                    }
                }


Concernant l'utilisation de scanner, ce programme est à réaliser dans le cadre d'un TP et je ne sais pas du tout si j'ai le droit ou non d'utiliser autre chose que ce qu'il m'est indiqué de faire.

Je pense que l'erreur vient du client, car quelque soit les modifications dans Serveur, cela ne fonctionne pas. Je ne peux même pas être sûr de cela, cependant.

Je vous remercie encore de bien vouloir m'aider, j'ai vraiment du mal avec ce programme et cela me décourage malheureusement beaucoup.
0
KX Messages postés 16733 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 31 janvier 2024 3 015
Modifié par KX le 13/12/2014 à 21:03
if(message.equals("Bye") || message == null)

Il faut faire cela dans l'autre sens.

Les conditions sont évaluées de gauche à droite (sauf les dernières si elles ne servent à rien)
Du coup, ça plante sur le
message.equals
parce qu'il n'a pas encore vu que
message==null
.

Il faut donc tester la valeur null en premier.
if (message==null || message.equals("Bye"))

Il ne cherchera pas à faire le
message.equals
puisque c'est un OU alors que la première condition vaut true, il sait déjà que le résultat final est true, peu importe la deuxième condition (qui plante).
0
Je me sens tellement nul de ne pas penser à ce genre de chose. Merci !

J'ai obtenu un autre message d'erreur que j'ai résolue en remplaçant le vector clients par un arraylist, le serveur marche maintenant sans problème !

J'ai cependant un dernier soucis avec mon client. En effet, j'ai toujours le message d'erreur : "
Caused by: java.net.SocketException: Socket closed" à cause de la ligne 43, soit
                 ObjectInputStream ois = new ObjectInputStream(is);


Je suppose qu'il faut que j'effectue une démarche similaire à celle effectué dans Serveur. Je vous remercie encore pour votre aide !
0