18 gennaio 2009

Socket in Java (tcp e udp)

Molti sistemi distribuiti ed applicazioni sono costruiti e basati semplicemente sul modello message-oriented del livello di trasporto.
In fase di progettazione del livello di trasporto è stata prestata molta attenzione alle standardizzazioni delle interfaccie per permettere ai programmatori di utilizzare l'intera suite di protocolli mediante un semplice set di primitive.
Un classico esempio sono le interfaccie socket introdotte in Berkley Unix.

Concettualmente un socket è un endpoint di comunicazione tramite cui un applicazione può scrivere dati che vengono inviati tramite la rete e tramite cui dati in ingresso possono essere letti.
Consideriamo le primitive :
1. SOCKET : crea un nuovo endpoint di comunicazione
2. BIND : attacca un indirizzo locale ad un socket
3. LISTEN : annuncia l'approvazione di accettare la connessione
4. ACCEPT : blocca il chiamante finchè non arriva una richiesta di connessione
5. CONNECT : tenta attivamente di stabilire una connessione
6. SEND : invia dati mediante la connessione
7. RECEIVE : riceve dati dalla connessione
8. CLOSE : rilascia la connessione

I server generalmente eseguono le prime 4 nell'ordine dato:
1. Il sistema operativo locale riserva la risorsa per garantire l'invio e la ricezione di messaggi per il protocollo specificato
2. Dice al S.O. che il server vuole ricevere dati solo su quell'indirizzo e su quella porta
3. E' chiamata solo in caso di comunicazione orientata alla connessione, serve a dire al S.O di allocare abbastanza buffer per un numero massimo di connessioni.
4. Quando arriva una richiesta il S.O. crea un nuovo socket con le stesse proprietà di quello originale e lo restituisce al chiamante.

Guardiamo il Client:
1. Va creato il SOCKET utilizzando la primitiva
5. La primitiva CONNECTION richiede che il chiamante specifichi l'indirizzo del livello di trasporto al quale la richiesta di connessione deve essere inviata. Il client si blocca finchè la richiesta è completata. Quindi le 2 parti possono iniziare a scambiare dati mediante le primitive READ e RECEIVE.

8. La chiusura è simmetrica e può avvenire sia lato client che lato server.

Vediamo un esempio di implementazione in cui avvengono i seguenti passi :
- Il client legge una linea dallo standard input (inFromUser stream) , e lo invia al server mediante socket (outToServer stream)
- Il server legge la linea dal socket
- Il server converte la line in uppercase e la restituisce al client
- Il client legge e stampa la linea modificata mediante il socket (inFromServer stream)

IMPLEMENTAZIONE TCP
Nella versione TCP dell'implementazione dei socket distinguiamo le seguenti caratteristiche peculiari:
Poichè il TCP fornisce un affidabile e ordinato trasferimento di bytes tra client e server (orientato alla connessione),
è necessario che il processo server sia il primo ad essere eseguito, in maniera tale da creare il socket (door) che accetta la connessione da parte dei client.
Quando i client creano il socket, il client TCP stabilisce una connessione con il server TCP il quale crea un nuovo socket per la comunicazione con il client. Questo permette di avere una comunicazione simultanea con più client.
Ecco l'implementazione del server :
/***
* Classe per la gestione del server
*
* @author Alessandro Franzi
*
*/
public class TCPServer {
public static final int PORT = 6768;

public static void main(String[] args){
String fraseClient;
String fraseMaiuscola;
try{
// creo il socket di benvenuto
ServerSocket welcomeSocket = new ServerSocket(PORT);

// ciclo infinitamente (server)
while (true){

// aspetto il socket per il contatto con il client
Socket connectionSocket = welcomeSocket.accept();

System.out.println("Accettata connessione socket");
System.out.println("Il client è connesso dall'ip : "+connectionSocket.getRemoteSocketAddress());

//creo uno stream di input attaccato al socket
BufferedReader inFromClient = new BufferedReader(new InputStreamReader(connectionSocket.getInputStream()));

DataOutputStream outToClient = new DataOutputStream(connectionSocket.getOutputStream());

// leggo la frase del client
fraseClient = inFromClient.readLine();

// trasformo la frase in maiuscolo
fraseMaiuscola = fraseClient.toUpperCase();

System.out.println("frase da inviare : "+fraseMaiuscola);

outToClient.writeBytes(fraseMaiuscola+'\n');

}
}catch (IOException e) {
System.out.println("Errore :"+e.getMessage());
}
}
}


