Java Tutorial - Parte 2 0.1
Un tutorial per esempi
Caricamento in corso...
Ricerca in corso...
Nessun risultato
Il giocatore remote: le comunicazioni

Introduzione

In questo capitolo si introduce una breve panoramica sui protocolli di comunicazione utilizzati dalle macchine per comunicare tra di loro ed esamineremo le classi che Java mette a disposizione del programmatore per facilitare il compito.

Storia

Le reti di computer nascono fin dagli albori del calcolo elettronico e già nei primi anni '50 la comunicazione tra le macchine era possibile: si trattava di architetture centralizzate nelle quali una unità di elaborazione centrale (detta mainframe) era collegata a due o più terminali stupidi:

Agli inizi degli anni '60, in piena guerra fredda tra USA e URSS, il rischio di una guerra globale termonucleare era molto alto e le autorità statunitensi erano ben conscie del fatto che le comunicazioni, sia umane che tra computer, sarebbero state un bersaglio strategico importantissimo in caso di guerra.

La architettura centralizzata si è rivelata quindi essere estremamente vulnerabile ad un eventuale attacco: sarebbe sufficente colpire il mainframe per abbattere l'intera rete.
Il governo statunitense affidò ad una agenzia speciale, la Defence Advanced Research Projects Agency (D.A.R.P.A.), l'incarico di sviluppare una architettura di rete in grado di resistere il più possibile, anche in caso di guerra nucleare. Sul finire degli anni '60 nacque così ARPANET, una architettura di rete che collegava 4 università degli Stati Uniti e che utilizzava un protocollo di comunicazione assolutamente innovativo chiamato Arpanet Protocol che sarebbe diventato di li a poco uno standard de facto utilizzato in tutto il mondo e che oggi conosciamo col nome di: Internet Protocol. Per approfondire questo argomento leggete: La storia di Internet.

Internet Protocol (IP)

Sembra assurdo ma il protocollo IP è stato creato per essere facile, insicuro ed inaffidabile caratteristiche che lo rendono perfetto per resistere ad una guerra su scala globale (almeno in teoria...). Si basa su concetti che potremmo sintetizzare così:

  • una comunicazione può essere suddivisa in diversi "spezzoni" detti pacchetti IP che contengono, oltre ai dati, l'indirizzo del mittente e l'indirizzo del destinatario
  • non esiste alcun server centrale che smista il traffico; ogni nodo della rete è un Autonomous System (=sistema autonomo) che possiede diverse tabelle di routing (=instradamento) dei pacchetti
  • un pacchetto IP viene affidato alla rete la quale deve fare del suo meglio per consegnare il pacchetto al destinatario
  • ogni pacchetto IP viene instradato in maniera autonoma; può accadere che due o più pacchetti della stessa comunicazione prendano strade diverse
  • non vi è alcuna garanzia che un pacchetto IP sia effettivamente consegnato al destinatario
  • non vi è alcuna garanzia che i pacchetti IP siano ricevuti dal destinatario nello stesso ordine in cui sono stati spediti dal mittente

Il modello ISO/OSI

Agli albori della informatica la comunicazione tra dispositivi era realizzata su protocolli proprietari dei singoli produttori. Con l'avvento di Internet si è sviluppato un protocollo di comunicazione tra computer e dispositivi di vario tipo basato sulla flessibilità e sulla trasparenza: ne è nata una architettura a livelli nella quale ogni livello utilizza i servizi offerti dal livello immediatamente sottostante senza preoccuparsi di come esso venga effettivamente realizzato.
Questa architettura a livelli è nota come Modello ISO/OSI. Vi sono sette livelli di comunicazione:

  1. fisico: è il livello più basso a cui appartengono gli standard come il Bluetooth, DSL, OTN (=fibra ottica), etc.
  2. collegamento dati: a questo livello appartengono gli standard come Ethernet, Wi-Fi, Token-Ring, etc.
  3. rete: a questo livello appartiene il protocollo IP che, come abbiamo visto più sopra, è inaffidabile
  4. trasporto: a questo livello appartiene il protocollo TCP che rende la comunicazione stabile ed affidabile usando il sottolivello IP. Per questo è conosciuto anche col nome TCP/IP (TCP over IP)
  5. sessione: l'obiettivo di questo livello è di controllae la comunicazione tra applicazioni
  6. presentazione: tramite questo livello si ottiene la strutturazione dei dati in formati omogenei
  7. applicazione: questo è il livello applicativo cioè il livello che stabilisce il protocollo di come e a che cosa servono i dati. Appartengono a questo livello i protocolli quali HTTP, FTP, SMTP e POP che sono quelli che usiamo tutti i giorni per navigare il web, scaricare files, inviare un messaggio di posta e leggere i messaggi di posta elettronica.

