venerdì 15 ottobre 2010

Lubuntu 10.04, prova su Aspire One

Lubuntu è un sistema operativo GNU/Linux derivato da Ubuntu, la prima release risale a maggio 2010 (derivata da Ubuntu 10.04, Lucid Lynx). Il window manager utilizzato è Openbox, particolarmente leggero e adatto a dispositivi con poche risorse. Trovate altre notizie sulla homepage ufficiale della distribuzione. Perché non provarlo sull'Aspire One 110L?

La versione provata è stata la 10.04, ho scaricato l'immagine del sistema operativo (un file .iso di circa 520MB) e creato la pen-drive attraverso Unetbootin. Come dicevo all'inizio il window manager utilizzato è Openbox, il desktop environment è invece LXDE (Lightweight X11 Desktop Environment), da cui deriva appunto il nome dato alla distribuzione.

L'avvio ha richiesto, a partire dall'accensione, circa 70 secondi. Il tempo effettivamente utile all'avvio è pertanto minore se a quest'ultimo si sottrae infatti il tempo impiegato per selezionare la pen-drive usb in fase di avvio con il tasto F12. Va poi considerata la bontà della periferica esterna utilizzata, nel mio caso una pen-drive usb di 4GB prodotta da Kingston. Prestazioni maggiori si ottengono installando il sistema operativo sul disco fisso. La versione 10.04 di Lubuntu è basata sul kernel Linux 2.6.32-21. Lubuntu è una distribuzione minimale, il desktop offerto all'utente all'avvio ne fa intuire sicuramente le scelte:

Il software installato di default segue la politica che sta alla base della distribuzione, non impiegare troppa memoria ram nell'esecuzione dei processi. Nel menu delle applicazioni disponiamo (vi elenco i programmi più importanti): del file manager, PCMan File Manager (la versione 0.9.5); Image Viewer, un visualizzatore di immagini; mPaint graphic editor, un editor di immagini; Chromium web brower, un browser per Internet; Pidgin Internet messenger, per la messaggistica istantanea; Abiword e Gnumeric rispettivamente per editare file di testo e fogli elettronici; Aqualung e Gnome MPlayer per i file audio.

La memoria ram usata è davvero poca, nel mio caso 97MB su 485MB disponibili, come mostra il tool xfce4-taskmanager:

Il sistema operativo si presenta reattivo ai click dell'utente, durante la prova da me effettuata non sembrava di eseguire il sistema operativo da pen-drive usb! Vediamo nello specifico cosa funziona: il lettore di schede a destra dell'Aspire One non sembra funzionare (problema noto anche in altre distribuzioni Ubuntu), nessun problema per il lettore a sinistra che ha quindi letto il contenuto della scheda SD da me inserita. Le pen-drive usb inserite vengono montate automaticamente, l'audio viene riprodotto bene. Non ho avuto modo di provare il microfono. La webcam viene riconosciuta da Cheese che ne mostra correttamente l'output su video (con poca luce, si può rimediare nelle impostazioni). La scheda wifi viene riconosciuta e l'accesso al mio router avviene senza problemi. La potenza del segnale è stata del 55% (la distanza dal mio router è di circa 9 metri). Il modulo usato per la scheda wifi è l'ath5k per schede Atheros della serie 5xxx. La connessione di rete riesce anche attraverso il cavo ethernet. La ventola di raffreddamento è gestita dal modulo acerhdf. Non funzionano i tasti combinati, le combinazioni di tasti con il tasto "Fn" per intenderci. Il volume può essere aggiustato dal pannello inferiore ma come fare per la luminosità dello schermo? Nessuna notifica viene mostrata all'utente se la scheda wifi viene disabilitata attraverso l'apposito tasto. Dopo averlo toccato, disabilitando la schede di rete, la connessione è caduta dopo qualche secondo. L'interruttore, quindi, funziona ma lo stato non viene notificato (un problema noto anche su altre distribuzioni)!

Questa prova è stata effettuata su un Aspire One 110L, con SSD da 8GB e 512MB di memoria ram. Questa distribuzione è davvero ottima per il mio modello, l'hardware è ormai supportato a dovere dal kernel di Linux e la poca memoria ram richiesta dal sistema operativo nonché il basso utilizzo della cpu permette di avere un sistema operativo sempre pronto e reattivo alla risposta. Il sistema operativo si lascia piacere proprio per le caratteristiche dette poco fa (obiettivo, quindi, raggiunto dalla distribuzione), dall'altro lato bisogna però accettare il compromesso con l'ambiente grafico. Se le due cose vanno nella stessa direzione allora Lubuntu è la distribuzione fatta proprio per voi!

venerdì 8 ottobre 2010

La classe InetAddress

