packageud3.examples.cinema.models;importjava.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. */publicclassFilmimplementsSerializable{/** * Nom de la pel·licula */privateStringname;/** * Any de publiacació */privateintreleaseYear;/** * Duració de la pel·lícula en minuts */privateintduration;/** * Constructor * @param name Nom de la pel·lícula * @param releaseYear Any de publicació * @param duration Duració de la pel·lícula */publicFilm(Stringname,intreleaseYear,intduration){this.name=name;this.releaseYear=releaseYear;this.duration=duration;}publicStringgetName(){returnname;}publicvoidsetName(Stringname){this.name=name;}publicintgetReleaseYear(){returnreleaseYear;}publicvoidsetReleaseYear(intreleaseYear){this.releaseYear=releaseYear;}publicintgetDuration(){returnduration;}publicvoidsetDuration(intduration){this.duration=duration;}/** * Obté la representació de la pel·lícula en String * @return Representació de la pel·lícula */@OverridepublicStringtoString(){return"Film{"+"name='"+name+'\''+", releaseYear="+releaseYear+", duration="+duration+'}';}}
CinemaMessage.java
packageud3.examples.cinema.models;importjava.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. * * @see CinemaMessageType * @param type Tipus de petició (GET/POST/SUCCESS/ERROR) * @param object Objecte que es pot adjuntar a la comunicació * @param message Missatge opcional que es pot adjuntar a la comunicació */publicrecordCinemaMessage(CinemaMessageTypetype,Objectobject,Stringmessage)implementsSerializable{}
CinemaMessageType.java
packageud3.examples.cinema.models;importjava.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. */publicenumCinemaMessageTypeimplementsSerializable{/** * 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 ERROR s'utilitza per indicar que l'acció no s'ha dut a terme correctament */ERROR}
Servidor:
CinemaServer.java
packageud3.examples.cinema.server;importud3.examples.cinema.models.Film;importjava.io.IOException;importjava.net.ServerSocket;importjava.net.Socket;importjava.util.ArrayList;importjava.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 */publicclassCinemaServer{privatefinalServerSocketserver;privatefinalList<CinemaServerHandler>clients;privatefinalList<Film>films;privatebooleanrunning;/** * Crea un servidor CinemaServer en el port port especificat. * * @param port Port on escoltarà el servidor * @throws IOException Excepcions del constructor ServerSocket */publicCinemaServer(intport)throwsIOException{server=newServerSocket(port);clients=newArrayList<>();films=newArrayList<>();running=true;}/** * Afig una pel·licula al servidor. * @param film Pel·licula que s'afegirà */publicvoidaddFilms(Filmfilm){films.add(film);}/** * Obté les pel·licules disponibles en el servidor * @return Llista de pel·licules en el servidor */publicList<Film>getFilms(){returnfilms;}/** * 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. */publicFilmgetFilm(intid){returnfilms.get(id);}/** * Esborra un client de la llista de clients * @param client Client que es vol esborrar */publicvoidremoveClient(CinemaServerHandlerclient){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. */publicvoidrun(){while(running){try{// Escolta i esperà una nova connexió.Socketclient=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.");CinemaServerHandlerhandler=newCinemaServerHandler(client,this);clients.add(handler);// S'inicia CinemaServerHandler en un fil independenthandler.start();}catch(IOExceptione){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. */publicstaticvoidmain(String[]args){try{CinemaServerserver=newCinemaServer(1234);server.run();}catch(IOExceptione){thrownewRuntimeException(e);}}}
CinemaServerHandler.java
packageud3.examples.cinema.server;importud3.examples.cinema.models.CinemaMessageType;importud3.examples.cinema.models.Film;importud3.examples.cinema.models.CinemaMessage;importjava.io.*;importjava.net.Socket;/** * Classe que gestiona la comunicació del servidor * amb un únic client en un fil d'execució independent. */publicclassCinemaServerHandlerextendsThread{/** * Socket que permet comunicar-se amb el client. */privatefinalSocketclient;/** * Servidor CinemaServer */privatefinalCinemaServerserver;/** * Objecte ObjectInputStream que permet rebre objectes pel Socket. */privatefinalObjectInputStreamobjIn;/** * Objecte ObjectOutputStream que permet enviar objectes pel Socket. */privatefinalObjectOutputStreamobjOut;/** * 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ó */publicCinemaServerHandler(Socketclient,CinemaServerserver)throwsIOException{this.client=client;this.server=server;objIn=newObjectInputStream(client.getInputStream());objOut=newObjectOutputStream(client.getOutputStream());}/** * Fil d'execució independent per cada client. * <p> * El servidor espera peticions (CinemaMessage) 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. */@Overridepublicvoidrun(){try{// Obtenim objectes del tipus CinemaMessage del client fins que aquest es desconnecte.CinemaMessagereq;while((req=(CinemaMessage)objIn.readObject())!=null){if(req.type()==CinemaMessageType.POST){// Si la petició és del tipus POST// Recuperem la pel·licula de la peticióFilmfilm=(Film)req.object();// Afegim la pel·lícula al servidorserver.addFilms(film);// Enviem una resposta del tipus SUCCESS al client// indicant que la pel·licula s'ha afegit correctamentStringmessage=String.format("Film %s added.",film);CinemaMessageresponse=newCinemaMessage(CinemaMessageType.SUCCESS,null,message);System.out.println(message);objOut.writeObject(response);}elseif(req.type()==CinemaMessageType.GET){// Si la petició és del tipus GET// Recuperem la ID de pel·licula de la peticióintid=(Integer)req.object();// Enviem una resposta al clientCinemaMessageresponse;if(id>=server.getFilms().size())// Si no existeix la pel·licula, enviem una resposta del tipus ERRORresponse=newCinemaMessage(CinemaMessageType.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 solicitadaresponse=newCinemaMessage(CinemaMessageType.SUCCESS,server.getFilm(id),"");// Enviem la respostaobjOut.writeObject(response);}}client.close();}catch(IOException|ClassNotFoundExceptione){System.err.println("Error while handling client.");System.err.println(e.getMessage());}finally{this.server.removeClient(this);}}}
Client:
CinemaClient.java
packageud3.examples.cinema.client;importud3.examples.cinema.models.CinemaMessage;importud3.examples.cinema.models.CinemaMessageType;importud3.examples.cinema.models.Film;importjava.io.IOException;importjava.io.ObjectInputStream;importjava.io.ObjectOutputStream;importjava.net.Socket;importjava.util.Scanner;/** * Client que es connecta a un CinemaServer * i permet realitzar les accions de afegir o obtenir * pel·lícules del servidor */publicclassCinemaClient{/** * Socket que permet connectar-se amb el servidor */privatefinalSocketsocket;/** * Scanner per interactuar amb l'usuari */privatefinalScannerscanner;/** * Canal de comuncació per rebre objectes del servidor mitjançant el socket. */privatefinalObjectInputStreamobjIn;/** * Canal de comuncació per enviar objectes al servidor mitjançant el socket. */privatefinalObjectOutputStreamobjOut;/** * 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. */publicCinemaClient(Stringhost,intport)throwsIOException{this.scanner=newScanner(System.in);this.socket=newSocket(host,port);this.objOut=newObjectOutputStream(socket.getOutputStream());this.objIn=newObjectInputStream(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. */privatevoidsendFilm(Filmf)throwsIOException,ClassNotFoundException{// Creem una petició POST amb la pel·lículaCinemaMessagereq=newCinemaMessage(CinemaMessageType.POST,f,"");// Enviem la peticióobjOut.writeObject(req);// Esperem una resposta del servidorCinemaMessageresponse=(CinemaMessage)objIn.readObject();// Comprovem si hi ha hagut algun error i mostrem el missatge de repostaif(response.type()==CinemaMessageType.ERROR)System.err.printf("ERROR: %s\n",response.message());elseif(response.type()==CinemaMessageType.SUCCESS)System.out.printf("%s\n",response.message());}/** * 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. */privateFilmreceiveFilm(intid)throwsIOException,ClassNotFoundException{// Creem una petició del tipus GET amb la IDCinemaMessagereq=newCinemaMessage(CinemaMessageType.GET,id,"");// Enviem la peticióobjOut.writeObject(req);// Esperem una resposta del servidorCinemaMessagein=(CinemaMessage)objIn.readObject();// Si hi ha hagut algun error, mostrem el missatgeif(in.type()==CinemaMessageType.ERROR)System.err.printf("ERROR: %s\n",in.message());// Retornem l'objecte rebutreturn(Film)in.object();}/** * Mostra les accions del menú */privatevoidprintMenuActions(){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 */privateintaskUserAction(intmax){System.out.print("Introdueix la teua elecció: ");intaction=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();}returnaction;}/** * Mostra el menu principal de l'aplicació. */publicvoidmenu(){booleanexit=false;while(!exit){printMenuActions();intaction=askUserAction(2);switch(action){case0->{exit=true;}case1->{try{addFilm();}catch(IOException|ClassNotFoundExceptione){System.err.println("Error afegint una pel·lícula.");}}case2->{try{getFilm();}catch(IOException|ClassNotFoundExceptione){System.err.println("Error obtenint una pel·lícula.");}}}}try{this.socket.close();}catch(IOExceptionignored){}}/** * 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. */privatevoidaddFilm()throwsIOException,ClassNotFoundException{System.out.println("AFEGIR PEL·LÍCULA");System.out.print("Introdueix el nom de la pel·lícula: ");Stringnom=scanner.nextLine();System.out.print("Introdueix el any de publicació de la pel·lícula: ");intyear=scanner.nextInt();System.out.print("Introdueix la duració de la pel·lícula: ");intduration=scanner.nextInt();Filmfilm=newFilm(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. */privatevoidgetFilm()throwsIOException,ClassNotFoundException{System.out.println("OBTENIR PEL·LÍCULA");System.out.print("Introdueix la id de la pel·lícula: ");intid=scanner.nextInt();Filmfilm=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. */publicstaticvoidmain(String[]args){System.out.println("Connectant-se amb el servidor...");try{CinemaClientcinema=newCinemaClient("localhost",1234);cinema.menu();}catch(IOExceptione){System.err.println("Error connectant-se amb el servidor.");}}}
L'objectiu principal de l'aplicació és gestionar pel·lícules. Per això s'ha creat la classe
Film, que representa una pel·lícula. L'aplicació ha de permetre enviar pel·lícules en els
dos sentits i, per indicar al servidor quina acció es vol realitzar, s'ha creat l'enumeració
CinemaMessageType, que classifica cada petició en els tipus següents:
GET: el client envia una petició d'aquest tipus al servidor per sol·licitar-li una
pel·lícula.
POST: el client envia una petició d'aquest tipus al servidor per enviar-li una
pel·lícula.
SUCCESS: el servidor respon amb aquest tipus si l'acció sol·licitada s'ha portat a terme
correctament.
ERROR: el servidor respon amb aquest tipus si hi ha hagut algun error en processar
l'acció sol·licitada.
Per últim, s'ha creat l'objecte CinemaMessage, que representa una petició al servidor o una
resposta al client. Està implementat com un record de Java i conté el tipus de missatge
(CinemaMessageType), un Object que es pot adjuntar i un missatge (String).
Aquesta classe està pensada per ser enviada mitjançant un ObjectOutputStream i rebuda per un
ObjectInputStream, que es poden crear amb el OutputStream i el InputStream del Socket,
respectivament.
Client:
ObjectOutputStreamobjOut=newObjectOutputStream(socket.getOutputStream());CinemaMessagereq=newCinemaMessage(CinemaMessageType.POST,newFilm("La vida es bella",1997,116));objOut.writeObject(req);
La classe CinemaServer implementa un servidor que gestiona pel·lícules. Aquest servidor
escolta en el port especificat i espera 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, rep una pel·lícula i l'afig al servidor.
Si la petició és del tipus GET, busca la pel·lícula sol·licitada i la retorna al client.