A noi programmatori interessa solo il livello 4 della catena cioè quello di trasporto e, quando scriveremo la applicazione, dovremo implementare un protocollo applicativo, quello a livello 7 (vedi Versione 0.8: il protocollo Mastermind).

Il protocollo TCP/IP

Questo protocollo rende la connessione remota stabile ed affidabile pur basandosi su un protocollo di livello inferiore (IP) insicuro ed inaffidabile. Per comprendere meglio la differenza tra il TCP ed IP immaginiamo la comunicazione tra due umani lontani, per esempio Tizio che sta a Firenze e Caio che sta a Venezia. Abbiamo sostanzialmente due modalità di comunicazione:

  • le cartoline postali: dividiamo il messaggio (se molto lungo) in due o più cartoline e le imbuchiamo
  • telefoniamo

La prima modalità è detta connectionless (=senza connessione) nella quale il mittente non ha alcuna garanzia che le cartoline siano effettivamente consegnate al destinatario e nemmeno in quale ordine; può anche accadere che alcune cartoline siano recapitate mentre altre no. Di più, potrebbe anche accadere che le cartoline, o alcune di esse, siano recapitate alla persona sbagliata: questo è il protocollo IP.

La seconda modalità è detta connection oriented (=orientata alla connessione) in quanto Tizio deve comporre il numero di telefono di Caio e solo dopo la risposta si presenta. Ma non solo: chiede espressamente di parlare con Caio e in caso di risposta negativa (sbagliato numero o il telefono è in possesso ad altra persona) la comunicazione non ha nemmeno inizio: questo è il protocollo TCP.

Come in una telefonata, nella connessione TCP/IP vi è un chiamante, cioè colui che compone il numero di telefono, ed un chiamato cioè colui che accetta la telefonata in entrata. Ovviamente, il chiamante deve conoscere il numero di telefono del chiamato. Nella connessione TCP/IP, il chiamato viene detto server mentre il chiamante viene detto client. Il numero di telefono è, invece, l'indirizzo IP del server: potete immaginare il server come un palazzo comunale pieno di uffici: la anagrafe, lo sportello dello stato civile, la polizia municipale etc.
Questi servizi vengono erogati dal server ad un unico indirizzo IP (il palazzo comunale possiede un solo indirizzo e civico) pertanto il client deve specificare a quale sportello è rivolta la chiamata: in una connessione TCP/IP questo viene indicato con un numero che viene detto porta. Nella richiesta di connessione, il client deve pertanto indicare due dati: l'indirizzo IP del server e la porta del servizio. Convenzionalmente, questi dati si indicano in questo modo:

indirizzoIP:porta

Esempio:

www.acme.com:80

Java Networking

In questa sezione analizzeremo gli strumenti che Java mette a disposizione per gestire una comunicazione utilizzando il protocollo TCP/IP il quale aggiunge le caratteristiche di affidabilità e sicurezza alla comunicazione. Devo chiarire un aspetto che spesso sfugge: in questo contesto, per sicurezza del protocollo TCP, si intende la integrità dei dati trasmessi ma non mi riferisco affatto alla loro segretezza!


Questa sezione è una libera traduzione dalla lingua inglese di un articolo scritto da Jakob Jenkov e pubblicato sul sito ufficiale di Oracle™ e disponibile al seguente link: Trial: Custom Networking. In realtà, non tutto l'articolo viene tradotto in italiano ma solo le parti che interessano la gestione del protocollo TCP/IP.

Cos'è un socket?

Un socket è un endpoint (=punto finale) di un collegamento di comunicazione bidirezionale tra due programmi in esecuzione sulla rete. Le classi socket sono utilizzate per rappresentare la connessione tra un programma client e un programma server. Il package java.net fornisce due classi, Socket e ServerSocket, che implementano rispettivamente il lato client della connessione e il lato server della stessa.