La classe InetAddres (del package java.net) è in grado di rappresentare un indirizzo IP di rete. All'interno troviamo alcuni metodi statici (non hanno bisogno di un'istanza della classe) particolarmente utili:
  • static InetAddress[] getAllByName(String host): ritorna un array di indirizzi IP riconducibili al nome dell'host;
  • static InetAddress getByAddress(byte[] addr): ritorna un oggetto InetAddress a partire da un indirizzo IP (passato in forma binaria, per IPv4 occorrono 4 byte);
  • static InetAddress getByAddress(String host, byte[] addr): ritorna un oggetto InetAddress riconducibile ai paramatri host ed addr;
  • static InetAddress getByName(String host): ritorna un oggetto InetAddress corrispondente all'host passato;
In caso di errori, nome dell'host o indirizzo IP errato, questi metodi scatenano un'eccezione di tipo UnknownHostException, ragion per cui vanno sempre innescati all'interno di blocchi try/catch. Vi occorre sapere l'indirizzo IP di un host? Poche righe di codice e il problema è risolto:
package address;

import java.net.*;

public class AddressTest {
 public static void main(String[] args) {
  try {
   String hostname=args[0];
   InetAddress ip=InetAddress.getByName(hostname);
   System.out.println(" Hostname: "+hostname);
   System.out.println(" Indirizzo IP: "+ip.toString().substring(args[0].length()+1));
  }
  catch(ArrayIndexOutOfBoundsException e) {
   System.out.println("Attenzione: fornire il nome dell'host come parametro!");
  }
  catch(UnknownHostException e) {
   System.out.println("Attenzione: hostname sconosciuto!");
  }
 }
}

Qui trovate il file jar del programma.

giovedì 7 ottobre 2010

Applicazioni client/server con Java

La comunicazione e lo scambio dell'informazione sono da sempre alla base di Internet. Anche Java, nato inizialmente con lo stesso obiettivo, supporta la programmazione di rete. Il package java.net raccoglie le interfacce, le classi e le possibili eccezioni utili proprio alla programmazione per questo tipo di applicazioni, spesse volte dette anche applicazioni client/server.

Queste applicazioni osservano un noto modello di programmazione che è appunto il modello client/server. Durante lo scambio dell'informazione il suddetto modello assegna ai partecipanti dei ruoli ben precisi: il client è l'applicazione che, interfacciandosi attraverso la rete al server, richiede un preciso servizio; il server è invece quell'applicazione che soddisfa, attraverso la rete, la richiesta del client.

La comunicazione fra client e server si svolge inevitabilmente attraverso la rete, grazie ai protocolli di rete. A livello di trasporto essa può allora avvenire attraverso due dei principali protocolli di trasporto: TCP o UDP. Il protocollo di rete TCP realizza un canale logico per la connessione affidabile e orientato alla connessione, si impegna a fare del suo meglio per garantire l'arrivo dei pacchetti e l'ordine con cui essi vengono spediti. Il protocollo di rete UDP è assai più sbrigativo, non prevede una connessione e non garantisce ne l'arrivo ne l'ordine dei pacchetti. UDP viene usato, ad esempio, per lo streaming dei flusi audio e/o video. La scelta del protocollo per il trasporto può quindi variare in base al tipo di applicazione che abbiamo in mente.

Tale scelta va fatta ancora prima di mettersi a scrivere il codice dell'applicazione. Java implementa nel package jav.net diverse classi, alcune di queste sono orientate al protocollo TCP, altre invece seguono il protocollo UDP. La comunicazione fra client e server avviene attraverso le socket (presa o spina). Ogni applicazione si può dotare di una socket, la finestra sulla rete.

La classe Socket implementa uno dei due lati della connessione di rete (end-point) ed è orientata al protocollo TCP, tipicamente quella di un'applicazione client. La classe ServerSocket, invece, realizza la socket per l'applicazione server. Tale socket viene usata dai server per rimanere in ascolto e accettare le richieste di connessione dei client. La classe DatagramSocket realizza l'astrazione necessaria all'invio e alla ricezione dei dati attraverso il protocollo UDP.

In questo articolo mi occuperò esclusivamente delle classi Socket e ServerSocket. E' evidente che l'applicazione server deve trovarsi già in esecuzione prima di ricevere richieste dai client che altrimenti si perdono nella rete. Osserveremo allora prima la struttura di un'applicazione server. L'istanza di una socket per il server avviene attraverso il costruttore della classe ServerSocket:

public ServerSocket(int port) throws IOException

