Iniziamo con il dire che tra due nodi (host) di una rete possiamo stabilire un canale di comunicazione dove far scorrere, in maniera bidirezionale (I/O: Input/Output), un certo flusso di informazioni.
In Tcl questo canale di comunicazione può interessare differenti tipologie di connessioni, per esempio:
- una connessione con una entità software (p.e. un file);
- una connessione di rete (con protocollo TCP) attraverso la definizione, per ogni singolo processo, di un socket (composto da un Indirizzo IP + un numero di Porta).
Nota: dalla versione 8.5 di Tcl è stato introdotto il comando
chanche gestisce molte tipiche funzionalità di I/O. Il comando non viene però trattato in questo articolo.
Connessione con una entità software
Per creare una connessione con una entità software si utilizza il comando open così come abbiamo visto negli articoli Lavorare con i files e Come avviare altri programmi da Tcl.
Connessione di rete TCP/IP
Una connessione di rete TCP/IP viene instaurata in tre passi:
- creazione del Server ed apertura della sua Porta;
- un Client invia una richiesta di connessione sulla porta del Server indicando il socket (indirizzo IP + numero di Porta);
- il Server intercetta la richiesta e crea un canale di connessione con un ascoltatore su una Porta dedicata per la futura trasmissione dei dati Server-Client.
In Tcl per creare una connessione di rete di tipo TCP/IP si utilizza il comando socket che restituisce un identificatore del canale.
Questo tipo di canale I/O può essere aperto dal nostro programma sia come Client che come Server.
Creazione di un Server
# Sintassi
socket -server command ?options? port
L’opzione -server crea il Server ed avvia l’ascoltatore sulla porta port.
Il Server si metterà quindi in ascolto sulla rete in attesa di una richiesta di attività proveniente da un qualsiasi Client. Una volta ricevuta questa richiesta, viene creato un canale di connessione dedicato tra il nostro Server e il Client. Il Server continuerà comunque a rimanere in ascolto sulla rete per verificare se dovesse arrivare una richiesta da un altro Client, nel qual caso verrà creato un altro canale di connessione dedicato.
Un Server può comunicare simultaneamente con più Clients.
Quando sulla porta del Server port arriva una richiesta di connessione da parte di un Client, allora viene chiamato il comando proc con i seguenti argomenti:
- canale di I/O: il canale di connessione Server/Client
- indirizzo IP: l’indirizzo IP del Client
- numero Porta: la porta dedicata che viene assegnata al Client
Creazione di un Client
Il comando socket senza l’opzione -server crea un Client e richiede una connessione tra questo Client e il Server identificato dall’indirizzo IP host sulla porta port.
Per connettersi con il local host si utilizza l’indirizzo IP 127.0.0.1
# Sintassi
socket ?options? host port
Come definire un gestore (handler) sul canale di connessione I/O
Una volta creato il canale di connessione tra il Server ed il Client, occorre definire un suo gestore (handler) che verrà invocato quando il canale sarà pronto per ricevere o inviare informazioni.
Per definire questo gestore si utilizza il comando fileevent.
Il gestore è necessario per evitare il possibile blocco del programma: se una nostra procedura tenta di avviare una operazione di Input (gets) o di Output (puts) verso una qualsiasi entità software che però non è ancora pronta per questa operazione di I/O, allora il nostro programma rimarrà bloccato fino a quando quella entità software non sarà pronta; se poi questa risorsa dovesse risultare, per un qualsiasi motivo, irraggiungibile, allora avremo il blocco completo del nostro programma.
Il comando
fileeventesegue uno script quando il canale I/O diventa o leggibile (readable) o scrivibile (writable).
# sintassi
fileevent channelID readable ?script?
fileevent channelID writeable ?script?
# Il comando definisce il gestore del canale di comunicazione che verrà invocato
# quando vi è una delle seguenti due condizioni:
# 1) la condizione di leggibilità che avviene quando i dati sono pronti per essere
# letti sul canale channelID;
# 2) la condizione di scrivibilità che avviene quando il channelID è pronto per
# ricevere i dati.
# Nota: la fine del file deve essere verificata con un apposito script.
Come definire un ascoltatore di eventi
Con il comando vwait diciamo al nostro programma di attendere che una certa variabile sia valorizzata informando cosi una procedura di controllo che un evento si è verificato.
In pratica è come se avessimo un semaforo che ci informa quando un Server ed un Client sono pronti per scambiarsi dati.
# sintassi
vwait varName
# Il comando mette in pausa l'esecuzione dello script fino a che una
# azione in background non valorizza la variabile globale varName.
# Nota: una azione in background può essere un comando proc invocato dal fileevent,
# o una connessione socket, oppure un evento scaturito da un widget.
Esempio con evidenziata la sequenza di esecuzione
In questo esempio vedremo il comando socket usato sia lato Client che Server, ed inoltre i comandi fileevent e vwait per il controllo delle operazioni di I/O tra i due elementi di rete.
Script
puts "Sequenza di esecuzione dei comandi Tcl nella gestione di"
puts "un canale TCP/IP di Input/Output."
puts "--------------------------------------------------------"
proc serverOpen {channel addr port} {
puts "\nSTEP #6: procedura serverOpen"
global connected; # Rende la variabile globale.
set connected 1
puts "Creazione connessione Server/Client:"
puts " canale I/O: (ID $channel)"
puts " indirizzo IP: $addr"
puts " numero Porta: $port"
puts "Valorizzazione della variabile connected a $connected."
fileevent $channel readable [list readLine Server $channel]
puts "All'interno della procedura serverOpen, il comando fileevent"
puts "ha eseguito lo script \[list readLine Server \$channel]"
puts "sul canale (ID $channel) in attesa che questo"
puts "diventi leggibile (readable)."
}
proc readLine {who channel} {
puts "\n-----------------------------------"
puts "Procedura readLine"
puts "Sul canale $who (ID $channel)"
global didRead
if {[gets $channel line] < 0} {
puts "con line < 0"
puts "line = $line"
fileevent $channel readable {}
after idle "close $channel;set out 1"
puts "Chiusura della connessione (ID $channel)"
puts "-----------------------------------"
} else {
puts "con line >= 0"
puts "messaggio ricevuto line = $line"
puts $channel "This is a return"
flush $channel
puts "Scrittura sul canale (ID $channel)"
puts "del messaggio di ritorno \"This is a return\""
set didRead 1
puts "-----------------------------------"
}
}
puts "\nSTEP #1: set connected 0; set didRead 0; set out 0"
set connected 0; set didRead 0; set out 0
puts " - variabile connected per l'ascoltatore di eventi lato Server"
puts " - variabile didRead per riscontrare la lettura di un messaggio"
puts " - variabile out per riscontrare la chiusura del Server"
puts "\nSTEP #2: set server \[socket -server serverOpen 33000]"
set server [socket -server serverOpen 33000]
puts "Creazione del Server:"
puts " - la variabile server conterrà l'identificativo del canale:"
puts " - canale Server (ID $server);"
puts " - il comando socket crea il server sulla porta 33000;"
puts " - quando su questa porta si stabilisce una connessione, allora"
puts " viene chiamato il comando serverOpen."
puts "\nSTEP #3: after 100 update"
after 100 update
puts "Forzatura dell'ascolto di eventuali eventi con il comando update:"
puts "Il comando after esegue il comando update dopo 100 ms."
puts "Il comando update elabora tutti gli eventi in sospeso."
puts "\nSTEP #4: sock \[socket -async 127.0.0.1 33000]"
set sock [socket -async 127.0.0.1 33000]
puts "Creazione del Client:"
puts " - la variabile sock conterrà l'identificativo del canale:"
puts " - canale Client (ID $sock);"
puts " - il comando socket senza l'opzione -server crea il Client e"
puts " crea il canale tra questo Client e il Server"
puts " identificato dall'indirizzo local host sulla porta 33000."
puts "\nSTEP #5: vwait connected"
puts "Il comando vwait mette in pausa lo script fino a che una azione"
puts "in background non valorizza la variabile globale connected."
puts "Nota: una azione in background può essere un comando proc"
puts " invocato dal fileevent, o una connessione socket."
puts "Prima di un evento in background connected = $connected"
vwait connected
puts "Dopo l'evento connected = $connected"
puts "Server (ID $server) attivo."
puts "\nSTEP #7: puts \$sock \"A Test Line\""
puts $sock "A Test Line"
puts "Invio verso il Client (ID $sock) di una stringa."
puts "\nSTEP #8: flush \$sock"
flush $sock
puts "Forza l'invio dei dati ripulendo il buffer verso il"
puts "Client (ID $sock)."
puts "\nSTEP #9: vwait didRead"
puts "Il comando vwait mette in pausa lo script fino a che una azione"
puts "in background non valorizza la variabile globale didRead."
puts "Prima di un evento in background didRead = $didRead"
vwait didRead
puts "Dopo l'evento didRead = $didRead"
puts "\nSTEP #10: set len \[gets \$sock line]"
set len [gets $sock line]
puts "Valorizza la variabile len con la lunghezza della stringa line"
puts "letta sul Client (ID $sock)."
puts "len = $len"
puts "line = $line"
puts "\nSTEP #11: catch {close \$sock}"
catch {close $sock}
puts "Il comando catch intercetta eventuali errori causati dalla"
puts "esecuzione del comando close evitando il blocco del programma."
puts "Chiusura del Client (ID $sock)"
puts "\nSTEP #12: vwait out"
puts "Il comando vwait mette in pausa lo script fino a che una azione"
puts "in background non valorizza la variabile globale out."
puts "Prima di un evento in background out = $out"
vwait out
puts "Dopo l'evento out = $out"
puts "\nSTEP #13: close \$server"
close $server
puts "Chiusura del Server (ID $server)."
Risultato
Sequenza di esecuzione dei comandi Tcl nella gestione di
un canale TCP/IP di Input/Output.
--------------------------------------------------------
STEP #1: set connected 0; set didRead 0; set out 0
- variabile connected per l'ascoltatore di eventi lato Server
- variabile didRead per riscontrare la lettura di un messaggio
- variabile out per riscontrare la chiusura del Server
STEP #2: set server [socket -server serverOpen 33000]
Creazione del Server:
- la variabile server conterrà l'identificativo del canale:
- canale Server (ID sock55b55d69a040);
- il comando socket crea il server sulla porta 33000;
- quando su questa porta si stabilisce una connessione, allora
viene chiamato il comando serverOpen.
STEP #3: after 100 update
Forzatura dell'ascolto di eventuali eventi con il comando update:
Il comando after esegue il comando update dopo 100 ms.
Il comando update elabora tutti gli eventi in sospeso.
STEP #4: sock [socket -async 127.0.0.1 33000]
Creazione del Client:
- la variabile sock conterrà l'identificativo del canale:
- canale Client (ID sock55b55d69b440);
- il comando socket senza l'opzione -server crea il Client e
crea il canale tra questo Client e il Server
identificato dall'indirizzo local host sulla porta 33000.
STEP #5: vwait connected
Il comando vwait mette in pausa lo script fino a che una azione
in background non valorizza la variabile globale connected.
Nota: una azione in background può essere un comando proc
invocato dal fileevent, o una connessione socket.
Prima di un evento in background connected = 0
STEP #6: procedura serverOpen
Creazione connessione Server/Client:
canale I/O: (ID sock55b55d6b1720)
indirizzo IP: 127.0.0.1
numero Porta: 37197
Valorizzazione della variabile connected a 1.
All'interno della procedura serverOpen, il comando fileevent
ha eseguito lo script [list readLine Server $channel]
sul canale (ID sock55b55d6b1720) in attesa che questo
diventi leggibile (readable).
Dopo l'evento connected = 1
Server (ID sock55b55d69a040) attivo.
STEP #7: puts $sock "A Test Line"
Invio verso il Client (ID sock55b55d69b440) di una stringa.
STEP #8: flush $sock
Forza l'invio dei dati ripulendo il buffer verso il
Client (ID sock55b55d69b440).
STEP #9: vwait didRead
Il comando vwait mette in pausa lo script fino a che una azione
in background non valorizza la variabile globale didRead.
Prima di un evento in background didRead = 0
-----------------------------------
Procedura readLine
Sul canale Server (ID sock55b55d6b1720)
con line >= 0
messaggio ricevuto line = A Test Line
Scrittura sul canale (ID sock55b55d6b1720)
del messaggio di ritorno "This is a return"
-----------------------------------
Dopo l'evento didRead = 1
STEP #10: set len [gets $sock line]
Valorizza la variabile len con la lunghezza della stringa line
letta sul Client (ID sock55b55d69b440).
len = 16
line = This is a return
STEP #11: catch {close $sock}
Il comando catch intercetta eventuali errori causati dalla
esecuzione del comando close evitando il blocco del programma.
Chiusura del Client (ID sock55b55d69b440)
STEP #12: vwait out
Il comando vwait mette in pausa lo script fino a che una azione
in background non valorizza la variabile globale out.
Prima di un evento in background out = 0
-----------------------------------
Procedura readLine
Sul canale Server (ID sock55b55d6b1720)
con line < 0
line =
Chiusura della connessione (ID sock55b55d6b1720)
-----------------------------------
Dopo l'evento out = 1
STEP #13: close $server
Chiusura del Server (ID sock55b55d69a040).
In sintesi
Più sotto i passaggi fondamentali.
STEP #2 creazione Server: (ID sock55b55d69a040) sulla Porta 33000
STEP #4 creazione Client: (ID sock55b55d69b440) sulla Porta 33000
STEP #6 connessione Server/Client: (ID sock55b55d6b1720) sulla Porta 37197
attivazione ascoltatore su: (ID sock55b55d6b1720)
attivazione Server: (ID sock55b55d69a040)
STEP #7 invio messaggio al Client: (ID sock55b55d69b440)
procedure readLine
Input "A Test Line" su: (ID sock55b55d6b1720)
Output "This is a return" su: (ID sock55b55d6b1720)
STEP #10 ricezione "This is a return" su: (ID sock55b55d69b440)
STEP #11 chiusura del Client: (ID sock55b55d69b440)
procedure readLine
ricezione "" su: (ID sock55b55d6b1720)
chiusura della connessione Server/Client: (ID sock55b55d6b1720)
STEP #13 chiusura del Server: (ID sock55b55d69a040)
Nel prossimo articolo approfondiremo la gestione di un canale di Input/Output.