Il
Server
Il
nostro server è una sorta di servizio che sta in ascolto su una
porta TCP/IP in attesa di connessioni. Una volta stabilita la connessione
ed attivata la comunicazione bidirezionale, è possibile scambiare
messaggi con il client. Nel nostro caso, il client darà dei comandi
da eseguire su un database, e il server restituirà eventuali dati
o messaggi di errore.
Intanto
di che librerie avremo bisogno ? Sicuramente java.net per la gestione
dei socket, java.io per i flussi di dati in input/output e poi la java.sql
per la connessione al database. Quindi:
import
java.net.*;
import
java.io.*;
import
java.sql.*;
A questo
punto possiamo creare la nostra classe:
public
class JDBCServer {
static ServerSocket ssock;
static BufferedReader in;
static PrintWriter out;
static Connection con;
static Statement stmt ;
static ResultSet rs ;
static ResultSetMetaData rsmd;
Vediamo
a cosa ci serviranno gli oggetti dichiarati:
il
ServerSocket è per l’appunto il socket in ascolto su una porta in
attesa di connessioni. Come vedremo, all’arrivo di una richiesta di connessione
verrà creato un socket “normale” per la comunicazione con il client.
BufferedReader
e PrintWriter sono gli oggetti implicati nello “stream” dei dati, rispettivamente
in ingresso ed uscita.
Connection
gestisce la connessione al database,Statement rappresenta un oggetto per
le query, ResultSet conterrà i risultati delle query effettuate
e ResultSetMetaData conterrà informazioni sulla struttura dei dati
presenti nel recordset ( che utilizzeremo nel prossimo articolo).
10
public static void main(String[] args){
20
String comando;
30
String data,inputLine;
40
try {
50
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
60
String dburl = "jdbc:odbc:nomedb";
70
con = DriverManager.getConnection(dburl, "", "");
80
stmt = con.createStatement();
90
ServerSocket ssock=new ServerSocket(3000);
100
Socket sock=null;
In
questa parte viene effettuata la connessione al database. Come detto nel
precedente articolo, stiamo supponendo una connessione ad un database odbc
tramite bridge jdbc:odbc. Alla riga 60 si costruisce la stringa di connessione,
dove nomedb è il nome odbc del nostro database.Dopo avere inizializzato
la connessione al database creiamo(riga 80) l’oggetto Statement che ci
servirà per costruire le query.
Le
righe 90 e 100 si occupano invece di dichiarare 2 socket: uno server sulla
porta 3000, e uno client per la comunicazione con il client.
A questo
punto siamo pronti per entrare nel loop principale del nostro server:
110
while (true){
120
sock=ssock.accept();
130
in = new BufferedReader(new InputStreamReader(sock.getInputStream()));
140
out = new PrintWriter(sock.getOutputStream(),true);
Alla
riga 120 viene lanciato il metodo “accept” del ServerSocket. Questo metodo,
sincrono, mette il ServerSocket in attesa di una connessione da parte di
un client, dopo di che crea un socket per la comunicazione bidirezionale
con il client stesso.
Le
2 righe seguenti si occupano invece di inizializzare lo “streaming” in
ingresso e in uscita lungo il socket.
Una
volta creato il socket, e quindi instaurato un canale di trasmissione fra
client e server, possiamo far partire il secondo loop, quello che si occupa
di controllare se arrivino richieste dal client. Nel nostro caso questo
meccanismo è sufficiente in quanto è sempre il client a dare
disposizioni sul da farsi, quindi il server si limita ad attendere istruzioni,
eseguirle , ed eventualmente restituire informazioni.
150
while (true) {
160
inputLine = in.readLine();
Alla riga
160 , il metodo redLine() sullo stream di input attende in maniera sincrona
l’arrivo di una riga di dati.
Le
righe successive compongono il parser, quel modulo che si occupa di interpretare
i dati in arrivo. In questo caso, abbiamo utilizzato la seguente convenzione:
la riga è composta da un comando, seguito da un duepunti( : ), seguito
dalla stringa di dati da utilizzare. La riga 170 restituisce la posizione
nella stringa del segno di duepunti. Se viene trovato, il valore sarà
l’indice di posizione, altrimenti otterremo uno zero. Ed è questo
il controllo che viene effettuato alla riga 180. In pratica, la stringa
verrà processata solo se è presente il segno di due punti
e se viene riconosciuto un comando valido.
170
int p = inputLine.indexOf(":");
180
if (p > 0) {
190
comando = inputLine.substring(0,p);
200
data = inputLine.substring(p+1,inputLine.length());
La
variabile “comando” contiene il “metodo” richiesto dal client, che nella
stringa ricevuta è la prima parte prima del segno i duepunti. Nelle
righe successive verranno eseguite varie operazioni a seconda del comando
ricevuto, ed ora le vedremo una ad una. Notiamo che il metodo utilizzato,
cioè una serie di if per controllare il comando, non è molto
elegante, ma a fini didattici è sopportabile la mancanza di stile.
Ci
siamo limitati ad implementare 4 “comandi”, da un lato sufficienti per
vedere il nostro server in azione, dall’altro abbastanza esplicativi da
permettere l’implementazione di qualsiasi altro tipo di comando .
Il
comando “Query”, esegue appunto una query. Supponiamo che la stringa ricevuta
sia la seguente:
“Query:select
* from tabella where ID=13”. Il parser troverà un segno di due punti
in posizione 6 e dividerà la stringa di conseguenze. Avremo così
il comando “Query” e la stringa di dati “select * from tabella where ID=13”.
Osservando le righe dalla 210 alla 280 è piuttosto semplice capire
ciò che accade; la stringa di dati viene passata come argomento
alla funzione ( termine orrendo per definire un metodo della nostra classe)
ExecuteQuery ( righe 510-600), che lancia la query ed eventualmente restituisce
un messaggio di errore.
210
if (comando.equals("Query")) {
220
String esegui=ExecuteStatement(data);
230
out.println("Query:" + esegui);
240
if (esegui !="OK")
250
{
260
out.println("Errore:esecuzione della query non riuscita");
270
}
280
}
Il comando
getString lancia il metodo getString interno alla classe ( righe 610-680),
che legge un campo di tipo stringa dato il nome del campo, e ne restituisce
il valore.
290
if (comando.equals("getString")) {
300
out.println("getString:" + getString(data));
310
}
Stesso
meccanismo per il comando moveNext, che in caso di EOF restituisce appunto
la stringa “EOF”
320
if (comando.equals("moveNext")) {
330
out.println("moveNext:" + moveNext());
340
}
Il
comando “Esci” serve per chiudere la connessione fra client e server e
liberare il socket. Infatti, il comando break che viene lanciato, fa uscire
dal loop interno portando l’esecuzione alla riga 400, con in seguito la
chiusura del socket e il ritorno alla riga 120, dove il server si mette
in ascolto per una successiva connessione.
350
if (comando.equals("Esci")) {
360
break;
370
}
380
}
390
}
400
try {
410
sock.close();
420
}
430
catch(Throwable ee){
440
System.out.println(ee.toString());
450
}
460
}
470
} catch (Throwable e) {
480
System.out.println(e.toString());
490
}
500
}
510
public static String ExecuteStatement(String TestoQuery){
520
try{
530
rs = stmt.executeQuery(TestoQuery);
540
rsmd = rs.getMetaData();
550
return "OK";
560
}
570
catch (Throwable t1){
580
return t1.toString();
590
}
600
}
610
public static String getString(String Campo){
620
try{
630
return rs.getString(Campo);
640
}
650
catch (Throwable t1){
660
return t1.toString();
670
}
680
}
690
public static String moveNext(){
700
try{
710
if (! rs.next())
720
return "EOF";
730
else
740
return "OK";
750
}
760
catch (Throwable t1){
770
return t1.toString();
780
}
790
}
Come
è possibile vedere, risulta molto semplice aggiungere il codice
per la gestione di qualsiasi altro metodo o proprietà del database,
in modo da ottenere un’interfaccia di comunicazione il più simile
possibile a quella utilizzata per il normale accesso via JDBC ad un database.
Utilizzo del server
In
attesa di sviluppare l’applet per l’utilizzo del server, è comunque
possibile fare degli esperimenti per testare o ampliare le funzionalità
esposte dal server stesso. Allo scopo, è sufficiente fare le seguenti
cose:
1)
Creare un database e creare la voce ODBC corrispondente con un nome simbolico
da inserire nel codice del server.
2)
Compilare e lanciare il server
3)
Aprire una sessione di telnet sull’indirizzo della macchina che lo ospita,
porta 3000. Nel caso si usi il telnet standard di windows, ricordarsi di
attivare l’eco, altrimenti non si vedranno visualizzati i dati digitati.
Una
volta aperta la sessione, per eseguire una query, scrivere “Query: select
* from tabella”, naturalmente con una query che abbia senso per il database
che si utilizza. Dare l’invio. Se la query è corretta non ci sarà
risposta, altrimenti comparirà il messaggio d’errore apposito.
Lanciare
un “moveNext:” per posizionarsi sul primo record del recordset estratto.
Lanciare
un “getString:Nome”, dove al posto di “Nome” ci sia il nome di un campo
stringa esistente nel database. Se tutto va bene, si otterrà il
valore del campo richiesto.
Con
il comando “Esci:”, il server chiuderà la sessione in corso per
mettersi in attesa di una nuova sessione.
In
questo modo è possibile testare in maniera semplice tutto ciò
che si voglia implementare nel server.
Note
In
questa implementazione abbiamo volutamente trascurato tutta una serie di
importanti funzionalità, parte delle quali vedremo nel prossimo
articolo e parte sono lasciate al lettore in quanto semplici estensioni
di quanto fin qui visto.
Ne
cito alcune:
1)
gestione completa delle eccezioni
2)
Comportamento multithreading del server
3)
gestione dei MetaData del database
4)
Possibilità di indicare via client anche il database al quale accedere
5)
Tutta una serie di funzioni di recupero dati dal recordset.
6)
Gestione delle query di update
Conclusioni
Abbiamo
visto un bel po’ di materiale sul quale ragionare. E’ bene digerire tutta
la struttura prima di addentrarci nelle ulteriori implementazioni che vedremo
nel prossimo articolo. Nel frattempo è comunque possibile già
estendere notevolmente le potenzialità del nostro server. E’ da
notare che questa struttura non si limita al caso di accesso ad un database;
virtualmente, il nostro server è un genericissimo application server
al quale possiamo far fare ciò che vogliamo, con un meccanismo analogo
ad una “Remote Procedure Call”, in cui il client ordina al server di fare
qualcosa. E'ora tempo di accomiatarsi e di darci appuntamento alla terza
parte dell’articolo.
|