Normalmente, un server viene eseguito su un computer specifico e ha un socket che è associato a un numero di porta specifico. Il server attende rimanendo in ascolto sulla porta in attesa che un client inoltri una richiesta di connessione.

Il client conosce il nome (o indirizzo IP) della macchina su cui è in esecuzione il server e il numero di porta su cui il server è in ascolto. Il client deve anche identificarsi al server in modo da associare se stesso ad un numero di porta locale che utilizzerà durante questa connessione. Questo viene solitamente assegnato dal sistema.

Se non intervengono errori di comunicazione, il server accetta la connessione. Dopo l'accettazione, il server ottiene un nuovo socket associato alla stessa porta locale ed ha anche il suo endpoint remoto impostato sull'indirizzo e sulla porta del client. Il server ha bisogno di un nuovo socket in modo da poter continuare ad ascoltare sul socket originale per le richieste di connessione, occupandosi al contempo delle esigenze del client appena connesso. Il client e il server possono ora comunicare scrivendo o leggendo dai rispettivi socket.

Un socket è un endpoint di un collegamento di comunicazione bidirezionale tra due programmi in esecuzione sulla rete.
Un socket è vincolato a un numero di porta in modo che il livello TCP possa identificare l'applicazione a cui i dati sono destinati a essere inviati. Un endpoint è una combinazione di un indirizzo IP e un numero di porta. Ogni connessione TCP può essere identificata in modo univoco dai suoi due endpoint poichè non esisteranno mai, nella rete, due endpoint che abbiano lo stesso indirizzo IP e la stessa porta di altre due endpoint.

Creare un socket

Come accennato più sopra, ci sono due modalità distinte di creare un socket a seconda del fatto che il programma in esecuzione sia il lato client o il lato server della connessione.

Creare un socket lato client

Dal lato client la creazione del socket è facile conoscendo il nome, o indirizzo IP, del server ed il numero della porta di ascolto:

try {
Socket socket = new Socket("192.168.1.140", 4444 );
}
catch (IOException ex)
{
System.err.println( "IOException: " + ex.getClass().getName() );
System.err.println( "MESSAGE: " + ex.getMessage());
ex.printStackTrace();
}

Non sempre la connessione và a buon fine ed in questo caso Java solleva una eccezione di tipo IOException (o una sua derivata). Le cause più comuni per cui la connessione non può essere stabilita sono:

  • l'indirizzo IP del server non è raggiungibile o non esiste affatto
  • nessun server è in ascolto sulla porta specificata
  • il server è configurato in modo da accettare richieste solo da specifici indirizzi IP

Creare un socket lato server

Dal lato server, invece, le cose vanno diversamente: innanzitutto è necessario creare un ServerSOcket che rimane in ascolto sulla porta desiderata; faccio notare che il server-socket NON è un endpoint della connessione dal momento che fino a quando una richiesta di connessione non è stata accettata non esiste alcuna connessione:

try {
ServerSocket server = new ServerSocket(port);
Socket socket = server.accept(); // 'socket' è il endpoint
}
catch (IOException ex)
{
}

Scrittura su un socket

Sull'oggetto Socket di per se non si può "scrivere" qualcosa: è neessario ottenere dal socket un oggetto di classe OutputStream, cioè un flusso di dati in output (=uscita) sul quale si scrivono i dati usando il metodo write dello stream:

OutputStream out = socket.getOutputStream();
out.write( 65 );

Lettura da un socket

Come per la scrittura, dall'oggetto Socket di per se non si può "legger" qualcosa: è neessario ottenere dal socket un oggetto di classe InputStream, cioè un flusso di dati in input (=ingresso) dal quale si possono leggere i dati usando il metodo read dello stream:

InputStream in = socket.getInputStream();
int ch = in.read(); // legge un byte dal socket
if ( ch < 0 ) {
System.out.println( "Connection closed by the peer" );
}

Chiusura di un socket

Quando la connessione non serve più, è necessario chiudere esplicitamente il socket in modo che il sistema operativo possa liberare subito le risorse allocate sullo stack TCP/IP.

socket.close();

Un esempio pratico

Nel prossimo capitolo si presenterà una piccola applicazione che vi insegnerà come strutturare i due lati della connessione: il server ed il client.

Argomento precedente - Argomento successivo - Indice Generale