vediamo come si implementa un client ftp.
Dopo tanto penare, eccoci infine a vedere come si fa una semplice applicazione che gestisce il protocollo FTP.
Questa applicazione consentirà all’utente di loggarsi, inserendo UserName e Password, disconnettersi, settare la modalità di trasferimento Files (Binaria o ASCII), leggere la directory corrente, cambiarla, scaricare e uploadare un file.
In pratica è un completo sistema di FTP, magari un po’ “spartano” da usare, ma completo di tutto quanto è fondamentale.
Bene, come avevo detto l’altra volta, per prima cosa è necessario connettersi al server alla porta 21.
Per fare questo si usa questa parte di codice (che, bene o male, dovrebbe essere un sistema abbastanza standard e conosciuto un po’ da tutti noi):
// Apertura Socket e inizializzazione degli Stream di Input e Output
ftpSocket=new Socket(Host,21);
os=new PrintStream(ftpSocket.getOutputStream());
is=new DataInputStream(ftpSocket.getInputStream());
Qui, come si può notare, si apre il socket e si inizializzano gli Stream di Input e di Output.
A questo punto si lancia un procedura (la OpenComm), che ritorna true se tutto è andato bene, false altrimenti.
In fondo all’articolo darò il listato completo, per cui qui mi limiterò a dare spiegazioni sulle procedure fondamentali e sul funzionamento del sistema.
Vediamo ora un ciclo che troveremo molto ricorrente nel programma. Eccolo:
ok=true;
while(ok)
{
s=is.readLine();
System.out.println(s);
if(s.length()\>5)
if(s.substring(0,4).compareTo("220 ")==0)
ok=false;
}
Questo ciclo attende dati dal server fino a che non dà un certo messaggio (questo varierà di volta in volta). In questo caso attende il 220, ossia “Bene, dimmi di chi sei”.
Noi gli diciamo chi siamo con i comandi USER e PASS che danno, rispettivamente, lo username e la password.
In entrambi i casi dobbiamo attendere una risposta, sempre usando il ciclo di prima. Nel caso del comando USER, la risposta è 331, nel caso di PASS, 230 (se la pass è corretta) o 530 (Picche).
A questo punto ci si pone in attesa di comandi dall’utente.
I comandi validi in questo programma sono:
? ASCII Imposta la modalità di trasferimento ASCII
? Binary Imposta la modalità di trasferimento binaria
? Change Directory Cambia la directory corrente
? Dir Visualizza il contenuto della directory corrente
? Get File Scarica un file
? Help Visualizza l’Help
? Print Directory Visualizza la directory corrente
? Store File Spedisce un file
? Quit Esce dal programma
Vediamo un attimino due comandi (gli altri sono molto simili e lascio la semplice interpretazione alla lettura del sorgente).
I comandi che vedremo ora sono Change e Dir, che servono rispettivamente per cambiare la directory corrente e per visualizzare la directory corrente.
Questi due comandi hanno una grande differenza tra di loro. Il primo non ha un passaggio di dati, una volta lanciato il comando si attende la risposta ed è tutto finito li, il secondo invece ha anche un passaggio di dati che avviene, come ho detto nello scorso articolo, tramite un socket diverso, aperto sul momento e per questa funzione.
Gli altri comandi sono simili a questi due e, per amor di cronaca, dirò che i comandi ASCII, Binary, Print Directory e Quit sono uguali come funzionamento al comando Change, mentre Get File e Store File sono uguali come funzionamento al comando Dir.
Bene, cominciamo dunque a vedere il più semplice, ossia il comando Change.
Il codice che esegue questo comando è il seguente:
os.println("CWD "+Dir);
ok=true;
while(ok)
{
s=is.readLine();
System.out.println(s);
if(s.length()\>5)
if((s.substring(0,4).compareTo("250 ")==0) \|\|
(s.substring(0,4).compareTo("550 ")==0))
ok=false;
}
Come si vede è decisamente banale. Si spedisce il comando (CWD + Directory) e si cicla attendendo la risposta (250=Tutto OK, 550=Directory inesistente).
Vediamo ora il comando più “complesso”, ossia il Dir.
Bene, come ho già detto, è necessario aprire un secondo socket su una porta diversa attraverso il quale passeranno i dati.
Bene, per prima cosa dobbiamo comunicare al server l’indirizzo del proprio computer e la porta sulla quale verrà aperto il socket.
Per fare questo è necessario usare il comando PORT. Port richiede, come parametro, l’indirizzo nel formato a,b,c,d e la porta, nel formato bh,bl. Il tutto spedito assieme in questa maniera:
> PORT a,b,c,d,bh,bl
Port ritorna come codice 200=OK! o 500=Porta non valida.
A questo punto si apre la porta e si manda il comando LIST. Si accetta il messaggio (150=OK! Dati in arrivo o 425=Errore!). Infine ci si pone in attesa (nel secondo socket) della directory. Alla fine si riceve nel socket primario il messaggio di “fine dati” (226).
Il codice che fa questo lavoro è il seguente:
s1="";
H=ftpSocket.getInetAddress().getLocalHost().getHostAddress();
for(l=0;l\<H.length();l++)
if(H.charAt(l)=='.')
s1+=',';
else
s1+=H.charAt(l);
P=GetPort();
// Manda la porta da usarsi
System.out.println("PORT "+s1+","+P);
os.println("PORT "+s1+","+P);
ok=true;
while(ok)
{
s=is.readLine();
System.out.println(s);
if(s.length()\>5)
if((s.substring(0,4).compareTo("200 ")==0) \|\|
(s.substring(0,4).compareTo("500 ")==0))
ok=false;
}
if(s.substring(0,4).compareTo("200 ")==0)
{
// Apre il socket
Sck=new ServerSocket(NPort-1);
// Manda il comando
System.out.println("LIST");
os.println("LIST");
ok=true;
while(ok)
{
s=is.readLine();
System.out.println(s);
if(s.length()\>5)
if((s.substring(0,4).compareTo("150 ")==0) \|\|
(s.substring(0,4).compareTo("425 ")==0))
ok=false;
}
if(s.substring(0,4).compareTo("150 ")==0)
{
// Se tutto è andato bene, visualizza i dati che arrivano dalla nuova porta
sck=Sck.accept();
isd=new DataInputStream(sck.getInputStream());
ok1=false;
s=isd.readLine();
System.out.println(s);
while(isd.available()!=0)
{
s=isd.readLine();
System.out.println(s);
}
ok=true;
while(ok)
{
s=is.readLine();
System.out.println(s);
if(s.length()\>5)
if(s.substring(0,4).compareTo("226 ")==0)
ok=false;
}
sck.close();
}
Sck.close();
}
Come si vede la parte più onerosa in righe di codice è quella che contiene i cicli di attesa messaggi dal server. Questa parte è però fondamentale in quanto non si potrebbe altrimenti sapere se tutto è andato bene o meno.
Bene, a questo punto allego il sorgente perfettamente funzionante della classe, con tutti i commenti.
Come ho già detto prima le procedure sono simili o alla Change o alla GetDir, in quanto o non c’è comunicazione o, se c’è è prima necessario aprire un socket.
Anche le due funzioni di trasferimento file sono praticamente uguali alla GetDir, in fin dei conti, anche la directory corrente è un file: un file di testo che viene scaricato sullo schermo al posto di essere inserito in un file su disco.
Concludo dunque qui la mia breve dissertazione sul protocollo FTP. Come al solito, se qualcuno necessita di spiegazioni non ha che da mandarmi una E-Mail e risponderò volentieri.
L’uso del programma è banale. Occorre lanciarlo dando come parametro l’host a cui collegarsi. Il programma chiederà UserName e Password (naturalmente è anche possibile anche la connessione anonima) e quindi visualizza l’help e accetta i comandi.
Luca Bertoncello (lucaber@biella.alpcom.it)
Sorgente del programma FTP.java
import java.awt.\*; import java.io.\*;
import java.net.\*; public class ftp
{
public static DataInputStream is; // Stream di Input
public static PrintStream os; // Stream di Output
public static Socket ftpSocket; // Socket di comunicazione
public static int NPort; // Porta. Numero progressivo
public static String TrMode; // Transfer Mode. Ascii o Binary
public static char getch()
{
char c; // Questa procedura attende la pressione di un tasto.
c=0;
while(c==0)
try
{
c=(char) System.in.read();
}
catch(Exception e)
{
c=0;
}
return(c);
}
public static int Comando(boolean vis)
{
char c=0;
boolean ok=true;
try
{
while(System.in.available()!=0)
getch();
}
catch(Exception e)
{
}
// Schermata con l'elenco dei comandi disponibili.
// Attende che si prema un tasto e si immetta un comando.
if(vis)
{
System.out.println("Elenco comandi disponibili.");
System.out.println(" (A)SCII Imposta la modalita' di trasferimento ASCII");
System.out.println(" (B)inary Imposta la modalita' di trasferimento binaria");
System.out.println(" (C)hange Directory Cambia la directory corrente");
System.out.println(" (D)ir Visualizza il contenuto della directory corrente");
System.out.println(" (G)et File Scarica un file");
System.out.println(" (H)elp Visualizza questo Help");
System.out.println(" (P)rint Directory Visualizza la directory corrente");
System.out.println(" (S)tore File Spedisce un file");
System.out.println(" (Q)uit Esce dal programma");
System.out.println("Inserire un comando (C/D/H/P/S/Q): ");
}
c=getch();
try
{
while(System.in.available()!=0)
getch();
}
catch(Exception e)
{
}
c=Character.toUpperCase(c);
if((c=='Q') \|\| (c=='C') \|\| (c=='H') \|\| (c=='D') \|\| (c=='A') \|\| (c=='B') \|\| (c=='G') \|\| (c=='P') \|\| (c=='S'))
ok=false;
return(c);
}
public static long atol(String s)
{
// Converte la string passato in un long
Long tempInt;
> String s1;
> int l,k;
long q=0;
for(l=-1,k=0;(k\<s.length()) && (l==-1);k++)
> if((s.charAt(k)\<'0') \|\| (s.charAt(k)\>'9'))
l=k;
if(l==-1)
{
s1=s;
tempInt = Long.valueOf(s1);
q=tempInt.longValue();
}
else
if(l!=0)
{
s1=s.substring(0,l);
tempInt = Long.valueOf(s1);
q=tempInt.longValue();
}
return(q);
}
public static int atoi(String s)
{
// Converte la string passato in un integer
Integer tempInt;
> String s1;
int k,l;
for(l=-1,k=0;(k\<s.length()) && (l==-1);k++)
> if((s.charAt(k)\<'0') \|\| (s.charAt(k)\>'9'))
l=k;
if(l==-1)
{
s1=s;
tempInt = Integer.valueOf(s1);
k=tempInt.intValue();
}
else
if(l!=0)
{
s1=s.substring(0,l);
tempInt = Integer.valueOf(s1);
k=tempInt.intValue();
}
else
k=0;
return(k);
}
public static String GetString(String Prompt)
{
String s="",Ret="";
int n=0;
char c; // Prende una stringa da tastiera
System.out.print(Prompt+": ");
while(s.length()==0)
try
{
c=0;
while(c!='\n')
{
c=getch();
if(c!='\n')
s+=c;
}
}
catch(Exception e)
{
}
Ret=s.substring(0,s.length()-1);
return(Ret);
}
public static boolean OpenComm() throws IOException
{
// Questa procedura si "logga" con il server, fornendo UserName e Password.
// Visualizza messaggi di errore nel caso UserName o Password siano errati
// Ritorna TRUE se si e' loggato, FALSE altrimenti
String s="",User="",Passwd="";
boolean Ret=true,ok;
ok=true;
while(ok)
{
s=is.readLine();
System.out.println(s);
if(s.length()\>5)
if(s.substring(0,4).compareTo("220 ")==0)
ok=false;
}
User=GetString("UserName");
os.println("USER "+User);
System.out.println("USER "+User);
ok=true;
while(ok)
{
s=is.readLine();
System.out.println(s);
if(s.length()\>5)
if(s.substring(0,4).compareTo("331 ")==0)
ok=false;
}
Passwd=GetString("Password");
os.println("PASS "+Passwd);
System.out.println("PASS \*\*\*\*\*\*\*\*");
ok=true;
while(ok)
{
s=is.readLine();
System.out.println(s);
if(s.length()\>5)
if((s.substring(0,4).compareTo("230 ")==0) \|\| (s.substring(0,4).compareTo("530 ")==0))
ok=false;
}
if(s.substring(0,4).compareTo("230 ")==0)
System.out.println("Utente "+User+" Connesso");
else
Ret=false;
return(Ret);
}
public static void ChDir() throws IOException
{
String Dir,s="";
boolean ok; // Cambia la directory corrente
Dir=GetString("Nuova Directory");
os.println("CWD "+Dir);
// Ciclo di attesa messaggio dal server
ok=true;
while(ok)
{
s=is.readLine();
System.out.println(s);
if(s.length()\>5)
if((s.substring(0,4).compareTo("250 ")==0) \|\| (s.substring(0,4).compareTo("550 ")==0))
ok=false;
}
}
public static void PwDir() throws IOException
{
boolean ok;
String s=""; // Visualizza la directory corrente
os.println("PWD");
// Ciclo di attesa messaggio dal server
ok=true;
while(ok)
{
s=is.readLine();
System.out.println(s);
if(s.length()\>5)
if(s.substring(0,4).compareTo("257 ")==0)
ok=false;
}
}
public static String GetPort()
{
int l,h;
String R; // Scompone il numero di porta (progressivo) in Byte Alto e Byte Basso
> h=NPort/256;
> l=NPort-(h\*256);
> R=""+h+","+l;
NPort++;
return(R);
}
public static void GetFile() throws IOException
{
boolean ok,ok1=true;
String s="",s1,P="",H="",NFile="",NFileS="";
int l;
ServerSocket Sck;
Socket sck;
DataInputStream isd;
RandomAccessFile f; // Scarica un file
NFile=GetString("File da scaricare");
NFileS=GetString("File scaricato");
// Cicla finchè il file è stato trasferito senza errori
while(ok1)
{
s1="";
H=ftpSocket.getInetAddress().getLocalHost().getHostAddress();
for(l=0;l\<H.length();l++)
if(H.charAt(l)=='.')
s1+=',';
else
s1+=H.charAt(l);
P=GetPort();
// Imposta la porta
System.out.println("PORT "+s1+","+P);
os.println("PORT "+s1+","+P);
ok=true;
while(ok)
{
s=is.readLine();
System.out.println(s);
if(s.length()\>5)
if((s.substring(0,4).compareTo("200 ")==0) \|\| (s.substring(0,4).compareTo("500 ")==0))
ok=false;
}
if(s.substring(0,4).compareTo("200 ")==0)
{
// Imposta il tipo di file
System.out.println("TYPE "+TrMode);
os.println("TYPE "+TrMode);
ok=true;
while(ok)
{
s=is.readLine();
System.out.println(s);
if(s.length()\>5)
if((s.substring(0,4).compareTo("200 ")==0) \|\| (s.substring(0,4).compareTo("500 ")==0))
ok=false;
}
// Apre il nuovo socket
Sck=new ServerSocket(NPort-1);
// Manda il comando di download
System.out.println("RETR "+NFile);
os.println("RETR "+NFile);
ok=true;
while(ok)
{
s=is.readLine();
System.out.println(s);
if(s.length()\>5)
if((s.substring(0,4).compareTo("150 ")==0) \|\| (s.substring(0,4).compareTo("425 ")==0))
ok=false;
}
if(s.substring(0,4).compareTo("150 ")==0)
{
// Se tutto è andato bene, si prepara ad accettare i dati
sck=Sck.accept();
isd=new DataInputStream(sck.getInputStream());
f=new RandomAccessFile(NFileS,"rw");
ok1=false;
// Scrive i dati su disco
f.write(isd.read());
while(isd.available()!=0)
f.write(isd.read());
f.close();
ok=true;
while(ok)
{
s=is.readLine();
System.out.println(s);
if(s.length()\>5)
if(s.substring(0,4).compareTo("226 ")==0)
ok=false;
}
sck.close();
}
Sck.close();
}
}
}
public static void PutFile() throws IOException
{
boolean ok,ok1=true;
String s="",s1,P="",H="",NFile="",NFileS="";
int l;
ServerSocket Sck;
Socket sck;
PrintStream osd;
RandomAccessFile f; // Procedure di upload
NFile=GetString("File da mandare");
NFileS=GetString("File remoto");
while(ok1)
{
s1="";
H=ftpSocket.getInetAddress().getLocalHost().getHostAddress();
for(l=0;l\<H.length();l++)
if(H.charAt(l)=='.')
s1+=',';
else
s1+=H.charAt(l);
P=GetPort();
System.out.println("PORT "+s1+","+P);
os.println("PORT "+s1+","+P);
ok=true;
while(ok)
{
s=is.readLine();
System.out.println(s);
if(s.length()\>5)
if((s.substring(0,4).compareTo("200 ")==0) \|\| (s.substring(0,4).compareTo("500 ")==0))
ok=false;
}
if(s.substring(0,4).compareTo("200 ")==0)
{
System.out.println("TYPE "+TrMode);
os.println("TYPE "+TrMode);
ok=true;
while(ok)
{
s=is.readLine();
System.out.println(s);
if(s.length()\>5)
if((s.substring(0,4).compareTo("200 ")==0) \|\| (s.substring(0,4).compareTo("500 ")==0))
ok=false;
}
System.out.println("STOR "+NFile);
os.println("STOR "+NFile);
Sck=new ServerSocket(NPort-1);
ok=true;
while(ok)
{
s=is.readLine();
System.out.println(s);
if(s.length()\>5)
if((s.substring(0,4).compareTo("150 ")==0) \|\| (s.substring(0,4).compareTo("425 ")==0))
ok=false;
}
if(s.substring(0,4).compareTo("150 ")==0)
{
sck=Sck.accept();
osd=new PrintStream(sck.getOutputStream());
f=new RandomAccessFile(NFileS,"r");
ok1=false;
while(f.getFilePointer()\<f.length())
osd.write(f.read());
f.close();
sck.close();
ok=true;
while(ok)
{
s=is.readLine();
System.out.println(s);
if(s.length()\>5)
if(s.substring(0,4).compareTo("226 ")==0)
ok=false;
}
}
Sck.close();
}
}
}
public static void GetDir() throws IOException
{
boolean ok,ok1;
String s="",s1,P="",H="";
int l;
ServerSocket Sck;
Socket sck;
DataInputStream isd; // Visualizza il contenuto della directory corrente
ok1=true;
while(ok1)
{
s1="";
H=ftpSocket.getInetAddress().getLocalHost().getHostAddress();
for(l=0;l\<H.length();l++)
if(H.charAt(l)=='.')
s1+=',';
else
s1+=H.charAt(l);
P=GetPort();
// Manda la porta da usarsi
System.out.println("PORT "+s1+","+P);
os.println("PORT "+s1+","+P);
ok=true;
while(ok)
{
s=is.readLine();
System.out.println(s);
if(s.length()\>5)
if((s.substring(0,4).compareTo("200 ")==0) \|\| (s.substring(0,4).compareTo("500 ")==0))
ok=false;
}
if(s.substring(0,4).compareTo("200 ")==0)
{
// Apre il socket
Sck=new ServerSocket(NPort-1);
// Manda il comando
System.out.println("LIST");
os.println("LIST");
ok=true;
while(ok)
{
s=is.readLine();
System.out.println(s);
if(s.length()\>5)
if((s.substring(0,4).compareTo("150 ")==0) \|\| (s.substring(0,4).compareTo("425 ")==0))
ok=false;
}
if(s.substring(0,4).compareTo("150 ")==0)
{
// Se tutto è andato bene, visualizza i dati che arrivano dalla nuova porta
sck=Sck.accept();
isd=new DataInputStream(sck.getInputStream());
ok1=false;
s=isd.readLine();
System.out.println(s);
while(isd.available()!=0)
{
s=isd.readLine();
System.out.println(s);
}
ok=true;
while(ok)
{
s=is.readLine();
System.out.println(s);
if(s.length()\>5)
if(s.substring(0,4).compareTo("226 ")==0)
ok=false;
}
sck.close();
}
Sck.close();
}
}
}
public static void SetTrMode(String Mod) throws IOException
{
String s="";
boolean ok; // Imposta la modalità di trasferimento file.
// A=ASCII
// I=Binary
TrMode=Mod;
os.println("TYPE "+Mod);
System.out.println("TYPE "+Mod);
ok=true;
while(ok)
{
s=is.readLine();
System.out.println(s);
if(s.length()\>5)
if(s.substring(0,4).compareTo("200 ")==0)
ok=false;
}
}
public static void Connect(String Host) throws IOException
{
boolean ok=true,vis=true;
String s="";
int c,n; // Apertura Socket e inizializzazione degli Stream di Input e Output
ftpSocket=new Socket(Host,21);
os=new PrintStream(ftpSocket.getOutputStream());
is=new DataInputStream(ftpSocket.getInputStream());
NPort=50000; // Se l'utente e' loggato correttamente, accetta i comandi
if(OpenComm()==true)
{
// Ciclo finchè non si decide di uscire.
// Prende l'istruzione dall'utente e la esegue.
while(ok)
{
c=Comando(vis);
vis=false;
switch(c)
{
case 'A':
SetTrMode("A");
break;
case 'B':
SetTrMode("I");
break;
case 'G':
GetFile();
break;
case 'H':
vis=true;
break;
case 'C':
ChDir();
break;
case 'D':
GetDir();
break;
case 'P':
PwDir();
break;
case 'S':
PutFile();
break;
case 'Q':
ok=false;
break;
}
}
} // Chiude la comunicazione con FTP e si disconnette
os.println("QUIT");
System.out.println("QUIT");
ok=true;
while(ok)
{
s=is.readLine();
System.out.println(s);
if(s.length()\>5)
if(s.substring(0,4).compareTo("221 ")==0)
ok=false;
}
is.close();
os.close();
ftpSocket.close();
}
public static void main(String args\[\])
{
try
{
Connect(args\[0\]);
}
catch(Exception e)
{
System.out.println("Eccezione "+e);
}
}
}
