RETI DI CALCOLATORI A.A. 1999/2000

 

 

Coordinamento di FTP server

implementati in Java

 

 

Realizzato da:

Simone Bassi

Stefano Benedetti

 

 

  

When a packet hits a pocket on a socket on a port,

And the bus is interrupted as a very last resort,

And the address of the memory makes your floppy disk abort,

Then the socket packet pocket has an error to report.

Anonimo (www.ezzell.org)

Sommario

Introduzione *

Studio di Fattibilità *

File Transfer Protocol *

Porte e socket TCP *

Controllo FTP e connessione dati *

Analisi e Specifica dei Requisiti *

Coordinamento *

Progettazione *

Ftp server *

Coordinamento *

Oggetto ServerInfo *

Oggetto ServerCtrl *

Ingresso di un nuovo ftp server. *

Sincronizzazione tra gli ftp server *

Logical view *

Codice *

Esempio di Utilizzo *

Conclusione *

Bibliografia *

Programmazione in Java: *

Protocollo FTP: *

Software utilizzato: *

Introduzione

Scopo del seguente progetto è approfondire le conoscenze sulla programmazione ed implementazione di applicativi Java che richiedano uso di socket; argomento prescelto è la realizzazione di un coordinamento tra server che gestiscano un servizio per il trasferimento di file da remoto (FTP – File Transfer Protocol). In questo modo è possibile suddividere il lavoro in due problemi ben distinti e affrontabili uno alla volta:

La presente relazione intende essere un "diario di viaggio" che descriva, nel modo più chiaro possibile, il percorso compiuto da noi per la realizzazione del progetto.

La suddivisione dei capitoli segue direttamente il processo di sviluppo del software secondo il modello a cascata; abbiamo cercato di evitare, per quanto possibile, retroazioni nelle fasi di sviluppo, per poter essere coerenti con il diagramma delle attività redatto nella fase di start-up progettuale. L’unica retroazione forte che abbiamo ravvisato è stata nel raffinamento delle specifiche dovuto a difficoltà nella progettazione, causate dal fatto che fino ad oggi non avevamo mai utilizzato Java come ambiente di sviluppo.

Studio di Fattibilità

Il vincolo più stringete è senz’altro il tempo, avendo entrambi deciso di sostenere l’esame per fine gennaio, in modo da terminare la tesi di laurea per marzo. La realizzabilità del progetto, inoltre, è strettamente legata alle seguenti condizioni:

Per quanto riguarda il primo skill richiesto abbiamo messo in comune il know-how acquisito nei precedenti progetti (realizzati per lo più in C++ o Visual Basic) e, partendo da piccoli esercizi con le socket in Java, abbiamo acquisito una conoscenza del linguaggio sufficiente per l’affronto del problema (conoscenza che intendiamo comunque raffinare in seguito, visto l’interesse nato durante la fase di sviluppo di questo software).

