9. Programmazione TCP/IP
9.1. Informazioni generali
9.1.1. Protocolli Internet
Qui forniamo un'introduzione ai protocolli di comunicazione Internet, noti anche come suite di protocolli TCP/IP (Transmission Control Protocol / Internet Protocol), dal nome dei due protocolli principali. È consigliabile che il lettore abbia una comprensione generale di come funzionano le reti, e in particolare dei protocolli TCP/IP, prima di affrontare lo sviluppo di applicazioni distribuite.
Il testo che segue è una traduzione parziale di un testo tratto dal documento "LAN Workplace for DOS - Administrator's Guide" di NOVELL, un documento risalente ai primi anni '90.
Il concetto generale di creazione di una rete di computer eterogenei deriva dalla ricerca condotta dalla DARPA (Defense Advanced Research Projects Agency) negli Stati Uniti. La DARPA ha sviluppato la suite di protocolli nota come TCP/IP, che consente a macchine eterogenee di comunicare tra loro. Questi protocolli sono stati testati su una rete chiamata ARPAnet, che in seguito è diventata Internet. I protocolli TCP/IP definiscono formati e regole per la trasmissione e la ricezione che sono indipendenti dall'architettura di rete e dall'hardware utilizzato.
La rete progettata dalla DARPA e gestita dai protocolli TCP/IP è una rete a commutazione di pacchetti. Una rete di questo tipo trasmette le informazioni attraverso la rete in piccoli frammenti chiamati pacchetti. Pertanto, se un computer trasmette un file di grandi dimensioni, questo verrà suddiviso in piccoli frammenti che vengono inviati attraverso la rete per essere ricomposti a destinazione. Il TCP/IP definisce il formato di questi pacchetti, ovvero:
- origine del pacchetto
- destinazione
- lunghezza
- tipo
9.1.2. Il modello OSI
I protocolli TCP/IP seguono generalmente il modello di rete aperto noto come OSI (Open Systems Interconnection Reference Model) definito dall'ISO (International Standards Organization). Questo modello descrive una rete ideale in cui la comunicazione tra macchine può essere rappresentata da un modello a sette livelli:
![]() |
Ogni livello riceve servizi dal livello sottostante e fornisce i propri servizi al livello sovrastante. Supponiamo che due applicazioni situate su macchine diverse A e B vogliano comunicare: lo fanno a livello di Applicazione. Non hanno bisogno di conoscere tutti i dettagli di come funziona la rete: ogni applicazione passa le informazioni che desidera trasmettere al livello sottostante: il livello di Presentazione. L'applicazione deve quindi conoscere solo le regole per interfacciarsi con il livello di Presentazione.
Una volta che le informazioni si trovano nel livello di Presentazione, vengono passate secondo altre regole al livello di Sessione, e così via, fino a quando le informazioni raggiungono il mezzo fisico e vengono trasmesse fisicamente alla macchina di destinazione. Lì, subiranno il processo inverso di quello che hanno subito sulla macchina mittente.
Ad ogni livello, il processo mittente responsabile dell'invio delle informazioni le invia a un processo ricevente sull'altra macchina appartenente allo stesso livello. Lo fa secondo determinate regole note come protocollo di livello. Abbiamo quindi il seguente diagramma di comunicazione finale:
![]() |
I ruoli dei diversi livelli sono i seguenti:
Garantisce la trasmissione di bit su un mezzo fisico. Questo livello include apparecchiature terminali di elaborazione dati (DPTE) quali terminali o computer, nonché apparecchiature di terminazione del circuito dati (DCTE) quali modulatori/demodulatori, multiplexer e concentratori. I punti chiave a questo livello sono: . la scelta della codifica delle informazioni (analogica o digitale) . la scelta della modalità di trasmissione (sincrona o asincrona). | |
Nasconde le caratteristiche fisiche del livello fisico. Rileva e corregge gli errori di trasmissione. | |
Gestisce il percorso che le informazioni inviate sulla rete devono seguire. Questo processo è chiamato routing: determinare il percorso che le informazioni devono seguire per raggiungere la loro destinazione. | |
Consente la comunicazione tra due applicazioni, mentre i livelli precedenti permettevano solo la comunicazione tra macchine. Un servizio fornito da questo livello può essere il multiplexing: il livello di trasporto può utilizzare una singola connessione di rete (da macchina a macchina) per trasmettere dati appartenenti a più applicazioni. | |
Questo livello fornisce servizi che consentono a un'applicazione di aprire e mantenere una sessione operativa su una macchina remota. | |
Ha lo scopo di standardizzare la rappresentazione dei dati tra macchine diverse. Pertanto, i dati provenienti dalla macchina A saranno "formattati" dal livello di Presentazione della macchina A secondo un formato standard prima di essere inviati sulla rete. Una volta raggiunto il livello di Presentazione della macchina di destinazione B, che li riconoscerà grazie al loro formato standard, saranno formattati in modo diverso in modo che l'applicazione sulla macchina B possa riconoscerli. | |
A questo livello si trovano applicazioni che sono generalmente vicine all'utente, come la posta elettronica o il trasferimento di file. |
9.1.3. Il modello TCP/IP
Il modello OSI è un modello ideale che non è mai stato pienamente realizzato. La suite di protocolli TCP/IP lo approssima nel modo seguente:
![]() |
Livello fisico
Nelle reti locali si utilizzano generalmente le tecnologie Ethernet o Token Ring. In questa sede ci concentreremo esclusivamente sulla tecnologia Ethernet.
Ethernet
Questo è il nome dato a una tecnologia di rete locale a commutazione di pacchetti inventata presso il Xerox PARC nei primi anni '70 e standardizzata da Xerox, Intel e Digital Equipment nel 1978. La rete è costituita fisicamente da un cavo coassiale di circa 1,27 cm di diametro e lungo fino a 500 m. Può essere estesa utilizzando ripetitori, con non più di due ripetitori che separano due macchine qualsiasi. Il cavo è passivo: tutti i componenti attivi si trovano sulle macchine collegate al cavo. Ogni macchina è collegata al cavo tramite una scheda di accesso alla rete che comprende:
- un ricetrasmettitore che rileva la presenza di segnali sul cavo e converte i segnali analogici in segnali digitali e viceversa.
- un accoppiatore che riceve i segnali digitali dal ricetrasmettitore e li trasmette al computer per l'elaborazione, o viceversa.
Le caratteristiche principali della tecnologia Ethernet sono le seguenti:
- Capacità di 10 megabit al secondo.
- Topologia a bus: tutte le macchine sono collegate allo stesso cavo
![]() |
- Rete broadcast: un dispositivo trasmittente invia informazioni sul cavo indicando l'indirizzo del dispositivo ricevente. Tutti i dispositivi collegati ricevono quindi queste informazioni, ma solo il destinatario previsto le conserva.
- Il metodo di accesso è il seguente: il trasmettitore che desidera trasmettere ascolta il cavo — rileva quindi se è presente o meno un'onda portante, la cui presenza indicherebbe che è in corso una trasmissione. Questa è la tecnica CSMA (Carrier Sense Multiple Access). In assenza di una portante, un trasmettitore può decidere di trasmettere a sua volta. Diversi trasmettitori possono prendere questa decisione. I segnali trasmessi si sovrappongono: questo fenomeno è chiamato collisione. Il trasmettitore rileva questa situazione: mentre trasmette sul cavo, ascolta anche ciò che effettivamente lo attraversa. Se rileva che le informazioni che viaggiano sul cavo non sono quelle che ha trasmesso, conclude che si è verificata una collisione e interrompe la trasmissione. Gli altri trasmettitori che stavano trasmettendo faranno lo stesso. Ciascuno riprenderà la trasmissione dopo un ritardo casuale che dipende dal singolo trasmettitore. Questa tecnica è chiamata CD (Collision Detect). Il metodo di accesso è quindi chiamato CSMA/CD.
- Indirizzamento a 48 bit. Ogni dispositivo ha un indirizzo, qui denominato indirizzo fisico, che è riportato sulla scheda che lo collega al cavo. Questo indirizzo è chiamato indirizzo Ethernet del dispositivo.
Livello di rete
A questo livello si trovano i protocolli IP, ICMP, ARP e RARP.
Trasmette i pacchetti tra due nodi di rete | |
L'ICMP facilita la comunicazione tra il programma del protocollo IP su una macchina e quello su un'altra macchina. Si tratta quindi di un protocollo di scambio di messaggi all'interno del protocollo IP stesso. | |
mappa l'indirizzo Internet di una macchina al suo indirizzo fisico | |
mappa l'indirizzo fisico di una macchina al suo indirizzo Internet |
Livelli di trasporto/sessione
Questo livello include i seguenti protocolli:
Garantisce la trasmissione affidabile delle informazioni tra due client | |
Garantisce la trasmissione non affidabile di informazioni tra due client |
Livelli applicativo/di presentazione/di sessione
Qui si trovano vari protocolli:
Un emulatore di terminale che consente alla macchina A di connettersi alla macchina B come terminale | |
Consente il trasferimento di file | |
consente il trasferimento di file | |
consente lo scambio di messaggi tra gli utenti della rete | |
converte il nome di un computer nel suo indirizzo Internet | |
Creato da Sun Microsystems, specifica uno standard di rappresentazione dei dati indipendente dalla macchina | |
Definito anch'esso da Sun, è un protocollo di comunicazione tra applicazioni remote, indipendente dal livello di trasporto. Questo protocollo è importante: libera il programmatore dalla necessità di conoscere i dettagli del livello di trasporto e rende le applicazioni portabili. Questo protocollo si basa sul protocollo XDR | |
Definito anch'esso da Sun, questo protocollo permette a una macchina di "vedere" il file system di un'altra macchina. Si basa sul protocollo RPC menzionato sopra |
9.1.4. Come funzionano i protocolli Internet
Le applicazioni sviluppate nell'ambiente TCP/IP utilizzano generalmente diversi protocolli presenti in questo ambiente. Un programma applicativo comunica con il livello più alto dei protocolli. Questo livello trasmette le informazioni al livello sottostante e così via, fino a raggiungere il supporto fisico. Qui, le informazioni vengono trasferite fisicamente alla macchina di destinazione, dove attraverseranno nuovamente gli stessi livelli, questa volta in direzione opposta, fino a raggiungere l'applicazione destinata a ricevere le informazioni inviate. Il diagramma seguente mostra il percorso delle informazioni:
![]() |
Facciamo un esempio: l'applicazione FTP, definita a livello di applicazione, che consente il trasferimento di file tra macchine.
- L'applicazione invia una sequenza di byte da trasmettere al livello di trasporto.
- Il livello di trasporto divide questa sequenza di byte in segmenti TCP e aggiunge il numero di segmento all'inizio di ciascun segmento. I segmenti vengono passati al livello di rete, che è regolato dal protocollo IP.
- Il livello IP crea un pacchetto che incapsula il segmento TCP ricevuto. Nell'intestazione di questo pacchetto inserisce gli indirizzi Internet delle macchine di origine e di destinazione. Determina inoltre l'indirizzo fisico della macchina di destinazione. L'intero pacchetto viene passato al livello di collegamento dati e fisico, ovvero alla scheda di rete che collega la macchina alla rete fisica.
- Lì, il pacchetto IP viene a sua volta incapsulato in un frame fisico e inviato a destinazione tramite il cavo.
- Sulla macchina ricevente, il livello Data Link & Physical fa l'opposto: decapsula il pacchetto IP dal frame fisico e lo passa al livello IP.
- Il livello IP verifica che il pacchetto sia valido: calcola un checksum basato sui bit ricevuti, che deve corrispondere al checksum nell'intestazione del pacchetto. In caso contrario, il pacchetto viene scartato.
- Se il pacchetto viene ritenuto valido, il livello IP decapsula il segmento TCP in esso contenuto e lo passa al livello di trasporto.
- Il livello di trasporto — il livello TCP nel nostro esempio — esamina il numero del segmento per assicurarsi che i segmenti siano nell’ordine corretto.
- Calcola inoltre un checksum per il segmento TCP. Se risulta corretto, il livello TCP invia un riconoscimento alla macchina di origine; in caso contrario, il segmento TCP viene rifiutato.
- Tutto ciò che resta da fare al livello TCP è trasmettere la parte di dati del segmento all’applicazione destinata a riceverla nel livello superiore.
9.1.5. Indirizzamento su Internet
Un nodo di rete può essere un computer, una stampante intelligente, un file server: qualsiasi cosa, in effetti, che possa comunicare utilizzando i protocolli TCP/IP. Ogni nodo ha un indirizzo fisico con un formato che dipende dal tipo di rete. Su una rete Ethernet, l'indirizzo fisico è codificato su 6 byte. Un indirizzo di rete X.25 è un numero di 14 cifre.
L'indirizzo Internet di un nodo è un indirizzo logico: è indipendente dall'hardware e dalla rete utilizzati. Si tratta di un indirizzo di 4 byte che identifica sia una rete locale che un nodo su quella rete. L'indirizzo Internet è solitamente rappresentato da quattro numeri — i valori dei quattro byte — separati da un punto. Pertanto, l'indirizzo del computer Lagaffe presso la Facoltà di Scienze di Angers è 193.49.144.1, mentre quello del computer Liny è 193.49.144.9. Da ciò si deduce che l'indirizzo Internet della rete locale è 193.49.144.0. Su questa rete possono esserci fino a 254 nodi.
Poiché gli indirizzi Internet (indirizzi IP) sono indipendenti dalla rete, un computer sulla rete A può comunicare con un computer sulla rete B senza preoccuparsi del tipo di rete su cui si trova: deve semplicemente conoscere il suo indirizzo IP. Il protocollo IP di ciascuna rete gestisce la conversione tra indirizzo IP e indirizzo fisico, in entrambe le direzioni.
Tutti gli indirizzi IP devono essere univoci. In Francia, l'INRIA è responsabile dell'assegnazione degli indirizzi IP. Infatti, questa organizzazione assegna un indirizzo alla rete locale, ad esempio 193.49.144.0 per la rete della Facoltà di Scienze di Angers. L'amministratore di questa rete può quindi assegnare gli indirizzi IP da 193.49.144.1 a 193.49.144.254 a sua discrezione. Questo indirizzo è generalmente memorizzato in un file specifico su ogni macchina collegata alla rete.
9.1.5.1. Classi di indirizzi IP
Un indirizzo IP è una sequenza di 4 byte, spesso scritta come I1.I2.I3.I4, che in realtà contiene due indirizzi:
- l'indirizzo di rete
- l'indirizzo di un nodo su quella rete
A seconda delle dimensioni di questi due campi, gli indirizzi IP sono suddivisi in 3 classi: classi A, B e C.
Classe A
L'indirizzo IP: I1.I2.I3.I4 ha la forma R1.N1.N2.N3 dove
R1 | è l'indirizzo di rete |
N1.N2.N3 | è l'indirizzo di una macchina su quella rete |
Più precisamente, il formato di un indirizzo IP di Classe A è il seguente:
![]() |
L'indirizzo di rete è lungo 7 bit e l'indirizzo host è lungo 24 bit. Possono quindi esserci 127 reti di Classe A, ciascuna contenente fino a 2²⁴ host.
Classe B
In questo caso, l'indirizzo IP: I1.I2.I3.I4 ha la forma R1.R2.N1.N2 dove
R1.R2 | è l'indirizzo di rete |
N1.N2 | è l'indirizzo di una macchina su quella rete |
Più precisamente, il formato di un indirizzo IP di classe B è il seguente:
![]() |
L'indirizzo di rete è di 2 byte (esattamente 14 bit), così come l'indirizzo del nodo. Possono quindi esistere 2¹⁴ reti di Classe B, ciascuna contenente fino a 2¹⁶ nodi.
Classe C
In questa classe, l'indirizzo IP: I1.I2.I3.I4 ha la forma R1.R2.R3.N1 dove
R1.R2.R3 | è l'indirizzo di rete |
N1 | è l'indirizzo di una macchina su quella rete |
Più precisamente, il formato di un indirizzo IP di Classe C è il seguente:
![]() |
L'indirizzo di rete occupa 3 byte (meno 3 bit) e l'indirizzo host occupa 1 byte. Possono quindi esserci 2²¹ reti di Classe C, ciascuna contenente fino a 256 host.
Poiché l'indirizzo della macchina Lagaffe presso la Facoltà di Scienze di Angers è 193.49.144.1, vediamo che il byte più significativo è 193, che in binario è 11000001. Possiamo dedurre che la rete è una rete di Classe C.
Indirizzi riservati
- Alcuni indirizzi IP sono indirizzi di rete piuttosto che indirizzi di nodo all'interno della rete. Si tratta di quelli in cui l'indirizzo del nodo è impostato su 0. Pertanto, l'indirizzo 193.49.144.0 è l'indirizzo IP della rete della Facoltà di Scienze di Angers. Di conseguenza, nessun nodo in una rete può avere l'indirizzo zero.
- Quando l'indirizzo del nodo in un indirizzo IP è composto interamente da 1, si tratta di un indirizzo di broadcast: questo indirizzo si riferisce a tutti i nodi della rete.
- In una rete di Classe C, che teoricamente consente 2⁸ = 256 nodi, se rimuoviamo i due indirizzi proibiti, ci rimangono solo 254 indirizzi autorizzati.
9.1.5.2. Protocolli di conversione tra indirizzi Internet e indirizzi fisici
Abbiamo visto che quando i dati vengono trasmessi da una macchina all'altra, vengono incapsulati in pacchetti mentre passano attraverso il livello IP. Questi pacchetti hanno la seguente forma:
![]() |
Il pacchetto IP contiene quindi gli indirizzi Internet delle macchine di origine e di destinazione. Quando questo pacchetto viene trasmesso al livello responsabile dell'invio sulla rete fisica, vengono aggiunte ulteriori informazioni per formare il frame fisico che verrà infine inviato sulla rete. Ad esempio, il formato di un frame su una rete Ethernet è il seguente:
![]() |
Nel frame finale sono presenti gli indirizzi fisici delle macchine di origine e di destinazione. Come vengono ottenuti?
Il computer mittente, conoscendo l'indirizzo IP del computer con cui desidera comunicare, ottiene l'indirizzo fisico di quest'ultimo utilizzando un protocollo specifico chiamato ARP (Address Resolution Protocol).
- Invia un tipo speciale di pacchetto chiamato pacchetto ARP contenente l’indirizzo IP del computer di cui si sta cercando l’indirizzo fisico. Nel pacchetto include anche il proprio indirizzo IP e il proprio indirizzo fisico.
- Questo pacchetto viene inviato a tutti i nodi della rete.
- Questi nodi riconoscono la natura speciale del pacchetto. Il nodo che riconosce il proprio indirizzo IP nel pacchetto risponde inviando il proprio indirizzo fisico al mittente del pacchetto. Come può farlo? Ha trovato gli indirizzi IP e fisici del mittente nel pacchetto.
- Il mittente riceve così l'indirizzo fisico che stava cercando. Lo memorizza in modo da poterlo utilizzare in seguito se altri pacchetti devono essere inviati allo stesso destinatario.
L'indirizzo IP di una macchina è normalmente memorizzato in uno dei suoi file di configurazione, che può consultare per recuperarlo. Questo indirizzo può essere modificato: basta modificare il file. L'indirizzo fisico, invece, è memorizzato nella memoria della scheda di rete e non può essere modificato.
Quando un amministratore desidera riorganizzare la rete, potrebbe dover modificare gli indirizzi IP di tutti i nodi e quindi modificare i vari file di configurazione per ciascun nodo. Ciò può risultare noioso e soggetto a errori se le macchine sono numerose. Un metodo consiste nel non assegnare un indirizzo IP alle macchine: invece, viene inserito un codice speciale nel file in cui la macchina troverebbe normalmente il proprio indirizzo IP. Una volta scoperto di non avere un indirizzo IP, la macchina ne richiede uno utilizzando un protocollo chiamato RARP (Reverse Address Resolution Protocol). Quindi invia un pacchetto speciale chiamato pacchetto RARP sulla rete — simile al precedente pacchetto ARP — contenente il proprio indirizzo fisico. Questo pacchetto viene inviato a tutti i nodi che riconoscono un pacchetto RARP. Uno di essi, chiamato server RARP, mantiene un file contenente le mappature indirizzo fisico <--> indirizzo IP per tutti i nodi. Risponde quindi al mittente del pacchetto RARP inviando il proprio indirizzo IP. Un amministratore che desideri riconfigurare la propria rete deve quindi solo modificare il file di mappatura del server RARP. Il server RARP deve normalmente avere un indirizzo IP fisso che deve essere in grado di conoscere senza dover utilizzare il protocollo RARP stesso.
9.1.6. Il livello di rete, noto come livello del protocollo Internet (IP)
L'IP (Internet Protocol) definisce il formato che i pacchetti devono assumere e come devono essere gestiti durante la trasmissione o la ricezione. Questo specifico tipo di pacchetto è chiamato datagramma IP. Ne abbiamo già parlato:
![]() |
Il punto importante è che, oltre ai dati da trasmettere, il datagramma IP contiene gli indirizzi Internet delle macchine di origine e di destinazione. In questo modo, la macchina ricevente sa chi le sta inviando un messaggio.
A differenza di un frame di rete, la cui lunghezza è determinata dalle caratteristiche fisiche della rete su cui viaggia, la lunghezza del datagramma IP è fissata dal software e sarà quindi la stessa su reti fisiche diverse. Abbiamo visto che, man mano che si scende dal livello di rete al livello fisico, il datagramma IP viene incapsulato all'interno di un frame fisico. Abbiamo fornito l'esempio del frame fisico di una rete Ethernet:
![]() |
I frame fisici viaggiano da un nodo all'altro verso la loro destinazione, che potrebbe non trovarsi sulla stessa rete fisica della macchina mittente. Il pacchetto IP può quindi essere incapsulato in successione in diversi frame fisici nei nodi che collegano due reti di tipo diverso. È anche possibile che il pacchetto IP sia troppo grande per essere incapsulato in un singolo frame fisico. Il software IP sul nodo in cui si verifica questo problema suddivide quindi il pacchetto IP in frammenti secondo regole specifiche, ciascuno dei quali viene poi inviato sulla rete fisica. Essi verranno riassemblati solo alla loro destinazione finale.
9.1.6.1. Routing
L'instradamento è il metodo utilizzato per indirizzare i pacchetti IP verso la loro destinazione. Esistono due metodi: l'instradamento diretto e l'instradamento indiretto.
Routing diretto
Il routing diretto si riferisce alla trasmissione di un pacchetto IP direttamente dal mittente al destinatario all'interno della stessa rete:
- Il computer che invia un datagramma IP possiede l’indirizzo IP del destinatario.
- Ottiene l'indirizzo fisico del destinatario tramite il protocollo ARP o dalle proprie tabelle, se tale indirizzo è già stato ottenuto.
- Invia il pacchetto sulla rete a quell'indirizzo fisico.
Routing indiretto
Il routing indiretto si riferisce alla trasmissione di un pacchetto IP verso una destinazione situata su una rete diversa da quella a cui appartiene il mittente. In questo caso, le parti relative all'indirizzo di rete degli indirizzi IP delle macchine di origine e di destinazione sono diverse. La macchina di origine lo riconosce. Invia quindi il pacchetto a un nodo speciale chiamato router, un nodo che collega una rete locale ad altre reti e il cui indirizzo IP trova nelle proprie tabelle — un indirizzo inizialmente ottenuto da un file, dalla memoria permanente o tramite informazioni che circolano sulla rete.
Un router è collegato a due reti e possiede un indirizzo IP all'interno di entrambe queste reti.
![]() |
Nel nostro esempio sopra:
- La rete n. 1 ha l'indirizzo Internet 193.49.144.0 e la rete n. 2 ha l'indirizzo 193.49.145.0.
- All'interno della rete n. 1, il router ha l'indirizzo 193.49.144.6 e l'indirizzo 193.49.145.3 all'interno della rete n. 2.
Il ruolo del router è quello di prendere il pacchetto IP che riceve, contenuto in un frame fisico tipico della rete n. 1, e inserirlo in un frame fisico in grado di viaggiare sulla rete n. 2. Se l'indirizzo IP della destinazione del pacchetto si trova all'interno della rete n. 2, il router invierà il pacchetto direttamente a essa; altrimenti, lo invierà a un altro router, collegando la rete n. 2 alla rete n. 3, e così via.
9.1.6.2. Messaggi di errore e di controllo
Sempre all'interno del livello di rete, allo stesso livello del protocollo IP, si trova l'ICMP (Internet Control Message Protocol). Viene utilizzato per inviare messaggi relativi al funzionamento interno della rete: nodi guasti, congestione a un router, ecc. I messaggi ICMP sono incapsulati in pacchetti IP e inviati sulla rete. I livelli IP dei vari nodi intraprendono le azioni appropriate in base ai messaggi ICMP che ricevono. Pertanto, un'applicazione non vede mai questi problemi specifici della rete. Un nodo utilizzerà le informazioni ICMP per aggiornare le proprie tabelle di routing.
9.1.7. Il livello di trasporto: i protocolli UDP e TCP
9.1.7.1. Il protocollo UDP: User Datagram Protocol
Il protocollo UDP consente uno scambio di dati inaffidabile tra due punti, il che significa che la consegna corretta di un pacchetto alla sua destinazione non è garantita. L'applicazione, se lo desidera, può gestire questo aspetto autonomamente, ad esempio attendendo un riconoscimento dopo l'invio di un messaggio prima di inviare quello successivo.
Finora, a livello di rete, abbiamo parlato degli indirizzi IP delle macchine. Tuttavia, su una singola macchina possono coesistere contemporaneamente diversi processi, tutti in grado di comunicare tra loro. Pertanto, quando si invia un messaggio, è necessario specificare non solo l'indirizzo IP del computer di destinazione, ma anche il "nome" del processo di destinazione. Questo nome è in realtà un numero, chiamato numero di porta. Alcuni numeri sono riservati alle applicazioni standard: la porta 69 per l'applicazione TFTP (Trivial File Transfer Protocol), ad esempio. I pacchetti gestiti dal protocollo UDP sono chiamati anche datagrammi. Hanno la seguente forma:
![]() |
Questi datagrammi saranno incapsulati in pacchetti IP, quindi in frame fisici.
9.1.7.2. Il protocollo TCP: Transmission Control Protocol
Per comunicazioni sicure, il protocollo UDP è insufficiente: lo sviluppatore dell'applicazione deve creare un protocollo autonomamente per garantire che i pacchetti vengano instradati correttamente.
Il TCP (Transmission Control Protocol) evita questi problemi. Le sue caratteristiche sono le seguenti:
- Il processo che desidera inviare dati stabilisce innanzitutto una connessione con il processo che riceverà i dati. Questa connessione viene stabilita tra una porta sulla macchina mittente e una porta sulla macchina ricevente. Viene così creato un percorso virtuale tra le due porte, riservato esclusivamente ai due processi che hanno stabilito la connessione.
- Tutti i pacchetti inviati dal processo di origine seguono questo percorso virtuale e arrivano nell'ordine in cui sono stati inviati, cosa che non era garantita nel protocollo UDP poiché i pacchetti potevano seguire percorsi diversi.
- I dati trasmessi appaiono continui. Il processo mittente invia i dati al proprio ritmo. Questi dati non vengono necessariamente inviati immediatamente: il protocollo TCP attende di averne una quantità sufficiente da inviare. Essi vengono memorizzati in una struttura denominata segmento TCP. Una volta che questo segmento è pieno, viene trasmesso al livello IP, dove viene incapsulato in un pacchetto IP.
- Ogni segmento inviato dal protocollo TCP è numerato. Il protocollo TCP ricevente verifica di ricevere i segmenti in sequenza. Per ogni segmento ricevuto correttamente, invia un riconoscimento al mittente.
- Quando il mittente riceve questo riconoscimento, ne informa il processo di invio. Il processo di invio può così confermare che un segmento è arrivato a destinazione, cosa che non era possibile con il protocollo UDP.
- Se, dopo un certo periodo di tempo, il protocollo TCP che ha inviato un segmento non riceve un riconoscimento, ritrasmette il segmento in questione, garantendo così la qualità del servizio di consegna delle informazioni.
- Il circuito virtuale stabilito tra i due processi in comunicazione è full-duplex: ciò significa che le informazioni possono fluire in entrambe le direzioni. Pertanto, il processo di destinazione può inviare conferme anche mentre il processo di origine continua a inviare informazioni. Ciò consente, ad esempio, al protocollo TCP di origine di inviare più segmenti senza attendere una conferma. Se, dopo un certo periodo di tempo, si rende conto di non aver ricevuto un riconoscimento per un segmento specifico n, riprenderà a inviare segmenti da quel punto.
9.1.8. Il livello applicativo
Al di sopra dei protocolli UDP e TCP, esistono vari protocolli standard:
TELNET
Questo protocollo consente a un utente su una macchina A in rete di connettersi alla macchina B (spesso chiamata macchina host). TELNET emula un cosiddetto terminale universale sulla macchina A. L'utente si comporta quindi come se avesse un terminale collegato alla macchina B. Telnet si basa sul protocollo TCP.
FTP: (File Transfer Protocol)
Questo protocollo consente lo scambio di file tra due macchine remote, nonché operazioni sui file come, ad esempio, la creazione di directory. Si basa sul protocollo TCP.
TFTP: (Trivial File Transfer Protocol)
Questo protocollo è una variante dell'FTP. Si basa sul protocollo UDP ed è meno sofisticato dell'FTP.
DNS: (Domain Name System)
Quando un utente desidera scambiare file con una macchina remota, ad esempio tramite FTP, deve conoscere l'indirizzo Internet di quella macchina. Ad esempio, per utilizzare l'FTP sulla macchina Lagaffe dell'Università di Angers, dovresti eseguire l'FTP come segue: FTP 193.49.144.1
Ciò richiede una directory che associ le macchine agli indirizzi IP. In questa directory, le macchine sarebbero probabilmente designate da nomi simbolici come:
Macchina DPX2/320 presso l'Università di Angers
Macchina Sun presso l'ISERPA ad Angers
È chiaro che sarebbe più comodo riferirsi a una macchina con un nome piuttosto che con il suo indirizzo IP. Ciò solleva la questione dell'unicità dei nomi: ci sono milioni di macchine interconnesse. Si potrebbe immaginare un organismo centralizzato che assegni i nomi. Ciò sarebbe senza dubbio piuttosto macchinoso. Il controllo sui nomi è stato infatti distribuito tra i domini. Ogni dominio è gestito da un'organizzazione generalmente molto snella che ha completa libertà nella scelta dei nomi delle macchine. Pertanto, le macchine in Francia appartengono al dominio fr, gestito dall'Inria di Parigi. Per semplificare ulteriormente le cose, il controllo è distribuito ancora di più: all'interno del dominio fr vengono creati dei sottodomini. Pertanto, l'Università di Angers appartiene al dominio univ-Angers. Il dipartimento che gestisce questo dominio ha completa libertà di denominare le macchine sulla rete dell'Università di Angers. Per ora, questo dominio non è stato suddiviso. Ma in una grande università con molte macchine collegate in rete, potrebbe esserlo.
La macchina DPX2/320 dell'Università di Angers è stata denominata Lagaffe, mentre un PC 486DX50 è stato denominato liny. Come si fa a fare riferimento a queste macchine dall'esterno? Specificando la gerarchia dei domini a cui appartengono. Pertanto, il nome completo della macchina Lagaffe sarà:
Lagaffe.univ-Angers.fr
All'interno dei domini è possibile utilizzare nomi relativi. Pertanto, all'interno del dominio fr e all'esterno del dominio univ-Angers, è possibile fare riferimento alla macchina Lagaffe come
Lagaffe.univ-Angers
Infine, all'interno del dominio univ-Angers, è possibile fare riferimento ad essa semplicemente come
Lagaffe
Un'applicazione può quindi fare riferimento a una macchina tramite il suo nome. In definitiva, però, è comunque necessario ottenere l'indirizzo IP della macchina. Come si fa? Supponiamo di voler comunicare dalla macchina A alla macchina B.
- Se la macchina B appartiene allo stesso dominio della macchina A, il suo indirizzo IP si troverà probabilmente in un file presente sulla macchina A.
- Altrimenti, la macchina A troverà, in un altro file o nello stesso di prima, un elenco di diversi server dei nomi con i loro indirizzi IP. Un server dei nomi è responsabile della mappatura del nome di una macchina al suo indirizzo IP. La macchina A invierà una richiesta speciale al primo server dei nomi del suo elenco, nota come query DNS, che include il nome della macchina ricercata. Se il server interrogato ha quel nome nei propri record, invierà l'indirizzo IP corrispondente al computer A. Altrimenti, il server troverà nei propri file un elenco di server dei nomi che può interrogare. Lo farà quindi. In questo modo, verranno interrogati diversi server dei nomi, non a caso ma in modo da ridurre al minimo il numero di richieste. Se il computer viene finalmente trovato, la risposta verrà inviata al computer A.
XDR: (eXternal Data Representation)
Creato da Sun Microsystems, questo protocollo specifica una rappresentazione dei dati standard e indipendente dalla macchina.
RPC: (Remote Procedure Call)
Definito anch'esso da Sun, si tratta di un protocollo di comunicazione tra applicazioni remote, indipendente dal livello di trasporto. Questo protocollo è importante: solleva il programmatore dalla necessità di conoscere i dettagli del livello di trasporto e rende le applicazioni portabili. Questo protocollo si basa sul protocollo XDR
NFS: Network File System
Definito anch'esso da Sun, questo protocollo permette a una macchina di "vedere" il file system di un'altra macchina. Si basa sul protocollo RPC menzionato sopra.
9.1.9. Conclusione
In questa introduzione abbiamo illustrato alcune delle caratteristiche principali dei protocolli Internet. Per approfondire l'argomento, i lettori possono consultare l'eccellente libro di Douglas Comer:
Titolo | TCP/IP: Architettura, protocolli, applicazioni. |
Autore | Douglas COMER |
Editore | InterEditions |
9.2. Gestione degli indirizzi di rete
Un computer su Internet è identificato in modo univoco da un indirizzo IP (Internet Protocol) nella forma I1.I2.I3.I4, dove I1 è un numero compreso tra 1 e 254. Può anche essere identificato da un nome univoco. Questo nome non è obbligatorio, poiché le applicazioni utilizzano sempre gli indirizzi IP dei computer. Questi nomi servono a semplificare la vita agli utenti. Pertanto, è più semplice utilizzare un browser per richiedere l'URL http://www.ibm.com piuttosto che l'URL http://129.42.17.99, sebbene entrambi i metodi siano possibili. La mappatura tra indirizzo IP e nome del computer è gestita da un servizio Internet distribuito denominato DNS (Domain Name System). La piattaforma .NET fornisce la classe Dns per la gestione degli indirizzi Internet:

La maggior parte dei metodi della classe sono statici. Diamo un'occhiata a quelli che ci interessano:
Restituisce un oggetto IPHostEntry a partire da un indirizzo IP nella forma "I1.I2.I3.I4". Genera un'eccezione se l'indirizzo del computer non viene trovato. | |
restituisce un oggetto IPHostEntry a partire dal nome di un computer. Genera un'eccezione se il nome del computer non viene trovato. | |
restituisce il nome del computer su cui è in esecuzione il programma che esegue questa istruzione |
Gli indirizzi di rete IPHostEntry hanno il seguente formato:
![]() |
Le proprietà che ci interessano:
Elenco degli indirizzi IP di una macchina. Mentre un indirizzo IP identifica una e una sola macchina fisica, una macchina fisica può avere più indirizzi IP. Questo accade se dispone di più schede di rete che la collegano a reti diverse. | |
Elenco degli alias di una macchina, che può essere identificata da un nome principale e da alias | |
Il nome del computer, se ne possiede uno |
Dalla classe IPAddress, useremo il seguente costruttore, le proprietà e i metodi:

Un oggetto [IPAddress] può essere convertito nella stringa I1.I2.I3.I4 utilizzando il metodo ToString(). Viceversa, è possibile ottenere un oggetto IPAddress dalla stringa I1.I2.I3.I4 utilizzando il metodo statico IPAddress.Parse("I1.I2.I3.I4"). Si consideri il seguente programma, che visualizza il nome del computer su cui è in esecuzione e fornisce in modo interattivo le mappature tra indirizzo IP e nome del computer:
dos>address1
Machine Locale=tahe
Machine recherchée (fin pour arrêter) : istia.univ-angers.fr
Machine : istia.univ-angers.fr
Adresses IP : 193.49.146.171
Machine recherchée (fin pour arrêter) : 193.49.146.171
Machine : istia.istia.univ-angers.fr
Adresses IP : 193.49.146.171
Alias : 171.146.49.193.in-addr.arpa
Machine recherchée (fin pour arrêter) : www.ibm.com
Machine : www.ibm.com
Adresses IP : 129.42.17.99,129.42.18.99,129.42.19.99,129.42.16.99
Machine recherchée (fin pour arrêter) : 129.42.17.99
Machine : www.ibm.com
Adresses IP : 129.42.17.99
Machine recherchée (fin pour arrêter) : x.y.z
Impossible de trouver la machine [x.y.z]
Machine recherchée (fin pour arrêter) : localhost
Machine : tahe
Adresses IP : 127.0.0.1
Machine recherchée (fin pour arrêter) : 127.0.0.1
Machine : tahe
Adresses IP : 127.0.0.1
Machine recherchée (fin pour arrêter) : tahe
Machine : tahe
Adresses IP : 127.0.0.1
Machine recherchée (fin pour arrêter) : fin
Il programma è il seguente:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
Imports System.Net
Imports System.Text.RegularExpressions
' test module
Public Module adresses
Sub Main()
' displays the name of the local machine
' then interactively provides information on network machines
' identified by name or address IP
' local machine
Dim localHost As String = Dns.GetHostName()
Console.Out.WriteLine(("Machine Locale=" + localHost))
' interactive Q&A
Dim machine As String
Dim adresseMachine As IPHostEntry
While True
' enter the name of the machine you are looking for
Console.Out.Write("Machine recherchée (fin pour arrêter) : ")
machine = Console.In.ReadLine().Trim().ToLower()
' finished?
If machine = "fin" Then
Exit While
End If
' address I1.I2.I3.I4 or machine name?
Dim isIPV4 As Boolean = Regex.IsMatch(machine, "^\s*\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\s*$")
' management exception
Try
If isIPV4 Then
adresseMachine = Dns.GetHostByAddress(machine)
Else
adresseMachine = Dns.GetHostByName(machine)
End If
' the name
Console.Out.WriteLine(("Machine : " + adresseMachine.HostName))
' IP addresses
Console.Out.Write(("Adresses IP : " + adresseMachine.AddressList(0).ToString))
Dim i As Integer
For i = 1 To adresseMachine.AddressList.Length - 1
Console.Out.Write(("," + adresseMachine.AddressList(i).ToString))
Next i
Console.Out.WriteLine()
' aliases
If adresseMachine.Aliases.Length <> 0 Then
Console.Out.Write(("Alias : " + adresseMachine.Aliases(0)))
For i = 1 To adresseMachine.Aliases.Length - 1
Console.Out.Write(("," + adresseMachine.Aliases(i)))
Next i
Console.Out.WriteLine()
End If
Catch
' the machine doesn't exist
Console.Out.WriteLine("Impossible de trouver la machine [" + machine + "]")
End Try
End While
End Sub
End Module
9.3. Programmazione TCP/IP
9.3.1. Informazioni generali
Si consideri la comunicazione tra due macchine remote A e B:
![]() |
Quando un'applicazione AppA sul computer A vuole comunicare con un'applicazione AppB sul computer B su Internet, deve conoscere diverse cose:
- l'indirizzo IP o il nome host del computer B
- il numero di porta utilizzato dall'applicazione AppB. Infatti, il computer B può ospitare molte applicazioni in esecuzione su Internet. Quando riceve informazioni dalla rete, deve sapere a quale applicazione sono destinate. Le applicazioni sul computer B accedono alla rete tramite interfacce note anche come porte di comunicazione. Queste informazioni sono contenute nel pacchetto ricevuto dal computer B in modo che possa essere consegnato all'applicazione corretta.
- I protocolli di comunicazione compresi dal computer B. Nel nostro studio, useremo solo i protocolli TCP-IP.
- Il protocollo di comunicazione accettato dall'applicazione AppB. Infatti, le macchine A e B "parleranno" tra loro. Ciò che diranno sarà incapsulato all'interno dei protocolli TCP/IP. Tuttavia, quando, alla fine della catena, l'applicazione AppB riceve le informazioni inviate dall'applicazione AppA, deve essere in grado di interpretarle. Ciò è analogo alla situazione in cui due persone, A e B, comunicano per telefono: la loro conversazione è veicolata dal telefono. Il parlato viene codificato come segnali dal telefono A, trasmesso sulle linee telefoniche e arriva al telefono B per essere decodificato. La persona B sente quindi le parole. È qui che entra in gioco il concetto di protocollo di comunicazione: se A parla francese e B non capisce quella lingua, A e B non saranno in grado di comunicare efficacemente.
Pertanto, le due applicazioni che comunicano devono concordare il tipo di protocollo di comunicazione che useranno. Ad esempio, la comunicazione con un servizio FTP non è la stessa di quella con un servizio POP: questi due servizi non accettano gli stessi comandi. Hanno protocolli di comunicazione diversi.
9.3.2. Caratteristiche del protocollo TCP
Qui esamineremo solo le comunicazioni di rete che utilizzano il protocollo di trasporto TCP. Vediamo le sue caratteristiche:
- Il processo che desidera trasmettere dati stabilisce innanzitutto una connessione con il processo che riceverà le informazioni che sta per trasmettere. Questa connessione viene stabilita tra una porta sulla macchina mittente e una porta sulla macchina ricevente. Viene così creato un percorso virtuale tra le due porte, che sarà riservato esclusivamente ai due processi che hanno stabilito la connessione.
- Tutti i pacchetti inviati dal processo di origine seguono questo percorso virtuale e arrivano nell'ordine in cui sono stati inviati
- I dati trasmessi appaiono continui. Il processo mittente invia i dati al proprio ritmo. Questi dati non vengono necessariamente inviati immediatamente: il protocollo TCP attende di averne una quantità sufficiente da inviare. Essi vengono memorizzati in una struttura chiamata segmento TCP. Una volta che questo segmento è pieno, viene trasmesso al livello IP, dove viene incapsulato in un pacchetto IP.
- Ogni segmento inviato dal protocollo TCP è numerato. Il protocollo TCP ricevente verifica di ricevere i segmenti in sequenza. Per ogni segmento ricevuto correttamente, invia un riconoscimento al mittente.
- Quando il mittente riceve questo riconoscimento, ne informa il processo di invio. Il processo di invio può così confermare che un segmento è arrivato a destinazione.
- Se, dopo un certo periodo di tempo, il protocollo TCP che ha inviato un segmento non riceve un riconoscimento, ritrasmette il segmento in questione, garantendo così la qualità del servizio di consegna delle informazioni.
- Il circuito virtuale stabilito tra i due processi in comunicazione è full-duplex: ciò significa che le informazioni possono fluire in entrambe le direzioni. Pertanto, il processo di destinazione può inviare conferme anche mentre il processo di origine continua a inviare informazioni. Ciò consente, ad esempio, al protocollo TCP di origine di inviare più segmenti senza attendere una conferma. Se, dopo un certo periodo di tempo, si rende conto di non aver ricevuto un riconoscimento per un segmento specifico n. n, riprenderà a inviare segmenti da quel punto.
9.3.3. Il rapporto client-server
La comunicazione su Internet è spesso asimmetrica: la macchina A avvia una connessione per richiedere un servizio alla macchina B, specificando che desidera aprire una connessione con il servizio SB1 sulla macchina B. La macchina B accetta o rifiuta. Se accetta, la macchina A può inviare le sue richieste al servizio SB1. Queste richieste devono essere conformi al protocollo di comunicazione compreso dal servizio SB1. Si instaura così un dialogo richiesta-risposta tra la macchina A, detta macchina client, e la macchina B, detta macchina server. Uno dei due partner chiuderà la connessione.
9.3.4. Architettura client
L'architettura di un programma di rete che richiede i servizi di un'applicazione server sarà la seguente:
ouvrir la connexion avec le service SB1 de la machine B
si réussite alors
tant que ce n'est pas fini
préparer une demande
l'émettre vers la machine B
attendre et récupérer la réponse
la traiter
fin tant que
finsi
9.3.5. Architettura del server
L'architettura di un programma che offre servizi sarà la seguente:
ouvrir le service sur la machine locale
tant que le service est ouvert
se mettre à l'écoute des demandes de connexion sur un port dit port d'écoute
lorsqu'il y a une demande, la faire traiter par une autre tâche sur un autre port dit port de service
fin tant que
Il programma server gestisce la richiesta di connessione iniziale di un client in modo diverso dalle sue successive richieste di servizio. Il programma non fornisce il servizio stesso. Se lo facesse, non sarebbe più in ascolto delle richieste di connessione mentre il servizio è in corso e i client non verrebbero serviti. Proceda quindi in modo diverso: non appena una richiesta di connessione viene ricevuta sulla porta di ascolto e quindi accettata, il server crea un'attività responsabile di fornire il servizio richiesto dal client. Questo servizio viene fornito su un'altra porta della macchina server chiamata porta di servizio. Ciò consente di servire più client contemporaneamente. Un'attività di servizio avrà la seguente struttura:
tant que le service n'a pas été rendu totalement
attendre une demande sur le port de service
lorsqu'il y en a une, élaborer la réponse
transmettre la réponse via le port de service
fin tant que
libérer le port de service
9.3.6. La classe TcpClient
La classe TcpClient è la classe appropriata per rappresentare un client di servizio TCP. È definita come segue:

I costruttori, i metodi e le proprietà di nostro interesse sono i seguenti:
crea una connessione TCP con il server in esecuzione sulla porta specificata (port) della macchina specificata (hostname). Ad esempio, new TcpClient("istia.univ-angers.fr", 80) per connettersi alla porta 80 della macchina istia.univ-angers.fr | |
chiude la connessione al server TCP | |
Ottiene un NetworkStream per la lettura e la scrittura sul server. Questo flusso consente la comunicazione client-server. |
9.3.7. La classe NetworkStream
La classe NetworkStream rappresenta il flusso di rete tra il client e il server. La classe è definita come segue:

La classe NetworkStream deriva dalla classe Stream. Molte applicazioni client-server scambiano righe di testo terminate dai caratteri di fine riga "\r\n". Pertanto, è utile utilizzare gli oggetti StreamReader e StreamWriter per leggere e scrivere queste righe nel flusso di rete. Quando due macchine comunicano, è presente un oggetto TcpClient a ciascuna estremità della connessione. Il metodo GetStream di questo oggetto fornisce l'accesso al flusso di rete (NetworkStream) che collega le due macchine. Pertanto, se una macchina M1 ha stabilito una connessione con una macchina M2 utilizzando un oggetto TcpClient client1 e stanno scambiando righe di testo, può creare i propri flussi di lettura e scrittura come segue:
Dim in1 as StreamReader=new StreamReader(client1.GetStream())
Dim out1 as StreamWriter=new StreamWriter(client1.GetStream())
out1.AutoFlush=true
L'istruzione
significa che il flusso di scrittura proveniente da client1 non passerà attraverso un buffer intermedio, ma andrà direttamente alla rete. Questo punto è importante. Generalmente, quando client1 invia una riga di testo al suo partner, si aspetta una risposta. Questa risposta non arriverà mai se la riga è stata effettivamente bufferizzata sulla macchina M1 e non è mai stata inviata. Per inviare una riga di testo alla macchina M2, scriviamo:
Per leggere la risposta da M2, scriviamo:
9.3.8. Architettura di base di un client Internet
Ora disponiamo degli elementi per scrivere l'architettura di base di un client Internet:
Dim client As TcpClient = Nothing ' the customer
Dim [IN] As StreamReader = Nothing ' the customer's reading flow
Dim OUT As StreamWriter = Nothing ' the customer's writing flow
Dim demande As String = Nothing ' customer request
Dim réponse As String = Nothing ' server response
Try
' connect to the service running on port P of machine M
client = New TcpClient(nomServeur, port)
' create customer input/output flows TCP
[IN] = New StreamReader(client.GetStream())
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
' request-response loop
While True
' preparing the application
demande = ...
' we send it to the server
OUT.WriteLine(demande)
' we read the server response
réponse = [IN].ReadLine()
' the answer is processed
...
End While
' it's over
client.Close()
Catch ex As Exception
' we handle the exception
...
End Try
9.3.9. La classe TcpListener
La classe TcpListener è la classe appropriata per rappresentare un servizio TCP. È definita come segue:

I costruttori, i metodi e le proprietà di nostro interesse sono i seguenti:
crea un servizio TCP che ascolterà le richieste dei client su una porta passata come parametro (port), nota come porta di ascolto della macchina locale con indirizzo IP localaddr. | |
accetta una richiesta del client. Restituisce un oggetto TcpClient associato a un'altra porta, denominata porta di servizio. | |
Avvia l'ascolto delle richieste dei client | |
interrompe l'ascolto delle richieste dei client |
9.3.10. Architettura di base di un server Internet
Da quanto visto in precedenza, possiamo dedurre la struttura di base di un server:
' we create the listening service
Dim ecoute As TcpListener = Nothing
Dim port As Integer = ...
Try
' create the service
ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), port)
' launch it
ecoute.Start()
' service loop
Dim liaisonClient As TcpClient = Nothing
While not fini
' waiting for a customer
liaisonClient = ecoute.AcceptTcpClient()
' the service is provided by another task
Dim tache As Thread = New Thread(New ThreadStart(AddressOf [méthode]))
tache.Start()
End While
Catch ex As Exception
' we report the error
....
End Try
' end of service
ecoute.Stop()
La classe Service è un thread che potrebbe apparire così:
Public Class Service
Private liaisonClient As TcpClient ' customer liaison
Private [IN] As StreamReader ' iNPUTS
Private OUT As StreamWriter ' output flow
' manufacturer
Public Sub New(ByVal liaisonClient As TcpClient, ...)
Me.liaisonClient = liaisonClient
...
End Sub
' run method
Public Sub Run()
' renders service to the customer
Try
' iNPUTS
[IN] = New StreamReader(liaisonClient.GetStream())
' output flow
OUT = New StreamWriter(liaisonClient.GetStream())
OUT.AutoFlush = True
' loop read request/write response
Dim demande As String = Nothing
Dim reponse As String = Nothing
demande = [IN].ReadLine
While Not (demande Is Nothing)
' we process demand
...
' we send the answer
reponse = "[" + demande + "]"
OUT.WriteLine(reponse)
' following request
demande = [IN].ReadLine
End While
' end link
liaisonClient.Close()
Catch e As Exception
...
End Try
' end of service
End Sub
9.4. Esempi
9.4.1. Server Echo
Scriveremo un server echo che verrà avviato da una finestra DOS utilizzando il comando:
Il server funziona sulla porta passata come parametro. Si limita a rispedire al client la richiesta che il client gli ha inviato. Il programma è il seguente:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System.Net.Sockets
Imports System.Net
Imports System
Imports System.IO
Imports System.Threading
Imports Microsoft.VisualBasic
' call: serveurEcho port
' echo server
' returns the line sent to the customer
Public Class serveurEcho
Private Shared syntaxe As String = "Syntaxe : serveurEcho port"
' main program
Public Shared Sub Main(ByVal args() As String)
' is there an argument
If args.Length <> 1 Then
erreur(syntaxe, 1)
End If
' this argument must be integer >0
Dim port As Integer = 0
Dim erreurPort As Boolean = False
Dim E As Exception = Nothing
Try
port = Integer.Parse(args(0))
Catch ex As Exception
E = ex
erreurPort = True
End Try
erreurPort = erreurPort Or port <= 0
If erreurPort Then
erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2)
End If
' we create the listening service
Dim ecoute As TcpListener = Nothing
Dim nbClients As Integer = 0 ' of customers handled
Try
' create the service
ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), port)
' launch it
ecoute.Start()
' follow-up
Console.Out.WriteLine(("Serveur d'écho lancé sur le port " & port))
Console.Out.WriteLine(ecoute.LocalEndpoint)
' service loop
Dim liaisonClient As TcpClient = Nothing
While True
' infinite loop - will be stopped by Ctrl-C
' waiting for a customer
liaisonClient = ecoute.AcceptTcpClient()
' the service is provided by another task
nbClients += 1
Dim tache As Thread = New Thread(New ThreadStart(AddressOf New traiteClientEcho(liaisonClient, nbClients).Run))
tache.Start()
End While
' back to listening to requests
Catch ex As Exception
' we report the error
erreur("L'erreur suivante s'est produite : " + ex.Message, 3)
End Try
' end of service
ecoute.Stop()
End Sub
' error display
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' error display
System.Console.Error.WriteLine(msg)
' stop with error
Environment.Exit(exitCode)
End Sub
End Class
' -------------------------------------------------------
' provides service to an echo server client
Public Class traiteClientEcho
Private liaisonClient As TcpClient ' customer liaison
Private numClient As Integer ' customer no
Private [IN] As StreamReader ' iNPUTS
Private OUT As StreamWriter ' output flow
' manufacturer
Public Sub New(ByVal liaisonClient As TcpClient, ByVal numClient As Integer)
Me.liaisonClient = liaisonClient
Me.numClient = numClient
End Sub
' run method
Public Sub Run()
' renders service to the customer
Console.Out.WriteLine(("Début de service au client " & numClient))
Try
' iNPUTS
[IN] = New StreamReader(liaisonClient.GetStream())
' output flow
OUT = New StreamWriter(liaisonClient.GetStream())
OUT.AutoFlush = True
' loop read request/write response
Dim demande As String = Nothing
Dim reponse As String = Nothing
demande = [IN].ReadLine
While Not (demande Is Nothing)
' follow-up
Console.Out.WriteLine(("Client " & numClient & " : " & demande))
' the service stops when the client sends an end-of-file marker
reponse = "[" + demande + "]"
OUT.WriteLine(reponse)
' service stops when client sends "end
If demande.Trim().ToLower() = "fin" Then
Exit While
End If
' following request
demande = [IN].ReadLine
End While
' end link
liaisonClient.Close()
Catch e As Exception
erreur("Erreur lors de la fermeture de la liaison client (" + e.ToString + ")", 2)
End Try
' end of service
Console.Out.WriteLine(("Fin de service au client " & numClient))
End Sub
' error display
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' error display
System.Console.Error.WriteLine(msg)
' stop with error
Environment.Exit(exitCode)
End Sub
End Class
La struttura del server è conforme all'architettura generale dei server TCP.
9.4.2. Un client per il server echo
Ora scriveremo un client per il server precedente. Si chiamerà come segue:
Si connette alla macchina servername sulla porta port e poi invia righe di testo al server, che le ripete.
' options
Option Explicit On
Option Strict On
' namespaces
Imports System.Net.Sockets
Imports System.Net
Imports System
Imports System.IO
Imports System.Threading
Imports Microsoft.VisualBasic
Public Class clientEcho
' connects to an echo server
' any line typed on the keyboard is then received as an echo
Public Shared Sub Main(ByVal args() As String)
' syntax
Const syntaxe As String = "pg machine port"
' number of arguments
If args.Length <> 2 Then
erreur(syntaxe, 1)
End If
' note the server name
Dim nomServeur As String = args(0)
' port must be integer >0
Dim port As Integer = 0
Dim erreurPort As Boolean = False
Dim E As Exception = Nothing
Try
port = Integer.Parse(args(1))
Catch ex As Exception
E = ex
erreurPort = True
End Try
erreurPort = erreurPort Or port <= 0
If erreurPort Then
erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2)
End If
' we can work
Dim client As TcpClient = Nothing ' the customer
Dim [IN] As StreamReader = Nothing ' the customer's reading flow
Dim OUT As StreamWriter = Nothing ' the customer's writing flow
Dim demande As String = Nothing ' customer request
Dim réponse As String = Nothing ' server response
Try
' connect to the service running on port P of machine M
client = New TcpClient(nomServeur, port)
' create customer input/output flows TCP
[IN] = New StreamReader(client.GetStream())
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
' request-response loop
While True
' demand comes from the keyboard
Console.Out.Write("demande (fin pour arrêter) : ")
demande = Console.In.ReadLine()
' we send it to the server
OUT.WriteLine(demande)
' we read the server response
réponse = [IN].ReadLine()
' the answer is processed
Console.Out.WriteLine(("Réponse : " + réponse))
' finished?
If demande.Trim().ToLower() = "fin" Then
Exit While
End If
End While
' it's over
client.Close()
Catch ex As Exception
' we handle the exception
erreur(ex.Message, 3)
End Try
End Sub
' error display
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' error display
System.Console.Error.WriteLine(msg)
' stop with error
Environment.Exit(exitCode)
End Sub
End Class
La struttura di questo client è conforme all'architettura generale dei client TCP. Di seguito sono riportati i risultati ottenuti con la seguente configurazione:
- Il server è in esecuzione sulla porta 100 in una finestra DOS
- Sulla stessa macchina, vengono avviati due client in altre due finestre DOS
Nella finestra del Client 1, otteniamo i seguenti risultati:
dos>clientEcho localhost 100
demande (fin pour arrêter) : ligne1
Réponse : [ligne1]
demande (fin pour arrêter) : ligne1B
Réponse : [ligne1B]
demande (fin pour arrêter) : ligne1C
Réponse : [ligne1C]
demande (fin pour arrêter) : fin
Réponse : [fin]
Nel client 2:
dos>clientEcho localhost 100
demande (fin pour arrêter) : ligne2A
Réponse : [ligne2A]
demande (fin pour arrêter) : ligne2B
Réponse : [ligne2B]
demande (fin pour arrêter) : fin
Réponse : [fin]
Sul lato server:
dos>serveurEcho 100
Serveur d'écho lancé sur le port 100
0.0.0.0:100
Début de service au client 1
Client 1 : ligne1
Début de service au client 2
Client 2 : ligne2A
Client 2 : ligne2B
Client 1 : ligne1B
Client 1 : ligne1C
Client 2 : fin
Fin de service au client 2
Client 1 : fin
Fin de service au client 1
^C
Si noti che il server è stato effettivamente in grado di servire due clienti contemporaneamente.
9.4.3. Un client TCP generico
Molti servizi creati agli albori di Internet funzionano secondo il modello del server echo discusso in precedenza: gli scambi client-server consistono nello scambio di righe di testo. Scriveremo un client TCP generico che verrà avviato come segue: cltgen server porta
Questo client TCP si connetterà alla porta port sul server server. Una volta connesso, creerà due thread:
- un thread responsabile della lettura dei comandi digitati sulla tastiera e del loro invio al server
- un thread responsabile della lettura delle risposte del server e della loro visualizzazione sullo schermo
Perché due thread quando ciò non era necessario nell'applicazione precedente? In quell'applicazione, il protocollo di comunicazione era fisso: il client inviava una singola riga e il server rispondeva con una singola riga. Ogni servizio ha il proprio protocollo specifico e ci imbattiamo anche nelle seguenti situazioni:
- il client deve inviare diverse righe di testo prima di ricevere una risposta
- la risposta di un server può contenere più righe di testo
Pertanto, il ciclo che invia una singola riga al server e ne riceve una dal server non è sempre adatto. Creeremo quindi due cicli separati:
- un ciclo per leggere i comandi digitati sulla tastiera da inviare al server. L'utente segnalerà la fine dei comandi con la parola chiave "fin".
- un ciclo per ricevere e visualizzare le risposte del server. Si tratterà di un ciclo infinito che verrà interrotto solo dalla chiusura della connessione di rete da parte del server o dalla digitazione del comando "end" da parte dell'utente sulla tastiera.
Per avere questi due cicli separati, abbiamo bisogno di due thread indipendenti. Vediamo un esempio di esecuzione in cui il nostro client TCP generico si connette a un servizio SMTP (Simple Mail Transfer Protocol). Questo servizio è responsabile dell'inoltro delle e-mail ai destinatari. Opera sulla porta 25 e utilizza un protocollo di scambio basato su testo.
dos>cltgen istia.univ-angers.fr 25
Commandes :
<-- 220 istia.univ-angers.fr ESMTP Sendmail 8.11.6/8.9.3; Mon, 13 May 2002 08:37:26 +0200
help
<-- 502 5.3.0 Sendmail 8.11.6 -- HELP not implemented
mail from: machin@univ-angers.fr
<-- 250 2.1.0 machin@univ-angers.fr... Sender ok
rcpt to: serge.tahe@istia.univ-angers.fr
<-- 250 2.1.5 serge.tahe@istia.univ-angers.fr... Recipient ok
data
<-- 354 Enter mail, end with "." on a line by itself
Subject: test
ligne1
ligne2
ligne3
.
<-- 250 2.0.0 g4D6bks25951 Message accepted for delivery
quit
<-- 221 2.0.0 istia.univ-angers.fr closing connection
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]
Commentiamo questi scambi client-server:
- Il servizio SMTP invia un messaggio di benvenuto quando un client si connette ad esso:
- Alcuni servizi dispongono di un comando "help" che fornisce informazioni sui comandi disponibili per quel servizio. In questo caso non è così. I comandi SMTP utilizzati nell'esempio sono i seguenti:
- mail from: mittente, per specificare l'indirizzo e-mail del mittente
- rcpt to: destinatario, per specificare l'indirizzo e-mail del destinatario del messaggio. Se ci sono più destinatari, il comando rcpt to: viene ripetuto tante volte quante necessarie per ciascun destinatario.
- data che segnala al server SMTP che sta per essere inviato un messaggio. Come indicato nella risposta del server, questi dati consistono in una serie di righe che terminano con una riga contenente solo un punto. Un messaggio può avere intestazioni separate dal corpo del messaggio da una riga vuota. Nel nostro esempio, abbiamo incluso un oggetto utilizzando la parola chiave Subject:
- Una volta inviato il messaggio, è possibile comunicare al server che si è terminato utilizzando il comando quit. Il server chiude quindi la connessione di rete. Il thread di lettura può rilevare questo evento e arrestarsi.
- L'utente digita quindi "end" sulla tastiera per arrestare anche il thread che legge i comandi digitati sulla tastiera.
Se controlliamo l'e-mail ricevuta, vediamo quanto segue (Outlook):

Si noti che il servizio SMTP non è in grado di rilevare se un mittente sia valido o meno. Pertanto, non ci si può mai fidare del campo "Da" di un messaggio. In questo caso, il mittente machin@univ-angers.fr non esisteva. Questo client TCP generico ci permette di scoprire il protocollo di comunicazione dei servizi Internet e, da lì, costruire classi specializzate per i client di questi servizi. Esploriamo il protocollo di comunicazione del servizio POP (Post Office Protocol), che consente agli utenti di recuperare le proprie e-mail memorizzate su un server. Opera sulla porta 110.
dos>cltgen istia.univ-angers.fr 110
Commandes :
<-- +OK Qpopper (version 4.0.3) at istia.univ-angers.fr starting.
help
<-- -ERR Unknown command: "help".
user st
<-- +OK Password required for st.
pass monpassword
<-- +OK st has 157 visible messages (0 hidden) in 11755927 octets.
list
<-- +OK 157 visible messages (11755927 octets)
<-- 1 892847
<-- 2 171661
...
<-- 156 2843
<-- 157 2796
<-- .
retr 157
<-- +OK 2796 octets
<-- Received: from lagaffe.univ-angers.fr (lagaffe.univ-angers.fr [193.49.144.1])
<-- by istia.univ-angers.fr (8.11.6/8.9.3) with ESMTP id g4D6wZs26600;
<-- Mon, 13 May 2002 08:58:35 +0200
<-- Received: from jaume ([193.49.146.242])
<-- by lagaffe.univ-angers.fr (8.11.1/8.11.2/GeO20000215) with SMTP id g4D6wSd37691;
<-- Mon, 13 May 2002 08:58:28 +0200 (CEST)
...
<-- ------------------------------------------------------------------------
<-- NOC-RENATER2 Tl. : 0800 77 47 95
<-- Fax : (+33) 01 40 78 64 00 , Email : noc-r2@cssi.renater.fr
<-- ------------------------------------------------------------------------
<--
<-- .
quit
<-- +OK Pop server at istia.univ-angers.fr signing off.
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]
I comandi principali sono i seguenti:
- user login, dove si inserisce il proprio login sul server che ospita le e-mail
- pass password, dove si inserisce la password associata al login precedente
- list, per ottenere un elenco dei messaggi nel formato numero, dimensione in byte
- retr i, per leggere il messaggio numero i
- quit, per terminare la sessione.
Ora esploriamo il protocollo di comunicazione tra un client e un server web, che in genere funziona sulla porta 80:
dos>cltgen istia.univ-angers.fr 80
Commandes :
GET /index.html HTTP/1.0
<-- HTTP/1.1 200 OK
<-- Date: Mon, 13 May 2002 07:30:58 GMT
<-- Server: Apache/1.3.12 (Unix) (Red Hat/Linux) PHP/3.0.15 mod_perl/1.21
<-- Last-Modified: Wed, 06 Feb 2002 09:00:58 GMT
<-- ETag: "23432-2bf3-3c60f0ca"
<-- Accept-Ranges: bytes
<-- Content-Length: 11251
<-- Connection: close
<-- Content-Type: text/html
<--
<-- <html>
<--
<-- <head>
<-- <meta http-equiv="Content-Type"
<-- content="text/html; charset=iso-8859-1">
<-- <meta name="GENERATOR" content="Microsoft FrontPage Express 2.0">
<-- <title>Bienvenue a l'ISTIA - Universite d'Angers</title>
<-- </head>
....
<-- face="Verdana"> - Dernire mise jour le <b>10 janvier 2002</b></font></p>
<-- </body>
<-- </html>
<--
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]
Un client web invia i propri comandi al server secondo il seguente schema:
Il server web risponde solo dopo aver ricevuto la riga vuota. In questo esempio, abbiamo utilizzato un solo comando:
che richiede l'URL /index.html dal server e indica che sta utilizzando la versione 1.0 di HTTP. La versione più recente di questo protocollo è la 1.1. L'esempio mostra che il server ha risposto inviando il contenuto del file index.html e poi ha chiuso la connessione, come possiamo vedere dalla lettura della risposta "thread terminating". Prima di inviare il contenuto del file index.html, il server web ha inviato una serie di intestazioni seguite da una riga vuota:
<-- HTTP/1.1 200 OK
<-- Date: Mon, 13 May 2002 07:30:58 GMT
<-- Server: Apache/1.3.12 (Unix) (Red Hat/Linux) PHP/3.0.15 mod_perl/1.21
<-- Last-Modified: Wed, 06 Feb 2002 09:00:58 GMT
<-- ETag: "23432-2bf3-3c60f0ca"
<-- Accept-Ranges: bytes
<-- Content-Length: 11251
<-- Connection: close
<-- Content-Type: text/html
<--
<-- <html>
La riga <html> è la prima riga del file /index.html. Il testo che la precede è denominato "intestazioni HTTP" (HyperText Transfer Protocol). In questa sede non approfondiremo questi dettagli, ma è bene tenere presente che il nostro client generico consente di accedervi, il che può essere utile per comprenderli. Ad esempio, la prima riga:
indica che il server web contattato supporta il protocollo HTTP/1.1 e che ha trovato con successo il file richiesto (200 OK), dove 200 è un codice di risposta HTTP. Le righe
comunicano al client che riceverà 11.251 byte di testo HTML (HyperText Markup Language) e che la connessione verrà chiusa una volta inviati i dati. Ecco quindi un client TCP molto utile. In realtà, questo client esiste già su alcune macchine con il nome di telnet, ma è stato interessante scriverlo da soli. Il programma generico del client TCP è il seguente:
' namespaces
Imports System
Imports System.Net.Sockets
Imports System.IO
Imports System.Threading
Imports Microsoft.VisualBasic
' the class
Public Class clientTcpGénérique
' receives the characteristics of a service as a parameter in the form
' server port
' connects to the service
' creates a thread to read keyboard commands
' these will be sent to the
' creates a thread to read server responses
' these will be displayed on the screen
' the whole thing ends with the command end typed on the keyboard
Public Shared Sub Main(ByVal args() As String)
' syntax
Const syntaxe As String = "pg serveur port"
' number of arguments
If args.Length <> 2 Then
erreur(syntaxe, 1)
End If
' note the server name
Dim serveur As String = args(0)
' port must be integer >0
Dim port As Integer = 0
Dim erreurPort As Boolean = False
Dim E As Exception = Nothing
Try
port = Integer.Parse(args(1))
Catch ex As Exception
E = ex
erreurPort = True
End Try
erreurPort = erreurPort Or port <= 0
If erreurPort Then
erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2)
End If
Dim client As TcpClient = Nothing
' there may be problems
Try
' connect to the service
client = New TcpClient(serveur, port)
Catch ex As Exception
' error
Console.Error.WriteLine(("Impossible de se connecter au service (" & serveur & "," & port & "), erreur : " & ex.Message))
' end
Return
End Try
' create read/write threads
Dim thReceive As New Thread(New ThreadStart(AddressOf New clientReceive(client).Run))
Dim thSend As New Thread(New ThreadStart(AddressOf New clientSend(client).Run))
' start execution of both threads
thSend.Start()
thReceive.Start()
' end of main thread
Return
End Sub
' error display
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' error display
System.Console.Error.WriteLine(msg)
' stop with error
Environment.Exit(exitCode)
End Sub
End Class
Public Class clientSend
' class for reading keyboard commands
' and send them to a server via a tcp client passed to the
Private client As TcpClient ' tcp client
' manufacturer
Public Sub New(ByVal client As TcpClient)
' we note the tcp client
Me.client = client
End Sub
' thread Run method
Public Sub Run()
' local data
Dim OUT As StreamWriter = Nothing ' network write streams
Dim commande As String = Nothing ' command read from keyboard
' error management
Try
' create network write stream
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
' order entry-send loop
Console.Out.WriteLine("Commandes : ")
While True
' read command typed on keyboard
commande = Console.In.ReadLine().Trim()
' finished?
If commande.ToLower() = "fin" Then
Exit While
End If
' send order to server
OUT.WriteLine(commande)
End While
Catch ex As Exception
' error
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
End Try
' end - we close the feeds
Try
OUT.Close()
client.Close()
Catch
End Try
' signals the end of the thread
Console.Out.WriteLine("[fin du thread d'envoi des commandes au serveur]")
End Sub
End Class
Public Class clientReceive
' class responsible for reading lines of text intended for a
' tcp client passed to builder
Private client As TcpClient ' tcp client
' manufacturer
Public Sub New(ByVal client As TcpClient)
' we note the tcp client
Me.client = client
End Sub
'manufacturer
' thread Run method
Public Sub Run()
' local data
Dim [IN] As StreamReader = Nothing ' network read stream
Dim réponse As String = Nothing ' server response
' error management
Try
' create network read stream
[IN] = New StreamReader(client.GetStream())
' loop read text lines from IN stream
While True
' network streaming
réponse = [IN].ReadLine()
' closed flow?
If réponse Is Nothing Then
Exit While
End If
' display
Console.Out.WriteLine(("<-- " + réponse))
End While
Catch ex As Exception
' error
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
End Try
' end - close flows
Try
[IN].Close()
client.Close()
Catch
End Try
' signals the end of the thread
Console.Out.WriteLine("[fin du thread de lecture des réponses du serveur]")
End Sub
End Class
9.4.4. Un server TCP generico
Ora vedremo un server
- che visualizza sullo schermo i comandi inviati dai propri client
- invia loro il testo digitato da un utente tramite la tastiera. L'utente funge quindi da server.
Il programma viene avviato con: srvgen listeningPort, dove listeningPort è la porta a cui i client devono connettersi. Il servizio client sarà gestito da due thread:
- un thread dedicato esclusivamente alla lettura delle righe di testo inviate dal client
- un thread dedicato esclusivamente alla lettura delle risposte digitate dall'utente. Questo thread segnalerà, utilizzando il comando fin, che sta chiudendo la connessione con il client.
Il server crea due thread per ogni client. Se ci sono n client, ci saranno 2n thread attivi contemporaneamente. Il server stesso non si ferma mai a meno che l'utente non prema Ctrl-C sulla tastiera. Vediamo alcuni esempi.
Il server è in esecuzione sulla porta 100 e usiamo il client generico per comunicare con esso. La finestra del client appare così:
dos>cltgen localhost 100
Commandes :
commande 1 du client 1
<-- réponse 1 au client 1
commande 2 du client 1
<-- réponse 2 au client 1
fin
L'erreur suivante s'est produite : Impossible de lire les données de la connexion de transport.
[fin du thread de lecture des réponses du serveur]
[fin du thread d'envoi des commandes au serveur]
Le righe che iniziano con <-- sono quelle inviate dal server al client; le altre provengono dal client al server. La finestra del server è la seguente:
dos>srvgen 100
Serveur générique lancé sur le port 100
Thread de lecture des réponses du serveur au client 1 lancé
1 : Thread de lecture des demandes du client 1 lancé
<-- commande 1 du client 1
réponse 1 au client 1
1 : <-- commande 2 du client 1
réponse 2 au client 1
1 : [fin du Thread de lecture des demandes du client 1]
fin
[fin du Thread de lecture des réponses du serveur au client 1]
Le righe che iniziano con <-- sono quelle inviate dal client al server. Le righe N: sono quelle inviate dal server al client N. Il server sopra indicato è ancora attivo anche se il client 1 ha terminato. Avviamo un secondo client per lo stesso server:
dos>cltgen localhost 100
Commandes :
commande 3 du client 2
<-- réponse 3 au client 2
fin
L'erreur suivante s'est produite : Impossible de lire les données de la connexion de transport.
[fin du thread de lecture des réponses du serveur]
[fin du thread d'envoi des commandes au serveur]
La finestra del server ora appare così:
dos>srvgen 100
Serveur générique lancé sur le port 100
Thread de lecture des réponses du serveur au client 1 lancé
1 : Thread de lecture des demandes du client 1 lancé
<-- commande 1 du client 1
réponse 1 au client 1
1 : <-- commande 2 du client 1
réponse 2 au client 1
1 : [fin du Thread de lecture des demandes du client 1]
fin
[fin du Thread de lecture des réponses du serveur au client 1]
Thread de lecture des réponses du serveur au client 2 lancé
2 : Thread de lecture des demandes du client 2 lancé
<-- commande 3 du client 2
réponse 3 au client 2
2 : [fin du Thread de lecture des demandes du client 2]
fin
[fin du Thread de lecture des réponses du serveur au client 2]
^C
Ora simuliamo un server web eseguendo il nostro server generico sulla porta 88:
Ora apriamo un browser e inviamo una richiesta all'URL http://localhost:88/exemple.html. Il browser si connetterà quindi alla porta 88 del computer localhost e richiederà la pagina /example.html:

Ora diamo un'occhiata alla finestra del nostro server:
dos>srvgen 88
Serveur générique lancé sur le port 88
Thread de lecture des réponses du serveur au client 2 lancé
2 : Thread de lecture des demandes du client 2 lancé
<-- GET /exemple.html HTTP/1.1
<-- Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/msword, */*
<-- Accept-Language: fr
<-- Accept-Encoding: gzip, deflate
<-- User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.0.3705; .NET CLR 1.0.2
914)
<-- Host: localhost:88
<-- Connection: Keep-Alive
<--
Ecco come vediamo le intestazioni HTTP inviate dal browser. Questo ci permette di imparare gradualmente il protocollo HTTP. In un esempio precedente, abbiamo creato un client web che inviava solo una singola richiesta GET. Era sufficiente. Qui vediamo che il browser invia altre informazioni al server. Queste informazioni hanno lo scopo di comunicare al server con quale tipo di client ha a che fare. Vediamo anche che le intestazioni HTTP terminano con una riga vuota. Creiamo una risposta per il nostro client. L'utente alla tastiera è il vero e proprio server in questo caso e può creare una risposta manualmente. Ricordiamo la risposta inviata da un server web in un esempio precedente:
<-- HTTP/1.1 200 OK
<-- Date: Mon, 13 May 2002 07:30:58 GMT
<-- Server: Apache/1.3.12 (Unix) (Red Hat/Linux) PHP/3.0.15 mod_perl/1.21
<-- Last-Modified: Wed, 06 Feb 2002 09:00:58 GMT
<-- ETag: "23432-2bf3-3c60f0ca"
<-- Accept-Ranges: bytes
<-- Content-Length: 11251
<-- Connection: close
<-- Content-Type: text/html
<--
<-- <html>
Proviamo a fornire una risposta simile:
...
<-- Host: localhost:88
<-- Connection: Keep-Alive
<--
2 : HTTP/1.1 200 OK
2 : Server: serveur tcp generique
2 : Connection: close
2 : Content-Type: text/html
2 :
2 : <html>
2 : <head><title>Serveur generique</title></head>
2 : <body>
2 : <center>
2 : <h2>Reponse du serveur generique</h2>
2 : </center>
2 : </body>
2 : </html>
2 : fin
L'erreur suivante s'est produite : Impossible de lire les données de la connexion de transport.
[fin du Thread de lecture des demandes du client 2]
[fin du Thread de lecture des réponses du serveur au client 2]
Le righe che iniziano con 2: vengono inviate dal server al client n. 2. Il comando end chiude la connessione dal server al client. Nella nostra risposta, ci siamo limitati alle seguenti intestazioni HTTP:
HTTP/1.1 200 OK
2 : Server: serveur tcp generique
2 : Connection: close
2 : Content-Type: text/html
2 :
Non specifichiamo la dimensione del file che stiamo inviando (Content-Length), ma ci limitiamo a indicare che chiuderemo la connessione (Connection: close) dopo l'invio. Questo è sufficiente per il browser. Non appena rileva la chiusura della connessione, capirà che la risposta del server è completa e visualizzerà la pagina HTML che gli è stata inviata. La pagina è la seguente:
2 : <html>
2 : <head><title>Serveur generique</title></head>
2 : <body>
2 : <center>
2 : <h2>Reponse du serveur generique</h2>
2 : </center>
2 : </body>
2 : </html>
L'utente chiude quindi la connessione al client digitando il comando "fin". Il browser capisce così che la risposta del server è completa e può visualizzarla:

Se, nell'esempio sopra riportato, si seleziona Visualizza/Sorgente per vedere cosa ha ricevuto il browser, si ottiene:

ovvero, esattamente ciò che è stato inviato dal server generico. Il codice per il server TCP generico è il seguente:
' namespaces
Imports System
Imports System.Net
Imports System.Net.Sockets
Imports System.IO
Imports System.Threading
Imports Microsoft.VisualBasic
Public Class serveurTcpGénérique
' main program
Public Shared Sub Main(ByVal args() As String)
' receives the port of listening to customer requests
' creates a thread to read client requests
' these will be displayed on the screen
' creates a thread to read keyboard commands
' these will be sent as a reply to the customer
' the whole thing ends with the command end typed on the keyboard
Const syntaxe As String = "Syntaxe : pg port"
' is there an argument
If args.Length <> 1 Then
erreur(syntaxe, 1)
End If
' this argument must be integer >0
Dim port As Integer = 0
Dim erreurPort As Boolean = False
Dim E As Exception = Nothing
Try
port = Integer.Parse(args(0))
Catch ex As Exception
E = ex
erreurPort = True
End Try
erreurPort = erreurPort Or port <= 0
If erreurPort Then
erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2)
End If
' we create the listening service
Dim ecoute As TcpListener = Nothing
Dim nbClients As Integer = 0 ' of customers handled
Try
' create the service
ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), port)
' launch it
ecoute.Start()
' follow-up
Console.Out.WriteLine(("Serveur générique lancé sur le port " & port))
' customer service loop
Dim client As TcpClient = Nothing
While True ' infinite loop - will be stopped by Ctrl-C
' waiting for a customer
client = ecoute.AcceptTcpClient()
' the service is provided by separate threads
nbClients += 1
' thread for reading customer requests
Dim thReceive As New Thread(New ThreadStart(AddressOf New serveurReceive(client, nbClients).Run))
' thread for reading responses typed on the keyboard by the user
Dim thSend As New Thread(New ThreadStart(AddressOf New serveurSend(client, nbClients).Run))
' start execution of both threads
thSend.Start()
thReceive.Start()
End While
' back to listening to requests
Catch ex As Exception
' we report the error
erreur("L'erreur suivante s'est produite : " + ex.Message, 3)
End Try
End Sub
' error display
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' error display
System.Console.Error.WriteLine(msg)
' stop with error
Environment.Exit(exitCode)
End Sub
End Class
Public Class serveurSend
' class responsible for reading typed responses
' and send them to a client via a tcp client passed to the
Private client As TcpClient ' tcp client
Private numClient As Integer ' customer no
' manufacturer
Public Sub New(ByVal client As TcpClient, ByVal numClient As Integer)
' we note the tcp client
Me.client = client
' and its
Me.numClient = numClient
End Sub
' thread Run method
Public Sub Run()
' local data
Dim OUT As StreamWriter = Nothing ' network write streams
Dim réponse As String = Nothing ' answer read from keyboard
' follow-up
Console.Out.WriteLine(("Thread de lecture des réponses du serveur au client " & numClient & " lancé"))
' error management
Try
' network write stream creation
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
' order entry-send loop
While True
' customer identification
Console.Out.Write((numClient & " : "))
' read response typed on keyboard
réponse = Console.In.ReadLine().Trim()
' finished?
If réponse.ToLower() = "fin" Then
Exit While
End If
' send response to server
OUT.WriteLine(réponse)
End While
' following response
Catch ex As Exception
' error
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
End Try
' end - close flows
Try
OUT.Close()
client.Close()
Catch
End Try
' signals the end of the thread
Console.Out.WriteLine(("[fin du Thread de lecture des réponses du serveur au client " & numClient & "]"))
End Sub
End Class
Public Class serveurReceive
' class responsible for reading text lines sent to the server
' via a tcp client passed to the builder
Private client As TcpClient ' tcp client
Private numClient As Integer ' customer no
' manufacturer
Public Sub New(ByVal client As TcpClient, ByVal numClient As Integer)
' we note the tcp client
Me.client = client
' and its
Me.numClient = numClient
End Sub
' thread Run method
Public Sub Run()
' local data
Dim [IN] As StreamReader = Nothing ' network read stream
Dim réponse As String = Nothing ' server response
' follow-up
Console.Out.WriteLine(("Thread de lecture des demandes du client " & numClient & " lancé"))
' error management
Try
' create network read stream
[IN] = New StreamReader(client.GetStream())
' loop read text lines from IN stream
While True
' network streaming
réponse = [IN].ReadLine()
' closed flow?
If réponse Is Nothing Then
Exit While
End If
' display
Console.Out.WriteLine(("<-- " + réponse))
End While
Catch ex As Exception
' error
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
End Try
' end - close flows
Try
[IN].Close()
client.Close()
Catch
End Try
' signals the end of the thread
Console.Out.WriteLine(("[fin du Thread de lecture des demandes du client " & numClient & "]"))
End Sub
End Class
9.4.5. Un client Web
Nell'esempio precedente abbiamo visto alcune delle intestazioni HTTP inviate da un browser:
<-- GET /exemple.html HTTP/1.1
<-- Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/msword, */*
<-- Accept-Language: fr
<-- Accept-Encoding: gzip, deflate
<-- User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.0.3705; .NET CLR 1.0.2
914)
<-- Host: localhost:88
<-- Connection: Keep-Alive
<--
Scriveremo un client web che accetta un URL come parametro e visualizza sullo schermo il testo inviato dal server. Supporremo che il server supporti il protocollo HTTP 1.1. Tra le intestazioni elencate sopra, useremo solo le seguenti:
- La prima intestazione indica quale pagina vogliamo
- la seconda specifica quale server stiamo interrogando
- La terza indica che vogliamo che il server chiuda la connessione dopo averci risposto.
Se nell'esempio sopra riportato sostituiamo GET con HEAD, il server ci invierà solo le intestazioni HTTP e non la pagina HTML.
Il nostro client web verrà chiamato come segue: webclient URL cmd, dove URL è l'URL desiderato e cmd è una delle due parole chiave GET o HEAD per indicare se vogliamo solo le intestazioni (HEAD) o anche il contenuto della pagina (GET). Vediamo un primo esempio. Avviamo il server IIS e poi il client web sulla stessa macchina:
dos>clientweb http://localhost HEAD
HTTP/1.1 302 Object moved
Server: Microsoft-IIS/5.0
Date: Mon, 13 May 2002 09:23:37 GMT
Connection: close
Location: /IISSamples/Default/welcome.htm
Content-Length: 189
Content-Type: text/html
Set-Cookie: ASPSESSIONIDGQQQGUUY=HMFNCCMDECBJJBPPBHAOAJNP; path=/
Cache-control: private
La risposta
indica che la pagina richiesta è stata spostata (e quindi ha un nuovo URL). Il nuovo URL è fornito dall'intestazione Location:
Se utilizziamo GET invece di HEAD nella chiamata al client web:
dos>clientweb http://localhost GET
HTTP/1.1 302 Object moved
Server: Microsoft-IIS/5.0
Date: Mon, 13 May 2002 09:33:36 GMT
Connection: close
Location: /IISSamples/Default/welcome.htm
Content-Length: 189
Content-Type: text/html
Set-Cookie: ASPSESSIONIDGQQQGUUY=IMFNCCMDAKPNNGMGMFIHENFE; path=/
Cache-control: private
<head><title>L'objet a changé d'emplacement</title></head>
<body><h1>L'objet a changé d'emplacement</h1>Cet objet peut être trouvé <a HREF="/IISSamples/Default/we
lcome.htm">ici</a>.</body>
Otteniamo lo stesso risultato di HEAD, più il corpo della pagina HTML. Il programma è il seguente:
' namespaces
Imports System
Imports System.Net.Sockets
Imports System.IO
Public Class clientWeb1
' requests a URL
' displays its contents on the screen
Public Shared Sub Main(ByVal args() As String)
' syntax
Const syntaxe As String = "pg URI GET/HEAD"
' number of arguments
If args.Length <> 2 Then
erreur(syntaxe, 1)
End If
' note the URI required
Dim URIstring As String = args(0)
Dim commande As String = args(1).ToUpper()
' URI validity check
Dim uri As Uri = Nothing
Try
uri = New Uri(URIstring)
Catch ex As Exception
' URI incorrect
erreur("L'erreur suivante s'est produite : " + ex.Message, 2)
End Try
' order verification
If commande <> "GET" And commande <> "HEAD" Then
' incorrect order
erreur("Le second paramètre doit être GET ou HEAD", 3)
End If
' we can work
Dim client As TcpClient = Nothing ' the customer
Dim [IN] As StreamReader = Nothing ' the customer's reading flow
Dim OUT As StreamWriter = Nothing ' the customer's writing flow
Dim réponse As String = Nothing ' server response
Try
' connect to the server
client = New TcpClient(uri.Host, uri.Port)
' create customer input/output flows TCP
[IN] = New StreamReader(client.GetStream())
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
' request URL - send HTTP headers
OUT.WriteLine((commande + " " + uri.PathAndQuery + " HTTP/1.1"))
OUT.WriteLine(("Host: " + uri.Host + ":" & uri.Port))
OUT.WriteLine("Connection: close")
OUT.WriteLine()
' we read the answer
réponse = [IN].ReadLine()
While Not (réponse Is Nothing)
' the answer is processed
Console.Out.WriteLine(réponse)
' we read the answer
réponse = [IN].ReadLine()
End While
' it's over
client.Close()
Catch e As Exception
' we handle the exception
erreur(e.Message, 4)
End Try
End Sub
' error display
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' error display
System.Console.Error.WriteLine(msg)
' stop with error
Environment.Exit(exitCode)
End Sub
End Class
L'unica novità di questo programma è l'uso della classe Uri. Il programma riceve un URL (Uniform Resource Locator) o URI (Uniform Resource Identifier) nella forma http://serveur:port/cheminPageHTML?param1=val1;param2=val2;.... La classe Uri ci permette di scomporre la stringa URL nei suoi vari componenti. Un oggetto Uri viene costruito a partire dalla stringa URI ricevuta come parametro:
' URI validity check
Dim uri As Uri = Nothing
Try
uri = New Uri(URIstring)
Catch ex As Exception
' URI incorrect
erreur("L'erreur suivante s'est produite : " + ex.Message, 2)
End Try
Se la stringa URI ricevuta come parametro non è un URI valido (protocollo mancante, server, ecc.), viene generata un'eccezione. Questo ci permette di verificare la validità del parametro ricevuto. Una volta costruito l'oggetto URI, abbiamo accesso ai suoi vari elementi. Pertanto, se l'oggetto URI del codice precedente è stato costruito dalla stringa http://serveur:port/cheminPageHTML?param1=val1;param2=val2;..., avremo:
uri.Host=server, uri.Port=port, uri.Path=HTMLPagePath, uri.Query=param1=val1;param2=val2;..., uri.pathAndQuery=HTMLPagePath?param1=val1;param2=val2;..., uri.Scheme=http.
9.4.6. Gestione dei reindirizzamenti da parte del client web
Il client web precedente non gestisce eventuali reindirizzamenti dell'URL richiesto. Il client seguente invece li gestisce.
- Legge la prima riga delle intestazioni HTTP inviate dal server per verificare se contiene la stringa "302 Object moved", che indica un reindirizzamento
- Legge le intestazioni seguenti. Se è presente un reindirizzamento, cerca la riga "Location: url" che fornisce il nuovo URL della pagina richiesta e lo registra.
- Visualizza il resto della risposta del server. Se c'è un reindirizzamento, i passaggi da 1 a 3 vengono ripetuti con il nuovo URL. Il programma non accetta più di un reindirizzamento. Questo limite è definito da una costante che può essere modificata.
Ecco un esempio:
dos>clientweb2 http://localhost GET
HTTP/1.1 302 Object moved
Server: Microsoft-IIS/5.0
Date: Mon, 13 May 2002 11:38:55 GMT
Connection: close
Location: /IISSamples/Default/welcome.htm
Content-Length: 189
Content-Type: text/html
Set-Cookie: ASPSESSIONIDGQQQGUUY=PDGNCCMDNCAOFDMPHCJNPBAI; path=/
Cache-control: private
<head><title>L'objet a chang d'emplacement</title></head>
<body><h1>L'objet a chang d'emplacement</h1>Cet objet peut tre trouv <a HREF="/IISSamples/Default/we
lcome.htm">ici</a>.</body>
<--Redirection vers l'URL http://localhost:80/IISSamples/Default/welcome.htm-->
HTTP/1.1 200 OK
Server: Microsoft-IIS/5.0
Connection: close
Date: Mon, 13 May 2002 11:38:55 GMT
Content-Type: text/html
Accept-Ranges: bytes
Last-Modified: Mon, 16 Feb 1998 21:16:22 GMT
ETag: "0174e21203bbd1:978"
Content-Length: 4781
<html>
<head>
<title>Bienvenue dans le Serveur Web personnel</title>
</head>
....
</body>
</html>
Il programma è il seguente:
' namespaces
Imports System
Imports System.Net.Sockets
Imports System.IO
Imports System.Text.RegularExpressions
Imports Microsoft.VisualBasic
' web client class
Public Class clientWeb
' requests a URL and displays its contents on the screen
Public Shared Sub Main(ByVal args() As String)
' syntax
Const syntaxe As String = "pg URI GET/HEAD"
' number of arguments
If args.Length <> 2 Then
erreur(syntaxe, 1)
End If
' note the URI required
Dim URIstring As String = args(0)
Dim commande As String = args(1).ToUpper()
' URI validity check
Dim uri As Uri = Nothing
Try
uri = New Uri(URIstring)
Catch ex As Exception
' URI incorrect
erreur("L'erreur suivante s'est produite : " + ex.Message, 2)
End Try 'catch
' order verification
If commande <> "GET" And commande <> "HEAD" Then
' incorrect order
erreur("Le second paramètre doit être GET ou HEAD", 3)
End If
' we can work
Dim client As TcpClient = Nothing ' the customer
Dim [IN] As StreamReader = Nothing ' the customer's reading flow
Dim OUT As StreamWriter = Nothing ' the customer's writing flow
Dim réponse As String = Nothing ' server response
Const nbRedirsMax As Integer = 1 ' no more than one redirection accepted
Dim nbRedirs As Integer = 0 ' number of redirects in progress
Dim premièreLigne As String ' 1st line of the answer
Dim redir As Boolean = False ' indicates redirection or not
Dim locationString As String = "" ' the URI string of a possible redirection
' regular expression to find a URL redirect
Dim location As New Regex("^Location: (.+?)$") '
' error management
Try
' you may have several URL to request if there are redirections
While nbRedirs <= nbRedirsMax
' connect to the server
client = New TcpClient(uri.Host, uri.Port)
' create customer input/output flows TCP
[IN] = New StreamReader(client.GetStream())
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
' we send HTTP headers to request URL
OUT.WriteLine((commande + " " + uri.PathAndQuery + " HTTP/1.1"))
OUT.WriteLine(("Host: " + uri.Host + ":" & uri.Port))
OUT.WriteLine("Connection: close")
OUT.WriteLine()
' read the first line of the answer
premièreLigne = [IN].ReadLine()
' screen echo
Console.Out.WriteLine(premièreLigne)
' redirection?
If Regex.IsMatch(premièreLigne, "302 Object moved$") Then
' there is a redirection
redir = True
nbRedirs += 1
End If
' next HTTP headers until you find the empty line signalling the end of the headers
Dim locationFound As Boolean = False
réponse = [IN].ReadLine()
While réponse <> ""
' the answer is displayed
Console.Out.WriteLine(réponse)
' if there is a redirection, we search for the Location header
If redir And Not locationFound Then
' compare the line with the relational expression location
Dim résultat As Match = location.Match(réponse)
If résultat.Success Then
' if found, note the URL of redirection
locationString = résultat.Groups(1).Value
' we note that we found
locationFound = True
End If
End If
' next line
réponse = [IN].ReadLine()
End While
' following lines of the answer
Console.Out.WriteLine(réponse)
réponse = [IN].ReadLine()
While Not (réponse Is Nothing)
' the answer is displayed
Console.Out.WriteLine(réponse)
' next line
réponse = [IN].ReadLine()
End While
' close the connection
client.Close()
' are we done?
If Not locationFound Or nbRedirs > nbRedirsMax Then
Exit While
End If
' there is a redirection to be made - we build the new Uri
URIstring = uri.Scheme + "://" & uri.Host & ":" & uri.Port & locationString
uri = New Uri(URIstring)
' follow-up
Console.Out.WriteLine((ControlChars.Lf + "<--Redirection vers l'URL " + URIstring + "-->" + ControlChars.Lf))
End While
Catch e As Exception
' we handle the exception
erreur(e.Message, 4)
End Try
End Sub
' error display
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' error display
System.Console.Error.WriteLine(msg)
' stop with error
Environment.Exit(exitCode)
End Sub
End Class
9.4.7. Server di calcolo delle imposte
Stiamo riesaminando l'esercizio TAXES, che è già stato trattato in varie forme. Rivediamo l'ultima versione. È stata creata una classe tax. I suoi attributi sono tre array di numeri:
Public Class impôt
' les données nécessaires au calcul de l'impôt
' proviennent d'une source extérieure
Private limites(), coeffR(), coeffN() as double
La classe ha due costruttori:
- un costruttore che accetta i tre array di dati necessari per calcolare l'imposta
// constructeur 1
Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
' initializes the three limit arrays, coeffR, coeffN from
' parameters passed to the constructor
- un costruttore a cui passiamo il nome DSN di un database ODBC
' builder 2
Public Sub New(ByVal DSNimpots As String, ByVal Timpots As String, ByVal colLimites As String, ByVal colCoeffR As String, ByVal colCoeffN As String)
' initializes the three limit arrays, coeffR, coeffN from
' the contents of the Timpots table in the ODBC DSNimpots database
' colLimites, colCoeffR, colCoeffN are the three columns of this table
' can throw an exception
È stato scritto un programma di test:
dos>vbc /r:impots.dll testimpots.vb
dos>test mysql-impots timpots limites coeffr coeffn
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 200000
impôt=22506 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :n 2 200000
impôt=33388 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 3 200000
impôt=16400 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :n 3 300000
impôt=50082 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :n 3 200000
impôt=22506 F
In questo caso, il programma di test e l'oggetto fiscale si trovavano sulla stessa macchina. Proponiamo di collocare il programma di test e l'oggetto fiscale su macchine diverse. Avremo un'applicazione client-server in cui l'oggetto fiscale remoto fungerà da server. La nuova classe si chiama TaxServer ed è derivata dalla classe tax:
Public Class ServeurImpots
Inherits impôt
' attributes
Private portEcoute As Integer ' the ability to listen to customer requests
Private actif As Boolean ' server status
' manufacturer
Public Sub New(ByVal portEcoute As Integer, ByVal DSNimpots As String, ByVal Timpots As String, ByVal colLimites As String, ByVal colCoeffR As String, ByVal colCoeffN As String)
MyBase.New(DSNimpots, Timpots, colLimites, colCoeffR, colCoeffN)
' we note the listening port
Me.portEcoute = portEcoute
' currently inactive
actif = False
' creates and launches a thread for reading keyboard commands
' the server will be managed using these commands
Dim threadLecture As Thread = New Thread(New ThreadStart(AddressOf admin))
threadLecture.Start()
End Sub
L'unico nuovo parametro nel costruttore è la porta su cui ascoltare le richieste dei client. Gli altri parametri vengono passati direttamente alla classe base tax. Il server tax è controllato tramite comandi da tastiera. Pertanto, creiamo un thread per leggere questi comandi. Saranno disponibili due comandi: start per avviare il servizio e stop per arrestarlo definitivamente. Il metodo admin che gestisce questi comandi è il seguente:
Public Sub admin()
' reads server administration commands typed from the keyboard
' in an endless loop
Dim commande As String = Nothing
While True
' invite
Console.Out.Write("Serveur d'impôts>")
' read command
commande = Console.In.ReadLine().Trim().ToLower()
' order execution
If commande = "start" Then
' active?
If actif Then
'error
Console.Out.WriteLine("Le serveur est déjà actif")
Else
' we launch the listening service
Dim threadEcoute As Thread = New Thread(New ThreadStart(AddressOf ecoute))
threadEcoute.Start()
End If
Else
If commande = "stop" Then
' end of all execution threads
Environment.Exit(0)
Else
' error
Console.Out.WriteLine("Commande incorrecte. Utilisez (start,stop)")
End If
End If
End While
End Sub
Se il comando immesso tramite tastiera è "start", viene avviato un thread che ascolta le richieste dei client. Se il comando immesso è "stop", tutti i thread vengono arrestati. Il thread di ascolto esegue il metodo "ecoute":
Public Sub ecoute()
' thread for listening to customer requests
' we create the listening service
Dim ecoute As TcpListener = Nothing
Try
' create the service
ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), portEcoute)
' launch it
ecoute.Start()
' follow-up
Console.Out.WriteLine(("Serveur d'écho lancé sur le port " & portEcoute))
' service loop
Dim liaisonClient As TcpClient = Nothing
While True ' infinite loop
' waiting for a customer
liaisonClient = ecoute.AcceptTcpClient()
' the service is provided by another task
Dim threadClient As Thread = New Thread(New ThreadStart(AddressOf New traiteClientImpots(liaisonClient, Me).Run))
threadClient.Start()
End While
' back to listening to requests
Catch ex As Exception
' we report the error
erreur("L'erreur suivante s'est produite : " + ex.Message, 3)
End Try
End Sub
' error display
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' error display
System.Console.Error.WriteLine(msg)
' stop with error
Environment.Exit(exitCode)
End Sub
Qui abbiamo un classico server TCP in ascolto sulla porta portEcoute. Le richieste del client vengono gestite dal metodo Run di un oggetto a cui vengono passati due parametri:
- l'oggetto TcpClient, che consente l'accesso al client
- l'oggetto tax this, che fornisce l'accesso al metodo di calcolo delle imposte this.calculate.
' -------------------------------------------------------
' provides service to a tax server client
Public Class traiteClientImpots
Private liaisonClient As TcpClient ' customer liaison
Private [IN] As StreamReader ' iNPUTS
Private OUT As StreamWriter ' output flow
Private objImpôt As impôt ' object Tax
' manufacturer
Public Sub New(ByVal liaisonClient As TcpClient, ByVal objImpôt As impôt)
Me.liaisonClient = liaisonClient
Me.objImpôt = objImpôt
End Sub
Il metodo Run elabora le richieste dei clienti. Queste possono assumere due forme:
- calcolaConiugi(sì/no) n.Figli stipendioAnnuale
- endCalculations
Il modulo 1 calcola un'imposta, mentre il modulo 2 chiude la connessione client-server.
' run method
Public Sub Run()
' renders service to the customer
Try
' iNPUTS
[IN] = New StreamReader(liaisonClient.GetStream())
' output flow
OUT = New StreamWriter(liaisonClient.GetStream())
OUT.AutoFlush = True
' send a welcome message to the customer
OUT.WriteLine("Bienvenue sur le serveur d'impôts")
' loop read request/write response
Dim demande As String = Nothing
Dim champs As String() = Nothing ' elements of the request
Dim commande As String = Nothing ' customer order: calculation or fincalculs
demande = [IN].ReadLine()
While Not (demande Is Nothing)
' demand is broken down into fields
champs = Regex.Split(demande.Trim().ToLower(), "\s+")
' two successful applications: calcul and fincalculs
commande = champs(0)
Dim erreur As Boolean = False
If commande <> "calcul" And commande <> "fincalculs" Then
' customer error
OUT.WriteLine("Commande incorrecte. Utilisez (calcul,fincalculs).")
End If
If commande = "calcul" Then
calculerImpôt(champs)
End If
If commande = "fincalculs" Then
' good-bye message to customer
OUT.WriteLine("Au revoir...")
' freeing up resources
Try
OUT.Close()
[IN].Close()
liaisonClient.Close()
Catch
End Try
' end
Return
End If
' new request
demande = [IN].ReadLine()
End While
Catch e As Exception
erreur("L'erreur suivante s'est produite (" + e.ToString + ")", 2)
End Try
End Sub
Il calcolo dell'imposta viene eseguito dal metodo CalculateTax, che accetta come parametro l'array di campi della richiesta del cliente. Viene verificata la validità della richiesta e, se valida, l'imposta viene calcolata e restituita al cliente.
' tax calculation
Public Sub calculerImpôt(ByVal champs() As String)
' processing the application: calculation married nbEnfants salaireAnnuel
' broken down into fields in the fields table
Dim marié As String = Nothing
Dim nbEnfants As Integer = 0
Dim salaireAnnuel As Integer = 0
' validity of arguments
Try
' at least 4 fields are required
If champs.Length <> 4 Then
Throw New Exception
End If
' married
marié = champs(1)
If marié <> "o" And marié <> "n" Then
Throw New Exception
End If
' children
nbEnfants = Integer.Parse(champs(2))
' salary
salaireAnnuel = Integer.Parse(champs(3))
Catch
OUT.WriteLine(" syntaxe : calcul marié(O/N) nbEnfants salaireAnnuel")
' finish
Exit Sub
End Try
' tax can be calculated
Dim impot As Long = objImpôt.calculer(marié = "o", nbEnfants, salaireAnnuel)
' we send the response to the client
OUT.WriteLine(impot.ToString)
End Sub
' error display
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' error display
System.Console.Error.WriteLine(msg)
' stop with error
Environment.Exit(exitCode)
End Sub
Questa classe è compilata da
dove impots.dll contiene il codice per la classe impôt. Un programma di prova potrebbe apparire così:
' namespaces
Imports System
Imports System.IO
Imports Microsoft.VisualBasic
Public Class testServeurImpots
Public Shared syntaxe As String = "Syntaxe : pg port dsnImpots Timpots colLimites colCoeffR colCoeffN"
' main program
Public Shared Sub Main(ByVal args() As String)
' you need6 arguments
If args.Length <> 6 Then
erreur(syntaxe, 1)
End If
' port must be integer >0
Dim port As Integer = 0
Dim erreurPort As Boolean = False
Dim E As Exception = Nothing
Try
port = Integer.Parse(args(0))
Catch ex As Exception
E = ex
erreurPort = True
End Try
erreurPort = erreurPort Or port <= 0
If erreurPort Then
erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2)
End If
' we create the tax server
Try
Dim srvimots As ServeurImpots = New ServeurImpots(port, args(1), args(2), args(3), args(4), args(5))
Catch ex As Exception
'error
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
End Try
End Sub
' error display
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' error display
System.Console.Error.WriteLine(msg)
' stop with error
Environment.Exit(exitCode)
End Sub
End Class
Passiamo i dati necessari per costruire un oggetto ServeurImpots al programma di test, che provvederà a creare tale oggetto. Questo programma di test viene compilato da:
Ecco un test iniziale:
dos>testimpots 124 odbc-mysql-dbimpots impots limites coeffr coeffn
Serveur d'impôts>Serveur d'impôts>start
Serveur d'impôts>Serveur d'écho lancé sur le port 124
stop
La riga
crea un oggetto TaxServer che non è ancora in ascolto delle richieste dei client. È il comando start digitato sulla tastiera che avvia questo processo di ascolto. Il comando stop arresta il server. Utilizziamo ora un client. Useremo il client generico creato in precedenza. Il server viene avviato:
dos>testimpots 124 odbc-mysql-dbimpots impots limites coeffr coeffn
Serveur d'impôts>Serveur d'impôts>start
Serveur d'impôts>Serveur d'écho lancé sur le port 124
Il client generico viene avviato in un'altra finestra DOS:
Possiamo vedere che il client ha ricevuto correttamente il messaggio di benvenuto dal server. Inviamo altri comandi:
x
<-- Commande incorrecte. Utilisez (calcul,fincalculs).
calcul
<-- syntaxe : calcul marié(O/N) nbEnfants salaireAnnuel
calcul o 2 200000
<-- 22506
calcul n 2 200000
<-- 33388
fincalculs
<-- Au revoir...
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]
Torniamo alla finestra del server per interromperlo:















