Nota che questi costruttori possono lanciare l'eccezione di tipo java.net.MalformedURLException , se la stringa passata nel costruttore non rappresenta un'url legale (come formato). MalformedURLException è sottoclasse di IOException e richiede il codice di gestione delle eccezioni obbligatorio.
Una volta avuto un oggetto di tipo URL, puoi chiamare il metodo openConnection() per aprire una connessione. Questo metodo restituisce un oggetto di tipo URLConnection. L'URLConnection a sua volta può essere usata per creare un InputStream per leggere dati dalla risorsa rappresentata dall'URL. Questo può essere fatto invocando il metodo getInputStream(). Per esempio:
URL url =newURL(urlAddressString);URLConnection connection =url.openConnection();InputStream in =connection.getInputStream();
I metodi openConnection() e getInputStream() possono entrambi generare IOException. Una volta che l'InputStream è creato, si può leggeer da esso nel modo usuale, incluso wrapparlo in un altro input stream, come BufferedReader o Scanner. Le letture dallo stream possono, naturalmente, generare delle eccezioni.
Un altro metodo d'istanza utile di URLConnection è getContentType(), che ritorna una String che descrive il tipo di informazione associata all'URL. Il valore di ritorno può essere null se il tipo dell'informazione non è conosciuto o se non è possibile determinare il tipo. L'informazione sul tipo potrebbe essere non disponibile finché l'input stream non sia creato, così in genere dovrebbe essere chiamato getContentType() dopo getInputStream(). La stringa ritornata da getContentType() è nel formato chiamato mime type. Mime types includono "text/plain", "text/html", "image/jpeg", "image/png" e molte altre. Tutti i mime type, contengono due parti: una parte generale come "text" o "image" e un tipo più specifico all'interno della categoria generale, come "html" o "png". Se ad esempio fossimo interessati solo a dati di tipo testo, si potrebbe controllare che la stringa ritornata da getContentType() inizi con "text". I Mime type furono all'inizio utilizzati per descrivere il contenuto dei messaggi di posta elettronica (email). Il nome sta per "Multipurpouse Internet Email Extensions". Essi sono usati oramai in modo universale per specificare il tipo di informazione di un file o altre risorse.
Sotto un piccolo esempio che sfrutta tutto questo per leggere i dati da un URL. La funzione sotto, apre una connessione all'URL specificata, controllo che il tipo di dato specificato nell'URl sia di tipo testo, e allora visualizza il dato a console.
/** * This subroutine attempts to copy text from the specified URL onto the screen. * Any error must be handled by the caller of this subroutine. * @param urlString contains the URL in text form */staticvoidreadTextFromURL( String urlString ) throws IOException {/* Open a connection to the URL, and get an input stream for reading data from the URL. */URL url =newURL(urlString);URLConnection connection =url.openConnection();InputStream urlData =connection.getInputStream();/* Check that the content is some type of text. Note: If getContentType() method were called before getting the input stream, it is possible for contentType to be null only because no connection can be made. The getInputStream() method will throw an error if no connection can be made. */String contentType =connection.getContentType();System.out.println("Stream opened with content type: "+ contentType);System.out.println();if (contentType ==null||contentType.startsWith("text") ==false)thrownewIOException("URL does not seem to refer to a text file.");System.out.println("Fetching context from "+ urlString +" ...");System.out.println();/* Copy lines of text from the input stream to the screen, until end-of-file is encountered (or an error occurs). */BufferedReader in; // For reading from the connection's input stream. in =newBufferedReader( new InputStreamReader(urlData) );while (true) {String line =in.readLine();if (line ==null)break;System.out.println(line); }in.close(); } // end readTextFromURL()
L’astrazione principale per la programmazione di rete è il Socket:
Identifica le risorse che permettono di stabilire un canale di comunicazione con un altro processo (eventualmente su un’altra macchina).
Tutto quello che viene trasmesso sono pacchetti TCP.
I partecipanti di una comunicazione tramite socket sono individuati da:
indirizzo IP.
numero di porta.
In Java si usa il package java.net
classi: Socket, ServerSocket
Comunicazione client-server
il java.net.ServerSocket del server si mette in attesa di una connessione;
il socket del client si collega al server socket;
viene creato un socket sul server e quindi stabilito un canale di comunicazione con il client;
Mettersi in attesa di una connessione (lato Server)
Creare un’istanza della classe java.net.ServerSocket specificando il numero di porta su cui rimanere in ascolto.
ServerSocket serverSocket = new ServerSocket(4567);
Chiamare il metodo accept() che fa in modo che il server rimanga in ascolto di una richiesta di connessione (la porta non deve essere già in uso)
Socket socket = serverSocket.accept();
Quando il metodo completa la sua esecuzione la connessione col client è stabilita e viene restituita un’istanza di java.net.Socket connessa al client remoto;
Aprire una connessione (lato Client)
Aprire un socket specificando indirizzo IP e numero di porta del server: Socket socket = new Socket(“127.0.0.1”, 4567)
All’indirizzo e numero di porta specificati ci deve essere in ascolto un processo server. Se la connessione ha successo si usano (sia dal lato client che dal lato server) gli stream associati al socket per permettere la comunicazione tra client e server (e viceversa) :
Scanner in =newScanner(socket.getInputStream()); PrintWriter out =newPrintWriter(socket.getOutputStream());
NB: per fare test in locale si può utilizzare l’indirizzo (riservato) di localhost, 127.0.0.1, che corrisponde al computer locale stesso.
Esempio Server: EchoServer
Si crei un server che accetta connessioni TCP sulla porta 1337.
Una volta accettata la connessione il server leggerà ciò che viene scritto una riga alla volta e ripeterà nella stessa connessione ciò che è stato scritto.
Se il server riceve una riga “quit” chiuderà la connessione e terminerà la sua esecuzione.
publicvoidstartServer() throws IOException {// apro una porta TCP serverSocket =newServerSocket(port);System.out.println("Server socket ready on port:"+ port);// resto in attesa di una connessioneSocket socket =serverSocket.accept();System.out.println("Received client connection");// apro gli stream di input e output per leggere// e scrivere nella connessione appena ricevutaScanner in =newScanner(socket.getInputStream());PrintWriter out =newPrintWriter(socket.getOutputStream());// leggo e scrivo nella connessione finche'non// ricevo "quit"while (true) {String line =in.nextLine();if (line.equals("quit")) {break; } else {out.println("Received: "+ line);out.flush(); } }// chiudo gli stream e il socketSystem.out.println("Closing sockets");in.close();out.close();socket.close();serverSocket.close(); }
Esempio Client: EchoClient
Si crei un client che si colleghi, utilizzando il protocollo TCP, alla porta 1337 dell’indirizzo IP 127.0.0.1.
Una volta stabilita la connessione il client legge, una riga alla volta, dallo standard input e invia il testo digitato al server.
Il client inoltre stampa sullo standard output le risposte ottenute dal server.
Il client deve terminare quando il server chiude la connessione.
packageio.checksound.networking;importjava.io.IOException;importjava.io.PrintWriter;importjava.net.ServerSocket;importjava.net.Socket;importjava.util.Scanner;publicclassEchoServer {privateint port;privateServerSocket serverSocket;publicEchoServer(int port) {this.port= port; }publicvoidstartServer() throwsIOException {// apro una porta TCP serverSocket =newServerSocket(port);System.out.println("Server socket ready on port:"+ port);// resto in attesa di una connessioneSocket socket =serverSocket.accept();System.out.println("Received client connection");// apro gli stream di input e output per leggere// e scrivere nella connessione appena ricevutaScanner in =newScanner(socket.getInputStream());PrintWriter out =newPrintWriter(socket.getOutputStream());// leggo e scrivo nella connessione finche'non// ricevo "quit"while (true) {String line =in.nextLine();if (line.equals("quit")) {break; } else {out.println("Received: "+ line);out.flush(); } }// chiudo gli stream e il socketSystem.out.println("Closing sockets");in.close();out.close();socket.close();serverSocket.close(); }publicstaticvoidmain(String[] args) {EchoServer server =newEchoServer(1337);try {server.startServer(); } catch (IOException e) {System.err.println(e.getMessage()); } }}
NOTA BENE: La versione del codice del server non può gestire più client che si connettono contemporaneamente essendoci un solo flusso di esecuzione, quello del main thread, che risulta impegnato nella comunicazione con il primo client che si connette e finché quest'ultimo rimane connesso non può accettare connessioni da altri client. Quando il primo client si sconnette, solo allora, il server può accettare la connessione del secondo client che aveva tentato di connettersi ed era rimasto in attesa. E così via con i client che si connettono in seguito; UN SOLO CLIENT VIENE SERVITO ALLA VOLTA.
Impareremo con il capitolo 'I Thread e networking' come Java implementa la gestione di più client che si connettono contemporaneamente a un server: la soluzione è tramite l'utilizzo dei thread, meccanismo che permette di avere più flussi di esecuzione all'interno della stesso programma. I thread sono l'argomento del prossimo capitolo. Un po' di pazienza quindi.
UN SEMPLICE DATE SERVER
Il server accetta le connessioni dai client e invia la data corrente sull'output stream.
packageio.checksound.networking;importjava.net.*;importjava.io.*;importjava.util.Date;/** * This program is a server that takes connection requests on * the port specified by the constant LISTENING_PORT. When a * connection is opened, the program sends the current time to * the connected socket. The program will continue to receive * and process connections until it is killed (by a CONTROL-C, * for example). Note that this server processes each connection * as it is received, rather than creating a separate thread * to process the connection. */publicclassDateServer {publicstaticfinalint LISTENING_PORT =32007;publicstaticvoidmain(String[] args) {ServerSocket listener; // Listens for incoming connections.Socket connection; // For communication with the connecting program./* Accept and process connections forever, or until some error occurs. (Note that errors that occur while communicating with a connected program are caught and handled in the sendDate() routine, so they will not crash the server.) */try { listener =newServerSocket(LISTENING_PORT);System.out.println("Listening on port "+ LISTENING_PORT);while (true) {// Accept next connection request and handle it. connection =listener.accept(); sendDate(connection); } }catch (Exception e) {System.out.println("Sorry, the server has shut down.");System.out.println("Error: "+ e);return; } } // end main() /** * The parameter, client, is a socket that is already connected to another * program. Get an output stream for the connection, send the current time, * and close the connection. */privatestaticvoidsendDate(Socket client) {try {System.out.println("Connection from "+client.getInetAddress().toString() );Date now =newDate(); // The current date and time.PrintWriter outgoing; // Stream for sending data. outgoing =newPrintWriter( client.getOutputStream() );outgoing.println( now.toString() );outgoing.flush(); // Make sure the data is actually sent!client.close(); }catch (Exception e){System.out.println("Error: "+ e); } } // end sendDate()} //end class DateServer
Il codice del client che fa la richiesta e riceve la data sotto forma di stringa sull'output di input:
packageio.checksound.networking;importjava.net.*;importjava.util.Scanner;importjava.io.*;/** * This program opens a connection to a computer specified * as the first command-line argument. If no command-line * argument is given, it prompts the user for a computer * to connect to. The connection is made to * the port specified by LISTENING_PORT. The program reads one * line of text from the connection and then closes the * connection. It displays the text that it read on * standard output. This program is meant to be used with * the server program, DataServer, which sends the current * date and time on the computer where the server is running. */publicclassDateClient {publicstaticfinalint LISTENING_PORT =32007;publicstaticvoidmain(String[] args) {String hostName; // Name of the server computer to connect to.Socket connection; // A socket for communicating with server.BufferedReader incoming; // For reading data from the connection./* Get computer name from command line. */if (args.length>0) hostName = args[0];else {Scanner stdin =newScanner(System.in);System.out.print("Enter computer name or IP address: "); hostName =stdin.nextLine(); }/* Make the connection, then read and display a line of text. */try { connection =newSocket( hostName, LISTENING_PORT ); incoming =newBufferedReader( new InputStreamReader(connection.getInputStream()) );String lineFromServer =incoming.readLine();if (lineFromServer ==null) {// A null from incoming.readLine() indicates that// end-of-stream was encountered.thrownewIOException("Connection was opened, "+"but server did not send any data."); }System.out.println();System.out.println(lineFromServer);System.out.println();incoming.close(); }catch (Exception e) {System.out.println("Error: "+ e); } } // end main()} //end class DateClient
UNA SEMPLICE APPLICAZIONE CHAT DI TIPO CLIENT/SERVER
Codice di un server che assieme a un client offre un servizio di chat.
packageio.checksound.networking;importjava.net.*;importjava.util.Scanner;importjava.io.*;/** * This program is one end of a simple command-line interface chat program. * It acts as a server which waits for a connection from the CLChatClient * program. The port on which the server listens can be specified as a * command-line argument. If it is not, then the port specified by the * constant DEFAULT_PORT is used. Note that if a port number of zero is * specified, then the server will listen on any available port. * This program only supports one connection. As soon as a connection is * opened, the listening socket is closed down. The two ends of the connection * each send a HANDSHAKE string to the other, so that both ends can verify * that the program on the other end is of the right type. Then the connected * programs alternate sending messages to each other. The client always sends * the first message. The user on either end can close the connection by * entering the string "quit" when prompted for a message. Note that the first * character of any string sent over the connection must be 0 or 1; this * character is interpreted as a command. */publicclassCLChatServer { /** * Port to listen on, if none is specified on the command line. */staticfinalint DEFAULT_PORT =1728; /** * Handshake string. Each end of the connection sends this string to the * other just after the connection is opened. This is done to confirm that * the program on the other side of the connection is a CLChat program. */staticfinalString HANDSHAKE ="CLChat"; /** * This character is prepended to every message that is sent. */staticfinalchar MESSAGE ='0'; /** * This character is sent to the connected program when the user quits. */staticfinalchar CLOSE ='1';publicstaticvoidmain(String[] args) {int port; // The port on which the server listens.ServerSocket listener; // Listens for a connection request.Socket connection; // For communication with the client.BufferedReader incoming; // Stream for receiving data from client.PrintWriter outgoing; // Stream for sending data to client.String messageOut; // A message to be sent to the client.String messageIn; // A message received from the client.Scanner userInput; // A wrapper for System.in, for reading// lines of input from the user./* First, get the port number from the command line, or use the default port if none is specified. */if (args.length==0) port = DEFAULT_PORT;else {try { port=Integer.parseInt(args[0]);if (port <0|| port >65535)thrownewNumberFormatException(); }catch (NumberFormatException e) {System.out.println("Illegal port number, "+ args[0]);return; } }/* Wait for a connection request. When it arrives, close down the listener. Create streams for communication and exchange the handshake. */try { listener =newServerSocket(port);System.out.println("Listening on port "+listener.getLocalPort()); connection =listener.accept();listener.close(); incoming =newBufferedReader( new InputStreamReader(connection.getInputStream()) ); outgoing =newPrintWriter(connection.getOutputStream());outgoing.println(HANDSHAKE); // Send handshake to client.outgoing.flush(); messageIn =incoming.readLine(); // Receive handshake from client.if (!HANDSHAKE.equals(messageIn) ) {thrownewException("Connected program is not a CLChat!"); }System.out.println("Connected. Waiting for the first message."); }catch (Exception e) {System.out.println("An error occurred while opening connection.");System.out.println(e.toString());return; }/* Exchange messages with the other end of the connection until one side or the other closes the connection. This server program waits for the first message from the client. After that, messages alternate strictly back and forth. */try { userInput =newScanner(System.in);System.out.println("NOTE: Enter 'quit' to end the program.\n");while (true) {System.out.println("WAITING..."); messageIn =incoming.readLine();if (messageIn.length() >0) {// The first character of the message is a command. If // the command is CLOSE, then the connection is closed. // Otherwise, remove the command character from the // message and proceed.if (messageIn.charAt(0) == CLOSE) {System.out.println("Connection closed at other end.");connection.close();break; } messageIn =messageIn.substring(1); }System.out.println("RECEIVED: "+ messageIn);System.out.print("SEND: "); messageOut =userInput.nextLine();if (messageOut.equalsIgnoreCase("quit")) {// User wants to quit. Inform the other side// of the connection, then close the connection.outgoing.println(CLOSE);outgoing.flush(); // Make sure the data is sent!connection.close();System.out.println("Connection closed.");break; }outgoing.println(MESSAGE + messageOut);outgoing.flush(); // Make sure the data is sent!if (outgoing.checkError()) {thrownewIOException("Error occurred while transmitting message."); } } }catch (Exception e) {System.out.println("Sorry, an error has occurred. Connection lost.");System.out.println("Error: "+ e);System.exit(1); } } // end main()} //end class CLChatServer
Questo programma è un po' più robusto del DateServer. Prima di tutto utilizza un handshake (stretta di mano, letteralmente) per essere sicuro che il client che sta cercando di connettersi è realmente un programma CLChatClient. Un handshake è una semplice informazione invita tra il client e il server come parte del settaggio della connessione, prima che i dati vengano inviati. In questo caso, ogni lato della connessione, invia una stringa all'altro lato per identificare se stessa. L'handshake fa parte del protocollo che si è convenuto tra CLChatClient e CLChatServer. Un protocollo è una specifica dettagliata di quali dati e messaggi possono essere scambiati su una connessione e in quale ordine. Quando si implementa un'applicazione di tipo client/server, i design del protocollo è una problematica importante. Un altro aspetto del CLChat protocol, è che dopo l'handshake, ogni linea di testo che è inviata sulla connessione inizia con una carattere che agisce come comando. Se il carattere è 0, il resto della stringa è il messaggio da un utente all'altro. Se il carattere è 1, la stringa indica che l'utente ha digitato il comando "quit", è deve essere eseguito lo shut down della connessone.
Il codice del client:
packageio.checksound.networking;importjava.net.*;importjava.util.Scanner;importjava.io.*;/** * This program is one end of a simple command-line interface chat program. * It acts as a client which makes a connection to a CLChatServer program. * The computer to connect to can be given as a command line argument. If * it is not, then the program prompts the user for computer name or IP and * for port number. If a computer is specified on the command line, a port * number can also be specified as the second command-line argument; if no * second argument is specified, the default port number is used. * Once a connection has been established, the two ends of the connection * each send a HANDSHAKE string to the other, so that both ends can verify * that the program on the other end is of the right type. Then the connected * programs alternate sending messages to each other. The client always sends * the first message. The user on either end can close the connection by * entering the string "quit" when prompted for a message. Note that the first * character of any string sent over the connection must be 0 or 1; this * character is interpreted as a command. */classCLChatClient { /** * Port number on server, if none is specified on the command line. */staticfinalString DEFAULT_PORT ="1728"; /** * Handshake string. Each end of the connection sends this string to the * other just after the connection is opened. This is done to confirm that * the program on the other side of the connection is a CLChat program. */staticfinalString HANDSHAKE ="CLChat"; /** * This character is prepended to every message that is sent. */staticfinalchar MESSAGE ='0'; /** * This character is sent to the connected program when the user quits. */staticfinalchar CLOSE ='1';publicstaticvoidmain(String[] args) {String computer; // The computer where the server is running,// as specified on the command line. It can// be either an IP number or a domain name.String portStr; // Port number as a string.int port; // The port on which the server listens.Socket connection; // For communication with the server.BufferedReader incoming; // Stream for receiving data from server.PrintWriter outgoing; // Stream for sending data to server.String messageOut; // A message to be sent to the server.String messageIn; // A message received from the server.Scanner userInput; // A wrapper for System.in, for reading// lines of input from the user./* First, get the computer and port number. */if (args.length==0) {Scanner stdin =newScanner(System.in);System.out.print("Enter computer name or IP address: "); computer =stdin.nextLine();System.out.print("Enter port, or press return to use default:"); portStr =stdin.nextLine();if (portStr.length() ==0) portStr = DEFAULT_PORT; }else { computer = args[0];if (args.length==1) portStr = DEFAULT_PORT;else portStr = args[1]; }try { port=Integer.parseInt(portStr);if (port <=0|| port >65535)thrownewNumberFormatException(); }catch (NumberFormatException e) {System.out.println("Illegal port number, "+ args[1]);return; }/* Open a connection to the server. Create streams for communication and exchange the handshake. */try {System.out.println("Connecting to "+ computer +" on port "+ port); connection =newSocket(computer,port); incoming =newBufferedReader(new InputStreamReader(connection.getInputStream()) ); outgoing =newPrintWriter(connection.getOutputStream());outgoing.println(HANDSHAKE); // Send handshake to server.outgoing.flush(); messageIn =incoming.readLine(); // Receive handshake from server.if (!messageIn.equals(HANDSHAKE) ) {thrownewIOException("Connected program is not CLChat!"); }System.out.println("Connected. Enter your first message."); }catch (Exception e) {System.out.println("An error occurred while opening connection.");System.out.println(e.toString());return; }/* Exchange messages with the other end of the connection until one side or the other closes the connection. This client program sends the first message. After that, messages alternate strictly back and forth. */try { userInput =newScanner(System.in);System.out.println("NOTE: Enter 'quit' to end the program.\n");while (true) {System.out.print("SEND: "); messageOut =userInput.nextLine();if (messageOut.equalsIgnoreCase("quit")) {// User wants to quit. Inform the other side// of the connection, then close the connection.outgoing.println(CLOSE);outgoing.flush();connection.close();System.out.println("Connection closed.");break; }outgoing.println(MESSAGE + messageOut);outgoing.flush();if (outgoing.checkError()) {thrownewIOException("Error occurred while transmitting message."); }System.out.println("WAITING..."); messageIn =incoming.readLine();if (messageIn.length() >0) {// The first character of the message is a command. If // the command is CLOSE, then the connection is closed. // Otherwise, remove the command character from the // message and proceed.if (messageIn.charAt(0) == CLOSE) {System.out.println("Connection closed at other end.");connection.close();break; } messageIn =messageIn.substring(1); }System.out.println("RECEIVED: "+ messageIn); } }catch (Exception e) {System.out.println("Sorry, an error has occurred. Connection lost.");System.out.println(e.toString());System.exit(1); } } // end main()} //end class CLChatClient
RIASSUNTO
Gli esempi qui visti hanno il limite che i server possono servire un solo client alla volta. Per creare dei server che possano servire più client contemporaneamente, abbiamo bisogno di server multithread; impareremo in seguito cosa sono i thread e come utilizzarli per creare dei server che possano rispondere a più client contemporaneamente.