Questa invece è l'implementazione del client :
/***
* Classe client TCP
*
* @author Alessandro Franzi
*
*/
public class TCPClient {

private static final String HOSTNAME = "1.46.193.82";
public static final int PORT = 6768;

public static void main(String[] args){
String host ="";
int port = PORT;
String frase ="";
String fraseModificata;

if (args.length==0){
System.out.println("Parametri di default.");
System.out.println("Host : "+HOSTNAME+" Porta : "+PORT);
host = HOSTNAME;
}else{
if (args.length==1){
host = args[0];
}
if (args.length==2){
host = args[0];
port = new Integer(args[1]).intValue();
}
}
// dichiaro un buffer Reader
BufferedReader inFromUser = new BufferedReader(new InputStreamReader(System.in));

try{
// connetto il socket locale

Socket clientSocket = new Socket(host,port);

System.out.println("Connessione accettata dal server ");
// crea un outputStream connesso allo stram di output del socket
DataOutputStream outToServer = new DataOutputStream(clientSocket.getOutputStream());

// creo un buffer di lettura dal server
BufferedReader inFromServer = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));


// prelevo la frase dallo standard input
frase = inFromUser.readLine();

// passo la frase al server
outToServer.writeBytes(frase + '\n');

// recupero la frase modificata dal server
fraseModificata = inFromServer.readLine();

System.out.println(fraseModificata);

// chiudo il socket
clientSocket.close();

}catch(UnknownHostException e){
System.out.println("Host sconosciuto : "+e.getMessage());
}catch(IOException e){
System.out.println("Problema con il server : "+e.getMessage());
}

}
}


IMPLEMENTAZIONE UDP
Il protocollo UDP non fornisce una connessione tra client e server, il server infatti esplicitamente si attacca ad un indirizzo IP ed una porta di un client del destinatario. Il server deve estrarre l'indirizzo IP e la porta del mittende ricavandoli dal datagram ricevuto.
Vediamo l'implementazione del server UDP:
public class UDPServer {
public static final int PORT = 6769;

public static void main(String[] args) {

try {

// creo il socket
DatagramSocket serverSocket = new DatagramSocket(PORT);

byte[] sendData = new byte[1024];
byte[] receiveData = new byte[1024];

while (true){

// istanzio il datagramma in input
DatagramPacket receivePacket = new DatagramPacket(receiveData,receiveData.length);

// ricevo il datagramma dal socket
serverSocket.receive(receivePacket);

// recupero la frase
String frase = new String(receivePacket.getData());

// indirizzo ip
InetAddress iPAddress = receivePacket.getAddress();

// porta
int port = receivePacket.getPort();

// modifico la porta
String fraseModificata = frase.toUpperCase();

// trasformo in byte
sendData = fraseModificata.getBytes();

// creo un datagramma per inviare al client
DatagramPacket sendPacket = new DatagramPacket(sendData,sendData.length,iPAddress,port);

//scrivo il datagramma sul socket
serverSocket.send(sendPacket);
}
}catch(SocketException e){
System.out.println("Problemi nell'apertura del socket "+e.getMessage());
}catch(IOException e){
System.out.println("Problemi di I/O : "+e.getMessage());
}
}

}


Ecco invece l'implementazione del corrispettivo client
/***
* Classe per la gestione del client UDP
*
* @author Alessandro Franzi
*
*/
public class UDPClient {
public static final String HOSTNAME = "localhost";
public static final int PORT = 6769;

public static void main(String[] args) {
String host ="";
int port = PORT;

if (args.length==0){
System.out.println("Parametri di default.");
System.out.println("Host : "+HOSTNAME);
host = HOSTNAME;
}else{
if (args.length==1){
host = args[0];
}
if (args.length==2){
host = args[0];
port = new Integer(args[1]).intValue();
}
}

// buffer di input del client
BufferedReader inFromUser = new BufferedReader(new InputStreamReader(System.in));

try{

// creo il socket client
DatagramSocket clientSocket = new DatagramSocket();

// creo l'indirizzo ip
InetAddress IPAddress = InetAddress.getByName(host);

byte[] sendData = new byte[1024];
byte[] receiveData = new byte[1024];

String frase = inFromUser.readLine();

sendData = frase.getBytes();

// creo un datagramma con i dati da inviare, lunghezza, ip e porta
DatagramPacket sendPacket = new DatagramPacket(sendData,sendData.length,IPAddress,port);

// invio il datagramma al server
clientSocket.send(sendPacket);

// credo un pacchetto
DatagramPacket receivePacket = new DatagramPacket(receiveData,receiveData.length);

clientSocket.receive(receivePacket);

// frase ricevuta dal server
String fraseModificata = new String(receivePacket.getData());

System.out.println("Frase ricevuta : "+fraseModificata);

// chiudo il socket
clientSocket.close();
}catch(SocketException e){
System.out.println("Problemi con il socket : "+e.getMessage());
}catch(UnknownHostException e){
System.out.println("Host sconosciuto : "+e.getMessage());
}catch (IOException e){
System.out.println("I/O exception : "+e.getMessage());
}
}
}


Spero che questi esempi siano sufficienti a chiarire il funzionamento dei socket in Java. Per qualsiasi perplessità o chiarimento non esitate a contattarmi. Alla prossima.

1 commento:

Giovanni Caputo ha detto...

la parte più bella incomincia quando iniziano connessioni multiple al server!!!