Il secondo punto, invece, è stato risolto con una ricerca in Internet delle specifiche del protocollo (l’università di Napoli mette a disposizione un servizio FTP molto ricco, in cui è possibile trovare tutte le RFC: http://ftp.unina.it), corredata dalla lettura di alcuni manuali (tra cui la "Guida a Internet" del Windows NT Server Resource Kit, Microsoft Press).

Per completare lo studio di fattibilità, riteniamo utile entrare in merito al problema con un breve incipit che descriva alcune caratteristiche generali del protocollo FTP, rimandando in appendice per la visione dettagliata della RFC959, che descrive nel dettaglio l’ultima release del protocollo.

 

File Transfer Protocol

È stato calcolato che attualmente, sparpagliati nelle memorie degli host computer connessi a Internet, ci siano diversi milioni di file. Si tratta di uno sconfinato serbatoio di programmi, immagini digitali, suoni, ecc. molti dei quali di "pubblico dominio". Il sistema che ci consente di trasferire questi file sul nostro computer, si chiama File Transfer Protocol (FTP).

Il protocollo FTP è usato per trasferire file tra due computer su una rete che usa i protocolli TCP/IP (Transmission Control Protocol / Internet Protocol). FTP rappresenta uno dei primi protocolli usati nelle reti basate su TCP/IP e in Internet. Sebbene il World Wide Web abbia rimpiazzato la maggior parte delle funzioni di FTP, quest’ultimo continua ad essere un ottimo sistema per copiare file da un computer client ad un server su Internet.

Per usare FTP nel trasferimento file tra due computer, entrambi devono supportare i rispettivi ruoli FTP. In altre parole occorre che uno sia il client FTP, e l’altro il server. Il client FTP può inviare comandi al server, come comandi per scaricare file, inviare file, creare directory e cambiare directory sul server.

Di seguito cercheremo di descrivere il funzionamento generale, con riferimenti particolari, laddove necessario, all’ambiente di Microsoft Windows NT. FTP utilizza TCP come proprio protocollo di trasporto per tutte le comunicazioni e gli scambi di dati tra client e server. TCP è un protocollo orientato alla connessione. Questo significa che la sessione di comunicazione viene stabilita tra il client e il server prima che i dati vengano trasmessi. La connessione rimane attiva durante l’intera sessione FTP. Le sessioni orientate alla connessione sono conosciute per la loro affidabilità e per la capacità di recuperare gli errori. Questo significa che i traferimenti di file attraverso FTP sono molto affidabili.

 

Caratteristica

Descrizione

Controllo di flusso

Sia i computer client che server partecipano alla trasmissione dei pacchetti, il che elimina virtualmente eventuali problemi con overflow o perdite di pacchetti.

Riconoscimento

Il computer che invia pacchetti di dati aspetta un messaggio di riconoscimento (ACK) dal computer destinazione. Questo riconoscimento verifica che il pacchetto sia stato ricevuto con successo dal destinatario.

Ritrasmissione

Se il computer inviante non riceve un ACK entro un determinato periodo di tempo, ne conclude che il pacchetto è andato perduto, oppure si è corrotto, quindi lo ritrasmette.

Sequenzializzazione

Tutti i pacchetti sono numerati e inviati in ordine in modo che il computer ricevente possa riorganizzare correttamente i dati.

Somma di controllo

Tutti i pacchetti contengono una somma di controllo (checksum) per assicurare l’integrità dei dati. Se i dati vengono rovinati in qualche punto della trasmissione, il checksum indicherà che i dati non sono gli stessi di quelli inviati

Figura 1 - Caratteristiche della connessione TCP

Porte e socket TCP

Vengono comunemente usati tre numeri identificativi per riferirsi ai socket TCP.

Ogni applicazione o processo che usa TCP come trasporto viene assegnato a un unico numero identificativo chiamato porta TCP. La porta TCP specifica il percorso della comunicazione tra le applicazioni server e client. Queste porte sono numerate a partire da zero. I numeri di porta per le applicazioni client sono assegnati dinamicamente dal sistema operativo quando c’è una richiesta di servizio. I numeri di porta per le applicazioni server sono preassegnati dalla Internet Assigned Numbers Authority (IANA) e non cambiano. IANA è il gruppo che assegna i numeri da 0 a 1023. Questo intervallo di numeri è riservato ai servizi. Una applicazione o un processo client che usano TCP come trasporto vengono assegnati a un numero superiore a 1023 dal sistema operativo.

I numeri di porta preassegnati per i servizi di server FTP sono 20 (dati) e 21 (controllo). Questi assegnamenti di porta sono detti "Well Known Port Number" e sono documentati in RFC1700 (vedi sul sito Internet http://ds.internic.net/std/std2.txt).

Controllo FTP e connessione dati

FTP usa due connessioni TCP per comunicare tra client e server. Queste connessioni sono chiamate connessione di controllo e connessione di trasferimento dati. Le connessioni possono essere pronte in uno dei due stati: apertura passiva (aspetta una trasmissione) o apertura attiva (inizia la trasmissione).

La comunicazione di controllo inizia la comunicazione tra client e server FTP, viene mantenuta per tutta la durata della sessione FTP, usa la porta 21 sul server e apre una porta superiore a 1023 sul client. La connessione di controllo viene gestita da un insieme di programmi chiamati interprete di protocollo server (server-PI) e interprete di protocollo utente (user-PI).

Il server-PI mantiene uno stato di apertura passiva sulla porta 21, aspettando l’arrivo di una richiesta di connessione FTP da un client. Quando questa richiesta arriva, il server-PI stabilisce una connessione di comunicazione di controllo, riceve i comandi FTP standard dallo user-PI, invia le risposte e governa il processo di trasferimento dati del server (server-DTP). Lo user-PI inizia la connessione di controllo (apertura attiva) dalla sua porta TCP verso il server-PI, inizia i comandi FTP e governa il processo di trasferimento dati dell’utente (user-DTP).

La connessione di trasferimento dati esiste solamente quando ci sono dati da trasferire tra server e client. Questa connessione si chiude ogni volta che il trasferimento dati è completato, mentre la connessione di controllo rimane aperta. A causa di questo, deve essere aperta una nuova porta dati sul client ogni volta che comincia un nuovo trasferimento di dati. La porta dati del server rimane sempre 20.

Terminata, questa breve introduzione, nei prossimi capitoli entreremo in merito al progetto, iniziando dalla determinazione delle specifiche che esso deve rispettare. Completiamo questa prima fase dello sviluppo a cascata, dicendo che lo studio di fattibilità ha indicato che il progetto è realizzabile in due mesi uomo.

Analisi e Specifica dei Requisiti

Per definire chiaramente i requisiti cui l’applicativo deve soddisfare, separiamo il problema in due argomenti: la realizzazione di un servizio server per il protocollo FTP e l’implementazione di un controllore del bilanciamento del carico di lavoro.

I requisiti richiesti al servizio di FTP server sono quelli descritti nella RFC959, ultima release del documento ufficiale che descrive questo standard. Il nostro intento è quello di realizzare almeno tutte le funzioni richieste come minima implementazione del protocollo; per esempio, abbiamo provato ad implementare anche la navigazione nelle directory che, per semplicità, è stata però disabilitata nel seguente progetto (viene dato un messaggio di avvertimento).

1)

In order to make FTP workable without needless error messages, the following minimum implementation is required for all servers:

TYPE - ASCII Non-print

MODE - Stream

STRUCTURE - File, Record

COMMANDS - USER, QUIT, PORT,

TYPE, MODE, STRU,

for the default values

RETR, STOR,

NOOP.

The default values for transfer parameters are:

TYPE - ASCII Non-print

MODE - Stream

STRU - File

All hosts must accept the above as the standard defaults.

Note that all FTP implementation must support data transfer using the default port, and that only the USER-PI may initiate the use of non-default ports.

2)