dove port rappresenta il numero di porta usato dal server, presso il proprio indirizzo di rete, per l'ascolto. Alcuni numeri di porta sono assegnati di default e spettano a vari servizi di rete (sono cioè usati da altri protocolli per applicazioni). Se l'istanza di una ServerSocket non riesce vi consiglio di cambiare il numero della porta usato nel costruttore, potrebbe infatti essere già impegnato! Il costruttore va poi eseguito all'interno di un blocco try/catch per intercettare proprio l'errore che vi descrivevo poco fa. Esistono altri tipi di cotruttori, date come sempre uno sguardo alla documentazione della classe:

server=new ServerSocket(10000);

Se l'istanza della socket riesce possiamo allora collocare la stessa in uno stato di attesa. Il metodo accept() blocca l'esecuzione del programma e lo pone in attesa di richieste di connessione. Il metodo si sblocca alla prima richiesta del client, ritornando l'oggetto socket usato dallo stesso! Da questo oggetto il server, se vuole, può estrarre l'indirizzo IP con il metodo getInetAddress():

client=server.accept();

Affinchè il server possa realmente mandare al client dei byte (testo o informazioni varie) deve necessariamente estrarre dalla socket del client gli stream per l'input:

input=new BufferedReader(new InputStreamReader(client.getInputStream()));
e per l'output:
output=new PrintWriter(client.getOutputStream(),true);
I metodi getInputStream() e getOutputStream() ritornano rispettivamente InputStream e OutputStream. La comunicazione adesso è davvero semplice, se il server intende leggere cosa ha scritto il client non deve fare altro che usare lo stream input. Nell'esempio ho appositamente convogliato lo stream di input in un oggetto BufferedReader che semplifica la lettura con il metodo readLine() (che ritorna una stringa). Per leggere, ad esempio, si potrebbe avere questa istruzione:
String clientMessage=input.readLine();
Se il server intende scrivere qualcosa al client userà allora lo stream di output:
output.println("Ciao!");
Prestate attenzione alla chiusura dell'applicazione e liberate sempre le risorse impegnate. Chiudete prima gli stream di input e di output, quindi la socket del client e infine quella del server:
input.close();
output.close();
client.close();
server.close();
L'applicazione client opera allo stesso modo di quella server, la prima operazione da fare è la connessione al server (che sarà in attesa, bloccato sul metodo accept()). Il client può allora usare la classe Socket in questo modo:
client=new Socket(ip,10000);
dove ip è la stringa con l'indirizzo ip del server e 10000 il numero di porta del servizio. Se la connessione riesce senza eccezzioni (anche questa operazione, così come le altre, va sempre innescata all'interno di un blocco try/catch) il client può allora ricavare dalla socket (ora connessa al client) gli stream per l'input e per l'output. Per l'input, come visto prima, ad esempio:
input=new BufferedReader(new InputStreamReader(client.getInputStream()));
per l'output, analogamente:
output=new PrintWriter(client.getOutputStream(),true);
Così come fa il server, anche il client può usare gli strem per scrivere al server e per leggerne i messaggi inviati dallo stesso, il protocollo TCP realizza infatti un canale bidirezionale. Le applicazioni client e server dovranno in ogni modo accordarsi su quando e come scriversi, dovranno cioè osservare un protocollo per lo scambio dei messaggi (un compito è lasciato alla fantasia del programmatore). Non dimenticate di chiudere nell'ordine esatto le risorse impegnate anche nell'applicazione client.
Qui potete scaricare l'archivio jar di un'applicazione server, a questo indirizzo invece trovate il client. Lanciate prima il server (con il comando "java -jar ServerSocket.jar", nella cartella che ospita il file scaricato) e successivamente il client (con "java -jar ClientSocket.jar", sempre nella cartella che ospita il file scaricato) se volete utilizzare l'applicazione nella vostra rete locale (con indirizzo ip di loopback 127.0.0.1). Altrimenti eseguite le due applicazioni su due computer con indirizzo ip differente. L'esecuzione del client inizia proprio con la richiesta dell'indirizzo ip del server (la porta usata è di default la 10000), se non conoscete il vostro indirizzo ip potete ricavarlo sfruttandore ad esempio i tanti servizi di rete, ad esempio qui. Qui potete vedere la parte server in esecuzione (su un computer con sistema operativo Linux, il mio Aspire One!):

Qui invece la parte client (in esecuzione su un PC con sistema operativo Windows XP):

L'applicazione permette lo scambio di messaggi fra client e server, dopo aver effettuato la connessione al server il client inizia a scrivere al server (che si mette quindi in attesa di un messaggio). Subito dopo è il client che si mette in attesa di un messaggio di risposta da parte del server. La comunicazione si svolge quindi su turni. Va inoltre osservata la capacità del server di rispondere a una sola richiesta di connessionne del client. Per implementare un server che accetta più connessioni quest'ultimo deve necessariamente usare una socket per l'ascolto e generare ad ogni connessione una socket per il client.