|
Java Tutorial - Parte 2 0.1
Un tutorial per esempi
|
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.
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.
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ì:
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:
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).
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:
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
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.
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.
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.
Dal lato client la creazione del socket è facile conoscendo il nome, o indirizzo IP, del server ed il numero della porta di ascolto:
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:
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:
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:
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:
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.
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