Salta el contingut
 

Exemple: Enviament d'objectes

Autor: Joan Puigcerver Ibáñez

Correu electrònic: j.puigcerveribanez@edu.gva.es

Llicència: CC BY-NC-SA 4.0

(Reconeixement - NoComercial - CompartirIgual) 🅭

Objectius

En aquest exemple es mostrarà com es pot crear un servidor i un client capaços de comunicar-se mitjançant l'enviament d'objectes.

Codi font

  • Models:

    Film.java
    package ud3.examples.cinema.models;
    
    import java.io.Serializable;
    
    /**
     * Classe que representa una pel·licula.
     * <p>
     * Aquesta classe implementa Serialitzable per poder ser convertida a
     * bytes i poder ser enviada mitjançant sockets.
     */
    public class Film implements Serializable {
        /**
         * Nom de la pel·licula
         */
        private String name;
        /**
         * Any de publiacació
         */
        private int releaseYear;
        /**
         * Duració de la pel·lícula en minuts
         */
        private int duration;
    
        /**
         * Constructor
         * @param name Nom de la pel·lícula
         * @param releaseYear Any de publicació
         * @param duration Duració de la pel·lícula
         */
        public Film(String name, int releaseYear, int duration) {
            this.name = name;
            this.releaseYear = releaseYear;
            this.duration = duration;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getReleaseYear() {
            return releaseYear;
        }
    
        public void setReleaseYear(int releaseYear) {
            this.releaseYear = releaseYear;
        }
    
        public int getDuration() {
            return duration;
        }
    
        public void setDuration(int duration) {
            this.duration = duration;
        }
    
        /**
         * Obté la representació de la pel·lícula en String
         * @return Representació de la pel·lícula
         */
        @Override
        public String toString() {
            return "Film{" +
                    "name='" + name + '\'' +
                    ", releaseYear=" + releaseYear +
                    ", duration=" + duration +
                    '}';
        }
    }
    
    Request.java
    package ud3.examples.cinema.models;
    
    import java.io.Serializable;
    
    /**
     * Classe que representa una petició o resposta entre el servidor i el client.
     * <p>
     * Aquesta classe implementa Serialitzable per poder ser convertida a
     * bytes i poder ser enviada mitjançant sockets.
     */
    public class Request implements Serializable {
        /**
         * Tipus de petició (GET/POST/SUCCESS/ERROR)
         * @see RequestType
         */
        private RequestType type;
        /**
         * Objecte que es pot adjuntar a la comunicació
         */
        private Object object;
        /**
         * Missatge opcional que es pot adjuntar a la comunicació
         */
        private String message;
    
        /**
         * Constructor de la petició
         * @param type Tipus de la petició
         * @param object Objecte adjuntat
         */
        public Request(RequestType type, Object object) {
            this.type = type;
            this.object = object;
        }
    
        /**
         * Constructor de la petició
         * @param type Tipus de la petició
         * @param object Objecte adjuntat
         * @param message Missatge adjuntat
         */
        public Request(RequestType type, Object object, String message) {
            this.type = type;
            this.object = object;
            this.message = message;
        }
    
        public RequestType getType() {
            return type;
        }
    
        public void setType(RequestType type) {
            this.type = type;
        }
    
        public Object getObject() {
            return object;
        }
    
        public void setObject(Object object) {
            this.object = object;
        }
    
        public String getMessage() {
            return message;
        }
    
        public void setMessage(String message) {
            this.message = message;
        }
    }
    
    RequestType.java
    package ud3.examples.cinema.models;
    
    import java.io.Serializable;
    
    /**
     * Enumeració amb els diferents tipus de peticions que podem trobar
     * <p>
     * Aquesta classe implementa Serialitzable per poder ser convertida a
     * bytes i poder ser enviada mitjançant sockets.
     */
    public enum RequestType implements Serializable {
        /**
         * El tipus GET s'utilitza per sol·licitar algun element del servidor
         */
        GET,
        /**
         * El tipus POST s'utilitza per enviar algun element al servidor
         */
        POST,
        /**
         * El tipus SUCCESS s'utilitza per indicar que l'acció s'ha dut a terme correctament
         */
        SUCCESS,
        /**
         * El tipus SUCCESS s'utilitza per indicar que l'acció no s'ha dut a terme correctament
         */
        ERROR
    }
    
  • Servidor:

    CinemaServer.java
    package ud3.examples.cinema.server;
    
    import ud3.examples.cinema.models.Film;
    
    import java.io.IOException;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * CinemaServer és un servidor TCP/IP que gestiona pel·lícules.
     * <p>
     * Actualment perme't obtindre pel·licules del servidor mitbançant la petició "GET"
     * i afegir pel·licules mitjançant la petició "POST"
     *
     * @author Joan Puigcerver
     */
    public class CinemaServer {
        private final ServerSocket server;
        private final List<CinemaServerHandler> clients;
        private final List<Film> films;
        private boolean running;
    
        /**
         * Crea un servidor CinemaServer en el port port especificat.
         *
         * @param port Port on escoltarà el servidor
         * @throws IOException Excepcions del constructor ServerSocket
         */
        public CinemaServer(int port) throws IOException {
            server = new ServerSocket(port);
            clients = new ArrayList<>();
            films = new ArrayList<>();
            running = true;
        }
    
        /**
         * Afig una pel·licula al servidor.
         * @param film Pel·licula que s'afegirà
         */
        public void addFilms(Film film){
            films.add(film);
        }
    
        /**
         * Obté les pel·licules disponibles en el servidor
         * @return Llista de pel·licules en el servidor
         */
        public List<Film> getFilms(){
            return films;
        }
    
        /**
         * Obté la pel·lícula identificada per un id. Actualment l'id és
         * la posició en la llista.
         * @param id id de la pel·lícula
         * @return Pel·lícula amb l'id especificada.
         */
        public Film getFilm(int id){
            return films.get(id);
        }
    
        /**
         * Esborra un client de la llista de clients
         * @param client Client que es vol esborrar
         */
        public void removeClient(CinemaServerHandler client){
            clients.remove(client);
        }
    
        /**
         * Fil d'execució del servidor.
         * <p>
         * El servidor escolta el port i espera noves connexions.
         * Quan una nou client es connecta, es crea un objecte CinemaServerHandler,
         * que gestionarà la comunicació amb aquest client en un fil distint.
         * <p>
         * D'aquesta manera, el servidor pot continuar escoltant i esperant
         * noves connexions mentres cada fil gestiona la comunicació
         * amb cada client.
         */
        public void run() {
            while (running){
                try {
                    // Escolta i esperà una nova connexió.
                    Socket client = server.accept();
                    // Quan un client es connecta, es crea un objecte CinemaServerHandler que
                    // gestionarà la comunicació amb el client connectat.
                    System.out.println("Nou client acceptat.");
                    CinemaServerHandler handler = new CinemaServerHandler(client, this);
                    clients.add(handler);
                    // S'inicia CinemaServerHandler en un fil independent
                    handler.start();
                } catch (IOException e) {
                    System.err.println("Error while accepting new connection");
                    System.err.println(e.getMessage());
                }
            }
        }
    
        /**
         * Inicia un servidor CinemaServer en el port 1234
         * @param args Arguments del programa. No se'n utilitza cap.
         */
        public static void main(String[] args) {
            try {
                CinemaServer server = new CinemaServer(1234);
                server.run();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    
    }
    
    CinemaServerHandler.java
    package ud3.examples.cinema.server;
    
    import ud3.examples.cinema.models.Film;
    import ud3.examples.cinema.models.Request;
    import ud3.examples.cinema.models.RequestType;
    
    import java.io.*;
    import java.net.Socket;
    
    /**
     * Classe que gestiona la comunicació del servidor
     * amb un únic client en un fil d'execució independent.
     */
    public class CinemaServerHandler extends Thread {
        /**
         * Socket que permet comunicar-se amb el client.
         */
        private final Socket client;
        /**
         * Servidor CinemaServer
         */
        private final CinemaServer server;
        /**
         * Objecte ObjectInputStream que permet rebre objectes pel Socket.
         */
        private final ObjectInputStream objIn;
        /**
         * Objecte ObjectOutputStream que permet enviar objectes pel Socket.
         */
        private final ObjectOutputStream objOut;
    
        /**
         * Constructor que inicialitza els canals de comunicació a partir de l'objecte Socket.
         * @param client Socket per comunicar-se amb el client.
         * @param server Servidor
         * @throws IOException Llançada si hi ha algun error creant els canals de comunicació
         */
        public CinemaServerHandler(Socket client, CinemaServer server) throws IOException {
            this.client = client;
            this.server = server;
            objIn = new ObjectInputStream(client.getInputStream());
            objOut = new ObjectOutputStream(client.getOutputStream());
        }
    
        /**
         * Fil d'execució independent per cada client.
         * <p>
         * El servidor espera peticions (Request) i contesta a elles.
         * <p>
         * Si es del tipus POST, afegirà la pel·licula rebuda al servidor.
         * Si es del tipus GET, enviarà la pel·licula sol·licitada al client.
         */
        @Override
        public void run() {
            try {
                // Obtenim objectes del tipus Request del client fins que aquest es desconnecte.
                Request req;
                while((req = (Request) objIn.readObject()) != null){
                    if (req.getType() == RequestType.POST){
                        // Si la petició és del tipus POST
                        // Recuperem la pel·licula de la petició
                        Film film = (Film) req.getObject();
                        // Afegim la pel·lícula al servidor
                        server.addFilms(film);
    
                        // Enviem una resposta del tipus SUCCESS al client
                        // indicant que la pel·licula s'ha afegit correctament
                        String message = String.format("Film %s added.", film);
                        Request response = new Request(RequestType.SUCCESS, null, message);
                        System.out.println(message);
                        objOut.writeObject(response);
    
                    } else if (req.getType() == RequestType.GET) {
                        // Si la petició és del tipus GET
                        // Recuperem la ID de pel·licula de la petició
                        int id = (Integer) req.getObject();
    
                        // Enviem una resposta al client
                        Request response;
                        if(id >= server.getFilms().size())
                            // Si no existeix la pel·licula, enviem una resposta del tipus ERROR
                            response = new Request(RequestType.ERROR, null,
                                    String.format("No s'ha trobat cap pel·licula amb id %d", id));
                        else
                            // Si existeix la pel·licula, enviem una resposta del tipus SUCCESS amb la pel·licula solicitada
                            response = new Request(RequestType.SUCCESS, server.getFilm(id));
    
                        // Enviem la resposta
                        objOut.writeObject(response);
                    }
                }
                client.close();
            } catch (IOException | ClassNotFoundException e) {
                System.err.println("Error while handling client.");
                System.err.println(e.getMessage());
            } finally {
                this.server.removeClient(this);
            }
        }
    }
    
  • Client:

    CinemaClient.java
    package ud3.examples.cinema.client;
    
    import ud3.examples.cinema.models.Film;
    import ud3.examples.cinema.models.Request;
    import ud3.examples.cinema.models.RequestType;
    
    import java.io.IOException;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.net.Socket;
    import java.util.Scanner;
    
    /**
     * Client que es connecta a un CinemaServer
     * i permet realitzar les accions de afegir o obtenir
     * pel·lícules del servidor
     */
    public class CinemaClient {
        /**
         * Socket que permet connectar-se amb el servidor
         */
        private final Socket socket;
        /**
         * Scanner per interactuar amb l'usuari
         */
        private final Scanner scanner;
        /**
         * Canal de comuncació per rebre objectes del servidor mitjançant el socket.
         */
        private final ObjectInputStream objIn;
        /**
         * Canal de comuncació per enviar objectes al servidor mitjançant el socket.
         */
        private final ObjectOutputStream objOut;
    
        /**
         * Constructor del client.
         * Es connecta al servidor indicat i inicialitza els cannals de comunicació i el Scanner.
         * @param host Direciió del servidor
         * @param port Port on escolta el servidor
         * @throws IOException Llançada si hi ha algun error connectant-se al servidor.
         */
        public CinemaClient(String host, int port) throws IOException {
            this.scanner = new Scanner(System.in);
            this.socket = new Socket(host, port);
            this.objOut = new ObjectOutputStream(socket.getOutputStream());
            this.objIn = new ObjectInputStream(socket.getInputStream());
        }
    
        /**
         * Envia una pel·licula al servidor
         * @param f Pel·lícula
         * @throws IOException Llançada si hi ha un error al enviar la pel·lícula o rebent una resposta
         * @throws ClassNotFoundException Llançada si l'objecte rebut pel servidor no és d'una classe coneguda.
         */
        private void sendFilm(Film f) throws IOException, ClassNotFoundException {
            // Creem una petició POST amb la pel·lícula
            Request req = new Request(RequestType.POST, f);
            // Enviem la petició
            objOut.writeObject(req);
    
            // Esperem una resposta del servidor
            Request response = (Request) objIn.readObject();
    
            // Comprovem si hi ha hagut algun error i mostrem el missatge de reposta
            if(response.getType() == RequestType.ERROR)
                System.err.printf("ERROR: %s\n", response.getMessage());
            else if(response.getType() == RequestType.SUCCESS)
                System.out.printf("%s\n", response.getMessage());
        }
        /**
         * Reb una pel·lícula del servidor a partir de la seua ID
         * @param id ID de la pel·lícula
         * @return Pel·licula rebuda pel servidor; null si hi hagut algun error.
         * @throws IOException Llançada si hi ha un error al rebre la pel·lícula
         * @throws ClassNotFoundException Llançada si l'objecte rebut pel servidor no és d'una classe coneguda.
         */
        private Film receiveFilm(int id) throws IOException, ClassNotFoundException {
            // Creem una petició del tipus GET amb la ID
            Request req = new Request(RequestType.GET, id);
            // Enviem la petició
            objOut.writeObject(req);
    
            // Esperem una resposta del servidor
            Request in = (Request) objIn.readObject();
    
            // Si hi ha hagut algun error, mostrem el missatge
            if(in.getType() == RequestType.ERROR)
                System.err.printf("ERROR: %s\n", in.getMessage());
    
            // Retornem l'objecte rebut
            return (Film) in.getObject();
        }
    
        /**
         * Mostra les accions del menú
         */
        private void printMenuActions(){
            System.out.println("1. Afegir pel·lícula.");
            System.out.println("2. Obtenir pel·lícula.");
            System.out.println("0. Eixir.");
        }
    
        /**
         * Demana una elecció vàlida a l'usuari (0 fins max incluït)
         * Si l'usuari no indica una elecció vàlida, li tornarà a preguntar.
         * @param max Elecció màxima
         * @return Elecció de l'usuari
         */
        private int askUserAction(int max){
            System.out.print("Introdueix la teua elecció: ");
            int action = scanner.nextInt();
            scanner.nextLine();
    
            while (action < 0 || action > max){
                System.out.print("La elecció introduida no està entre els valors vàlids.");
                System.out.print("Introdueix la teua elecció: ");
                action = scanner.nextInt();
                scanner.nextLine();
            }
    
            return action;
        }
    
        /**
         * Mostra el menu principal de l'aplicació.
         */
        public void menu(){
            while(true) {
                printMenuActions();
                int action = askUserAction(2);
                switch (action){
                    case 0:
                        return;
                    case 1:
                        try {
                            addFilm();
                        } catch (IOException | ClassNotFoundException e) {
                            System.err.println("Error afegint una pel·lícula.");
                        }
                        break;
                    case 2:
                        try {
                            getFilm();
                        } catch (IOException | ClassNotFoundException e) {
                            System.err.println("Error obtenint una pel·lícula.");
                        }
                        break;
                }
            }
        }
    
        /**
         * Interactua amb l'usuari per afegir una pel·lícula al servidor.
         * @throws IOException Llançada si hi ha un error al afegir la pel·lícula
         * @throws ClassNotFoundException Llançada si l'objecte rebut pel servidor no és d'una classe coneguda.
         */
        private void addFilm() throws IOException, ClassNotFoundException {
            System.out.println("AFEGIR PEL·LÍCULA");
            System.out.print("Introdueix el nom de la pel·lícula: ");
            String nom = scanner.nextLine();
            System.out.print("Introdueix el any de publicació de la pel·lícula: ");
            int year = scanner.nextInt();
            System.out.print("Introdueix la duració de la pel·lícula: ");
            int duration = scanner.nextInt();
            Film film = new Film(nom, year, duration);
            sendFilm(film);
        }
    
        /**
         * Interactua amb l'usuari per obtindre una pel·lícula al servidor.
         * @throws IOException Llançada si hi ha un error al obtindre la pel·lícula
         * @throws ClassNotFoundException Llançada si l'objecte rebut pel servidor no és d'una classe coneguda.
         */
        private void getFilm() throws IOException, ClassNotFoundException {
            System.out.println("OBTENIR PEL·LÍCULA");
            System.out.print("Introdueix la id de la pel·lícula: ");
            int id = scanner.nextInt();
            Film film = receiveFilm(id);
            if (film != null)
                System.out.printf("S'ha obtingut la pel·lícula %s.\n", film);
        }
    
        /**
         * Inicia el client
         * @param args Arguments del programa. No s'utilitzen.
         */
        public static void main(String[] args) {
            System.out.println("Connectant-se amb el servidor...");
            try {
                CinemaClient cinema = new CinemaClient("localhost", 1234);
                cinema.menu();
            } catch (IOException e){
                System.err.println("Error connectant-se amb el servidor.");
            }
        }
    }
    

Models: Request, RequestType i Film

L'objectiu principal de l'aplicació es gestionar pel·lícules, per tant, hem creat la classe Film que representa una pel·lícula.

L'aplicació ha de permetre realitzar l'enviament de pel·lícules en els dos sentits. Per poder indicar-li al servidor quina acció es vol realitzar, hem creat la enumeració RequestType.

Aquesta enumeració permet classificar cada petició en els següents tipus:

  • GET: El client enviarà una petició d'aquest tipus al servidor per sol·licitar una pel·lícula al servidor.
  • POST: El client enviarà una petició d'aquest tipus al servidor per enviar-li una pel·lícula.
  • SUCCESS: El servidor respondrà amb aquest tipus si la acció sol·licitada s'ha portat a terme correctament.
  • ERROR: El servidor respondrà amb aquest tipus si hi ha hagut algun error processant la acció sol·licitada.

Per últim, hem creat l'objecte Request, que representa una petició al servidor o una resposta al client.

Aquest objecte esta identificat per un tipus RequestType, conté un objecte Object que pot ser adjuntat i un missatge String.

L'objectiu d'aquesta classe es ser enviada mitjançant un ObjectOutputStream i ser rebuda per un ObjectInputStream, que poden ser creats amb el OutputStream i InputSteam de Socket respectivament.

  • Client:
    ObjectOutputStream objOut = new ObjectOutputStream(socket.getOutputSteam());
    Request req = new Request(RequestType.POST, new Film("La vida es bella", 1997, 116))
    objOut.writeObject(request);
    
  • Servidor:
    ObjectInputStream objin = new ObjectInputStream(socket.getInputStream());
    Request req = (Request) objIn.readObject();
    if(req.getType() == RequestType.POST)
        Film film = (Film) req.getObject();
    

Per poden enviar aquests objectes mitjançant sockets, s'han de poder convertir a un array de bytes. Per indicar-ho, és necessari que les classes implementen la interfície Serializable.

Client: CinemaClient

Aquesta classe implementa un programa que es connecta al servidor i proporciona un menú per afegir i rebre pel·lícules del servidor.

Servidor: CinemaServer i CinemaServerHandler

La classe CinemaServer implementa un servidor que gestiona pel·lícules.

Aquest servidor escolta en el port especificat i espera a que els clients es connecten. Cada vegada que un client es connecta, es crea un objecte CinemaServerHandler, que s'executa en un fil independent i gestiona la comunicació amb el client.

La classe CinemaServerHandler s'encarrega de gestionar les peticions del client.

  • Si la petició és del tipus POST, rebrà una pel·lícula i l'afegeix al servidor.
  • Si la petició és del tipus GET, buscarà la pel·lícula sol·licitada i li la retornarà al client.

Ampliació

Modifica el programa proporcionat amb les següents funcionalitats:

  • Permetre que el client puga demanar-li al servidor la llista de pel·lícules disponibles.
  • Permetre que el client puga eliminar una pel·lícula existent al servidor.

Totes les funcionalitats han de ser tolerants a tots els possibles errors, que cal gestionar.

Comentaris