Il server ascolta sulla porta di controllo 21 e trasferisce i dati sulla porta dati 20 (salvo diversa segnalazione); le porte utilizzate dai client, invece, sono oltre la 1023, come stabilito dallo IANA;

3)

L’autenticazione dell’utente avviene tramite controllo su un file locale contenente la lista degli aventi diritto al servizio; non è quindi consentito, salvo diversa impostazione nell’apposito file l’utilizzo dell’anonymous FTP;

4)

Tutte le operazioni di interesse sono salvate su un file di log, per consentire all’amministratore di controllare le richieste degli utenti.

Coordinamento

Le specifiche del coordinamento, invece, sono le seguenti:

1)

Il server quando riceve una richiesta di connessione assegna al client un intero progressivo per tenere traccia nel LOG degli accessi. Quando l’utente accede ad un server, deve sapere indirizzo IP (nome logico) e stato di ogni server disponibile in quel momento, con i rispettivi utenti presenti;

2)

Quando un nuovo server si unisce al gruppo deve presentarsi, segnalando le proprie generalità e chiedendo lo stato dei componenti del gruppo;

3)

Quando un server cambia stato (nuovo utente o disconnessione di un utente) gli altri server aggiornano i propri dati.

L’ultima specifica imposta è quella di realizzare il progetto in Java. La versione di JDK da noi utilizzata è la 1.1.8 disponibile all’URL http://java.sun.com.

Progettazione

 

Ftp server

Vediamo ora come si implementa in Java il protocollo FTP. La prima osservazione da fare è che si è pensato di ricorrere all’uso dei thread per la gestione delle nuove richieste da parte dei client. Riportiamo di seguito quello che sarà l’inizio del programma FtpServer.java, dando una breve descrizione delle variabili che verranno utilizzate:

public class FtpServer extends Thread {

private Socket ClientSocket;

private static String WorkDir="C:\\FtpPub";

private File AclFile;

private int TID;

private static ServerCtrl m_ServerCtrl;

private static FileOutputStream LogFile;

private static PrintWriter WriterLogFile;

...

La prima variabile definita è la Socket che il client chiede di aprire ad ogni richiesta di connessione; WorkDir è la directory di lavoro che viene presentata all’utente una volta connesso; AclFile, invece, è un file di testo dove sono riportati username e password degli utenti abilitati al servizio FTP, quindi se si vuole consentire l’accesso anonimo si deve inserire tale utente in questo file; l’intero TID è un identificatore del Thread, che permette di tenere traccia delle connessioni che il server ha stabilito in totale, mentre il numero di connessioni attive in un dato momento (rappresentativo del carico di lavoro) sarà salvato nella variabile Nthread (definita nell’oggetto ServerCtrl); m_ServerCtrl è la variabile associata al server per controllare il coordinamento con gli altri computer. Infine, viene definito un file per il LOG delle operazioni eseguite sul server.

Il main del programma è molto semplice: è sufficiente aprire il controllore del server e aprire una ServerSocket sulla porta di controllo 21, dopodiché si pone il server in ascolto di richieste di connessione da parte dei client. Per implementare ciò è sufficiente un ciclo infinito:

for(;;)

{

Socket ClientSocket = s.accept();

new FtpServer(ClientSocket,i).start();

i++;

}

Quando un client chiede di connettersi al server, il programma crea il nuovo thread e tramite start() invoca il metodo run(). In questo metodo sono implementate tutte le funzioni del File Transfer Protocol.

Questo protocollo di comunicazione tra client e server attraverso TCP si basa su codici di ritorno che descrivono l’esito di ogni richiesta (vedi RFC959).

Presentiamo adesso i principali diagrammi di stato per una semplice implementazione FTP. È usata soltanto la prima cifra dei codici di risposta. C’è un diagramma di stato per ogni gruppo di comandi FTP o sequenza di comandi.

Per ogni comando o sequenza di comandi ci sono tre possibili uscite: successo (S), failure (F) ed errore (E). Nei seguenti diagrammi di stato usiamo il simbolo B per "begin" e la lettera W per "wait for reply".

Il primo diagramma rappresenta la maggior parte dei comandi FTP:

Questo diagramma modella i seguenti comandi:

DELE, CWD, QUIT, PORT, PWD, TYPE

Un altro grande gruppo di comandi è rappresentato da un diagramma simile:

Questo diagramma modella i seguenti comandi:

LIST, RETR, STOR

Si noti che questo modello potrebbe essere usato per rappresentare il primo gruppo di comandi in quanto l’unica differenza è che nel primo gruppo la serie di reply 100 non è prevista e quindi è trattata come errore mentre questo secondo gruppo richiede un reply della serie 100.

Si ricordi che è permesso al massimo una sola risposta della serie 100 per un comando.

Il diagramma più complesso è quello della sequenza di login:

Si noti che dopo l’autenticazione dell’utente si può entrare nello stato di accounting che non è implementato in questa versione di ftp server (non rientra nell’insieme minimale dei comandi richiesti dall’RFC).

Coordinamento

Per implementare le specifiche inerenti il coordinamento abbiamo pensato di utilizzare un controllore (oggetto ServerCtrl) che monitora lo stato del server, rappresentato nell’oggetto ServerInfo. Di seguito, forniamo una breve spiegazione di questi oggetti.

Oggetto ServerInfo

È lo stato associato ad ogni ftp server. Si compone di tre campi:

ServerID

Identificatore univoco associato ad ogni server

ServerIP

Indirizzo IP del server

Nthread

Numero di connessioni ftp attualmente attive

Oggetto ServerCtrl

L’oggetto ServerCtrl si occupa del coordinamento tra gli ftp server. Per coordinamento intendiamo la consistenza dello stato tra le varie macchine. Lo stato associato ad ogni ftp server è una tabella costituita da tante entry quanti sono i server partecipanti. Ogni entry è un oggetto di tipo ServerInfo. La tabella ha quindi questa struttura:

0

ServerInfo

1

ServerInfo

….

….

….

….

Per svolgere questa funzione è stato definito un protocollo di coordinamento.

Le funzioni implementate sono:

Vediamo come è strutturato il protocollo.

Ingresso di un nuovo ftp server.

Il server S2 vuole entrare in un insieme di server già presenti. All’atto dell’esecuzione del server è necessario specificare il master al quale ci si vuole collegare per coordinarsi. S2 invia a questo punto il comando ADD. La sequenza è la seguente:

 

Sincronizzazione tra gli ftp server

Ogni volta che uno degli ftp server cambia il proprio stato effettua un broadcast a tutti gli altri partecipanti inviando loro la tabella modificata.

La sincronizzazione avviene inviando il comando SYN in sequenza a tutti i gestori del coordinamento risultando così event driven.

Ogni Ftp Server crea ed utilizza un oggetto ServerCtrl. All’atto della creazione il costruttore del ServerCtrl ha due possibilità:

  1. il server è il primo installato e quindi viene creata la tabella ServerList contenente una sola entry (quella del server corrente)
  2. il server viene aggiunto ad un insieme già esistente, allora viene avviato il protocollo di aggiunta di un nuovo server.

Le informazioni di stato sono utilizzate per fornire un’indicazione all’utente che si collega ad uno dei server. Quando viene stabilita la connessione gli viene infatti mostrata l’intera ServerList ed in particolare il numero di utenti connessi ad ogni Ftp Server. In questo modo l’utente può decidere di disconnettersi per accedere ad un server in quel momento meno carico.

Ogni volta che viene concesso l’accesso ad un server viene incrementato il contatore indicante il numero di client connessi e viene forzata la sincronizzazione tra i ServerCtrl, viceversa quando un utente si disconnette.

Logical view

Riportiamo la logical view finale creata con il Rational Rose: essa precede la implementazione vera e propria in Java, ma mette già in evidenza variabili e metodi che si intende utilizzare:

Codice

Riportiamo ora il codice dei file .java realizzati, cercando di descrivere le scelte effettuate :

************************************************************************************************************** FtpServer.java *************************************

********************************************************************************

import java.io.*;

import java.net.*;

import java.util.Date;

public class FtpServer extends Thread {

private Socket ClientSocket;

private static String WorkDir="C:\\FtpPub";

private File AclFile;

private int TID;

private static ServerCtrl m_ServerCtrl;

private static FileOutputStream LogFile;

private static PrintWriter WriterLogFile;

 

//**********************************************

//******************** MAIN ********************

//**********************************************

public static void main(String[] args)

{

final int FTP_CONTROL_PORT=21;

int i = 1;

File f=new File(WorkDir);

if(args.length == 0)

{ //Controllo del numero di argomenti

System.out.println("Usage: FtpServer Ring_IP");

System.exit(1);

}

if (!f.isDirectory())

{ //Controllo dell'esistenza della directory di lavoro del FTP server

System.out.println(WorkDir+" directory not found");

System.exit(2);

}

try {

//Avvio del controllore del server

m_ServerCtrl=new ServerCtrl(InetAddress.getByName(args[0]));

m_ServerCtrl.start();

//Il server apre una socket sulla porta di controllo 21

ServerSocket s = new ServerSocket(FTP_CONTROL_PORT);

//Apertura del file di LOG dove monitorare tutte le richieste

LogFile= new FileOutputStream("FtpLog.txt",true);

WriterLogFile = new PrintWriter(LogFile,true);

System.out.println("FTP server waiting connection on port 21");

for(;;)

{

Socket ClientSocket = s.accept();

new FtpServer(ClientSocket,i).start();

i++;

}

}

catch(Exception e)

{

System.out.println(e);

}

}

// END MAIN

 

//**********************************************

//***************** FTPSERVER ******************

//**********************************************

public FtpServer(Socket income, int c)

{

ClientSocket = income;

//Quando un client chiede una connessione il controllore incrementa NThread

m_ServerCtrl.IncThread();

TID=c;

}

// END FTPSERVER

 

//**********************************************

//******************** RUN *********************

//**********************************************

public void run()

{

int lng,lng1,lng2,i,ip1,ip2,ip = 1,h1;

String a1,a2,di,str1,UserName="",host,dir;

InetAddress ClientAddr;

InetAddress ServerAddr;

final int FTP_DATA_PORT=20;

System.out.println("New connection in progress...");

dir = WorkDir;

try

{

ClientAddr = ClientSocket.getInetAddress();

System.out.println(TID+" "+UserName+" ClientAddr: "+ClientAddr);

ServerAddr = ClientAddr.getLocalHost();

System.out.println(TID+" "+UserName+" ServerAddr: "+ServerAddr);

host = ClientAddr.toString();

System.out.println(TID+" "+UserName+" host: "+host);

h1 = host.indexOf("/");

host = host.substring(h1 + 1);

System.out.println(TID+" "+UserName

+" Connected to "+host+" port: "+ClientSocket.getPort());

BufferedReader in

= new BufferedReader(new InputStreamReader(ClientSocket.getInputStream()));

PrintWriter out

= new PrintWriter(ClientSocket.getOutputStream(),true);

ServerInfo ServerList[];

ServerList=new ServerInfo[10];

ServerList=m_ServerCtrl.GetStatus();

String List;

List="220 Ftp server[JAVA FTP server]ready.\n";

for (i=0;i<m_ServerCtrl.NumServer;i++)

{

List=List+ServerList[i].ServerIP+" "+ServerList[i].NThread+"\n";

}

out.println(List);

boolean done = false;

while(!done)

{

a1 = "";

a2 = "";

String str = in.readLine();

//*********** RETR **********************

if(str.startsWith("RETR"))

{

out.println("150 Binary data connection");

str = str.substring(4);

str = str.trim();

System.out.println(TID+" "+UserName

+" getting "+str+" from "+dir);

WriterLogFile.println(TimeStamp()+TID+" "+UserName

+" getting "+str+" from "+dir);

RandomAccessFile outFile = new RandomAccessFile(dir+"/"+str,"r");

//Viene aperta una socket per il trasferimento dati sulla porta 20

Socket t = new Socket(host,ip,ServerAddr,FTP_DATA_PORT);

OutputStream out2

= t.getOutputStream();

byte bb[] = new byte[1024];

int amount;

try

{

while((amount = outFile.read(bb)) != -1)

{

out2.write(bb, 0, amount);

}

out2.close();

out.println("226 transfer complete");

outFile.close();

//Terminato il trasferimento la socket viene chiusa

t.close();

}

catch(IOException e)

{

System.out.println("Exception: FtpServer.RETR");

System.out.println(e);

}

}

//************ STORE ********************

if(str.startsWith("STOR"))

{

out.println("150 Binary data connection");

str = str.substring(4);

str = str.trim();

System.out.println(TID+" "+UserName

+" storing "+str+" in "+dir);

RandomAccessFile inFile

= new RandomAccessFile(dir+"/"+str,"rw");

Socket t = new Socket(host,ip,ServerAddr,FTP_DATA_PORT);

System.out.println(host+" Opening data connection on port "+ip);

System.out.println("data connection localport: "+t.getLocalPort());

InputStream in2

= t.getInputStream();

byte bb[] = new byte[1024];

int amount;

try {

while((amount = in2.read(bb)) != -1) {

inFile.write(bb, 0, amount);

}

in2.close();

out.println("226 transfer complete");

inFile.close();

t.close();

}

catch(IOException e)

{

System.out.println("Exception: FtpServer.STORE");

System.out.println(e);

}

}

//************ TYPE *********************

if(str.startsWith("TYPE"))

{

out.println("200 type set");

}

//************ DELETE *******************

if(str.startsWith("DELE"))

{

str = str.substring(4);

str = str.trim();

File f = new File(dir,str);

if (f.exists())

{

boolean del = f.delete();

System.out.println(TID+" "+UserName

+" deleting "+str+" from "+dir);

out.println("250 delete command successful");

}

else

out.println("550 File not found");

}

//************ CDUP *********************

if(str.startsWith("CDUP")) {

int n = dir.lastIndexOf("/");

dir = dir.substring(0,n);

out.println("250 CWD command succesful");

}

//************ CWD **********************

if(str.startsWith("CWD"))

{

out.println("250 Directory browsing not allowed");

}

//************ QUIT *********************

if(str.startsWith("QUIT"))

{

out.println("GOOD BYE");

System.out.println(TID+" "+UserName

+" Disconnected from "+host);

WriterLogFile.println(TimeStamp()+TID+" "+UserName

+" Disconnected from "+host);

done = true;

}

//************ USER *********************

if(str.startsWith("USER"))

{

UserName = str.substring(4);

UserName = UserName.trim();

out.println("331 Password");

}

//************ PASS *********************

if(str.startsWith("PASS"))

{

String AclEntry,UserPass;

BufferedReader AclFile;

boolean FoundPwd=false;

UserPass = str.substring(4);

UserPass = UserPass.trim();

try

{

AclFile = new BufferedReader(new FileReader("ACL.txt"));

AclEntry=AclFile.readLine();

while ((!FoundPwd)&&(AclEntry!=null))

{

if (AclEntry.startsWith(UserName))

if (AclEntry.endsWith(UserPass))

{

out.println("230 User "+UserName+" logged in.");

System.out.println(TID+" Nuovo utente: "+UserName);

WriterLogFile.println(TimeStamp()+TID+" "

+UserName+" Connected to "+host

+" port:"+ClientSocket.getPort());

FoundPwd=true;

}

AclEntry=AclFile.readLine();

}

if (!FoundPwd)

{

out.println("530 Not logged in");

System.out.println(TID+" Connection refused for user: "

+UserName+" Password: "+UserPass);

WriterLogFile.println(TimeStamp()+TID

+" Connection refused for user: "

+UserName+" Password: "+UserPass);

done=true;

}

AclFile.close();

}

catch (IOException e)

{

System.out.println(e);

}

}

//************** PWD ********************

if(str.startsWith("PWD"))

{

out.println("257 \""+dir+"\" is current directory");

}

//************** SYS ********************

if(str.startsWith("SYS")) out.println("500 SYS not understood");

//************** PORT *******************

if(str.startsWith("PORT"))

{

out.println("200 PORT command successful");

lng = str.length() - 1;

lng2 = str.lastIndexOf(",");

lng1 = str.lastIndexOf(",",lng2-1);

for(i=lng1+1;i<lng2;i++)

{

a1 = a1 + str.charAt(i);

}

for(i=lng2+1;i<=lng;i++)

{

a2 = a2 + str.charAt(i);

}

ip1 = Integer.parseInt(a1);

ip2 =Integer.parseInt(a2);

ip = ip1 * 16 *16 + ip2;

}

//************** LIST *******************

if(str.startsWith("LIST"))

{

try

{

out.println("150 ASCII data");

out.println("Contents of "+dir);

Socket t = new Socket(host,ip);

PrintWriter out2

= new PrintWriter(t.getOutputStream(),true);

File f = new File(dir);

System.out.println(TID+" "+UserName

+" viewing dir: "+dir);

WriterLogFile.println(TimeStamp()+TID+" "+UserName

+" viewing dir: "+dir);

String[] a = new String[50];

String d,e;

int i1,j1;

a = f.list();

j1 = a.length;

d = f.getName();

for(i1=0;i1<j1;i1++) {

if (a[i1].indexOf(".") == -1) {

di = "d ";

}

else {

di = "- ";

}

out2.println(di+a[i1]);

}

t.close();

out.println("226 transfer complete");

}

catch(IOException e)

{

System.out.println("Exception: FtpServer.LIST");

System.out.println(e);

}

}

//*************** END FTP COMMAND *******

}

// END WHILE

}

// END TRY

catch (Exception e)

{

System.out.println("Exception: FtpServer.Run() TRY");

System.out.println(e);

}

finally

{

m_ServerCtrl.DecThread();

try

{

ClientSocket.close();

}

catch (IOException e)

{

System.out.println("Exception: FtpServer.Run() FINALLY");

System.out.println(e);

}

}

// END FINALLY

}

// END RUN

String TimeStamp()

{//Restituisce l'ora corrente

Date CurDate = new Date();

String s = CurDate.toString();

s=s.substring(11,20);

return(s);

}

}

// END CLASS FTPSERVER

 

****************************************************************************************************************** ServerInfo.java ********************************

********************************************************************************

// Source file: FtpSrv/ServerInfo.java

import java.net.*;

public class ServerInfo {

public int ServerID;

public InetAddress ServerIP;

public int NThread;

ServerInfo() {

}

}

****************************************************************************************************************** ServerInfo.java ********************************

********************************************************************************

// Source file: FtpSrv/ServerCtrl.java

import java.net.*;

import java.io.*;

public class ServerCtrl extends Thread {

private static int MAX_NUM_SERVER=10;

private static int CONTROL_PORT=3333;

public ServerInfo ServerList[];

int NumServer=0;

Socket ControlSocket;

private ServerSocket s;

private Socket ClientSocket;

/**

Identificatore del server

*/

private int ServerID=0;

ServerCtrl(InetAddress IP)

{

AddServer(IP);

}

public void SyncServer()

{

int i;

try

{

for(i=0;i<NumServer;i++)

{

Socket Master= new Socket(ServerList[i].ServerIP,CONTROL_PORT);

BufferedReader in

= new BufferedReader(new InputStreamReader(Master.getInputStream()));

PrintWriter out

= new PrintWriter(Master.getOutputStream(),true);

out.println("ADD\n");

NumServer=in.read();

for(i=0;i<NumServer;i++)

{

ServerList[i]=new ServerInfo();

ServerList[i].ServerID=in.read();

String s=in.readLine();

System.out.println(s);

int h1 = s.indexOf("/");

s = s.substring(h1 + 1);

ServerList[i].ServerIP=InetAddress.getByName(s);

ServerList[i].NThread=in.read();

//PrintServerList();

}

}

}

catch (Exception e)

{

}

}

/**

Aggiunge un server all'insieme di server già presenti

Il parametro è l'indirizzo IP di un server già attivo col quale avverrà la

sincronizzazione dello stato dei server. Se il parametro è localhost significa che

questo server è il primo e quindi viene costruita una nuova ServerList

@roseuid 387B57F50136

*/

public int AddServer(InetAddress NewIP) {

if (NewIP.toString().compareTo("localhost/127.0.0.1")==0)

{//E' il primo server e quindi creo una nuova tabella ed inserisco la nuova entry

try

{

ServerList= new ServerInfo[MAX_NUM_SERVER];

ServerList[ServerID]=new ServerInfo();

ServerList[ServerID].ServerID=ServerID;

ServerList[ServerID].ServerIP=InetAddress.getLocalHost();

ServerList[ServerID].NThread=0;

}

catch (Exception e){

System.out.println("Exception: ServerCtrl.AddServer: nuovo");

System.out.println(e);}

NumServer++;

System.out.println("Addserver.nuovo");

PrintServerList();

}

else

{

try

{

int i;

Socket Master= new Socket(NewIP,CONTROL_PORT);

System.out.println(Master);

BufferedReader in

= new BufferedReader(new InputStreamReader(Master.getInputStream()));

PrintWriter out

= new PrintWriter(Master.getOutputStream(),true);

ServerList= new ServerInfo[MAX_NUM_SERVER];

out.println("ADD\n");

NumServer=in.read();

for(i=0;i<NumServer;i++)

{

ServerList[i]=new ServerInfo();

String s=in.readLine();

ServerList[i].ServerID=Integer.parseInt(s);

s=in.readLine();

ServerList[i].NThread=Integer.parseInt(s);

s=in.readLine();

System.out.println(s);

int h1 = s.indexOf("/");

s = s.substring(h1 + 1);

ServerList[i].ServerIP=InetAddress.getByName(s);

}

ServerID++;

ServerList[ServerID]= new ServerInfo();

ServerList[ServerID].ServerID=ServerID;

ServerList[ServerID].ServerIP=InetAddress.getLocalHost();

ServerList[ServerID].NThread=0;

NumServer++;

System.out.println("Addserver.aggiunta");

PrintServerList();

Master.close();

}

catch (Exception e) {

System.out.println("Exception: ServerCtrl.Addserver: aggiunta");

System.out.println(e);}

}

return(ServerID);}

protected void PrintServerList()

{

int i;

System.out.println("PrintServerList\n");

for(i=0;i<NumServer;i++)

{

System.out.println(ServerList[i].ServerID);

System.out.println(ServerList[i].ServerIP);

System.out.println(ServerList[i].NThread);

}

}

public ServerInfo[] GetStatus()

{

return(ServerList);

}

private void Sync_NThread()

{

int i;

try

{

for (i=0;i<NumServer;i++)

{

if (i==ServerID) continue;

Socket Master= new Socket(ServerList[i].ServerIP,CONTROL_PORT);

PrintWriter out

= new PrintWriter(Master.getOutputStream(),true);

out.println("SYN");

String s=Integer.toString(ServerID);

out.println(s);

out.println(ServerList[ServerID].NThread);

Master.close();

}

}

catch (Exception e)

{}

}

public void IncThread()

{

ServerList[ServerID].NThread++;

Sync_NThread();

System.out.println("IncThread");

PrintServerList();

}

public void DecThread()

{

ServerList[ServerID].NThread--;

Sync_NThread();

System.out.println("DecThread");

PrintServerList();

}

public static void main(String args[])

{

try

{

ServerCtrl MySrv = new ServerCtrl(InetAddress.getByName(args[0]));

}

catch (Exception e)

{

System.out.println(e);

}

}

public void run()

{

int i;

System.out.println("Run");

try

{

s = new ServerSocket(CONTROL_PORT);

for(;;)

{

ClientSocket = s.accept();

BufferedReader in

= new BufferedReader(new InputStreamReader(ClientSocket.getInputStream()));

PrintWriter out

= new PrintWriter(ClientSocket.getOutputStream(),true);

String str=in.readLine();

if (str.startsWith("ADD"))

{

out.write(NumServer);

for (i=0;i<NumServer;i++)

{

out.println(ServerList[i].ServerID);

out.println(ServerList[i].NThread);

out.println(ServerList[i].ServerIP.toString()+"\n");

}

ServerList[NumServer]= new ServerInfo();

ServerList[NumServer].ServerID=NumServer;

ServerList[NumServer].ServerIP=ClientSocket.getInetAddress();

ServerList[NumServer].NThread=0;

NumServer++;

}//Fine ADD

if (str.startsWith("SYN"))

{

int j;

String s;

s=in.readLine();

j=Integer.parseInt(s);

s=in.readLine();

ServerList[j].NThread=Integer.parseInt(s);

}//Fine SYNTH

}

}

catch (Exception e)

{

System.out.println("Exception: ServerCtrl.run");

System.out.println(e);

}

}

}

Esempio di Utilizzo

Vediamo ora con un esempio, come funziona il programma. Dopo aver compilato il codice, lo si esegue sul primo server, dando come parametro localhost. Il server si mette in ascolto di connessioni sulla porta 21:

Il programma scrive la ServerInfo, specificando l’ID di questo server (0 essendo il primo), l’indirizzo IP e il numero di thread attivi sullo stesso; quindi, se ora lanciamo il programma su un altro server, specificando come parametro l’indirizzo IP (o il nome logico) del primo, otteniamo:

In output abbiamo il nuovo stato della catena, secondo la solita sequenza ServerID, IP, Nthread. Proviamo ora a lanciare da un client una richiesta di connessione. Per esempio, da una macchina Windows è sufficiente eseguire il comando "ftp nomeserver":

Windows a questo punto apre una finestra DOS da cui è possibile eseguire i comandi FTP: osserviamo, come prima cosa, che il programma segnala all’utente lo stato dei server disponibili, specificando il n° IP (e il nome logico) e il numero di connessioni attive. Il server chiede all’utente di autenticarsi e controlla che lo stesso sia abilitato all’utilizzo del servizio.

Il server mantiene anche un file di LOG dove riporta tutte le azioni eseguite: per esempio, dopo l’autenticazione dell’utente il server scrive i seguenti dati:

A questo punto è possibile eseguire tutti i classici comandi che il servizio FTP mette a disposizione. Per esempio, è possibile chiedere che venga visualizzato il contenuto della Working directory (che noi abbiamo impostato come C:\FtpPub sul server): digitando il comando "dir":

Una volta scelto il file di cui si desidera eseguire il download si esegue il comando "get nomefile" e il server apre una connessione per il trasferimento dati sulla porta 20:

Analogamente a quanto fin qui descritto è possibile eseguire qualsiasi comando FTP; quando il cliente ha terminato la sessione lo segnala al server con il comando "quit": il controllore del server aggiorna la tabella con il n° di utenti attivi (Nthread) e segnala l’aggiornamento agli altri server presenti nel gruppo.

Conclusione

Il seguente progetto è stato particolarmente utile per avvicinarci alla programmazione con Java. Sin dalla fase iniziale di training, ci siamo accorti delle potenzialità che questo linguaggio mette a disposizione: particolarmente apprezzate sono proprio le funzioni per la creazione di socket, molto più snelle rispetto alle analoghe del C++.

Il tentativo di implementare un protocollo, inoltre, è stato molto utile, perché ci siamo esercitati a fare un algoritmo che rispondesse a specifiche ben precise e dettagliate.

Infine, l’introduzione di un piccolo protocollo di coordinamento ha messo in luce tutte le problematiche di progettazione di un protocollo di comunicazione tra computer che devono sincronizzarsi tra di loro.

Bibliografia

Programmazione in Java:

B. Eckel – Thinking in Java – Prentice Hall PTR (URL: http://ftp.unina.it)

L. Lemay, C. L. Perkins, M. Morrison – Teach yourself Java in 21 days – Sams.net Premier (URL: http://www.mcp.com/sams)

AA. VV. – PC Professionale, dicembre 1999, art. "Le classi di Java rendono facile TCP/IP" – Mondatori Informatica (URL: http://www.mondadori.com/pcpro)

Protocollo FTP:

Software utilizzato: