la programmazione di rete - intranet...
TRANSCRIPT
La programmazione di rete
Introduzione alla programmazione di rete
La connessione
La trasmissione
20 febbraio 2004
- 2 -
Applicazioni distribuite
Applicazione: un insieme di programmi coordinati per
svolgere una data funzione applicativa.
Un’applicazione è distribuita se prevede più programmi
(o processi) eseguiti su differenti calcolatori connessi
tramite una rete. I programmi devono quindi contenere
funzioni che consentano la comunicazione tramite una
rete.
Processo: programma in esecuzione.
Rete: nodi collegati + servizi di rete che consentono la
comunicazione.
- 3 -
Protocollo applicativo
Le regole per la comunicazione in una applicazione
distribuita sono dette protocollo applicativo.
• P. es. il protocollo applicativo della navigazione Web è detto
HyperText Transfer Protocol - http.
Il protocollo applicativo deve essere definito
opportunamente e comune a tutti i programmi
dell’applicazione.
• P. es. ogni messaggio scambiato è terminato dalla stringa “\0 \0 \0”.
- 4 -
Interfacce e protocolli
Il protocollo applicativo utilizza i servizi messi a disposizione dal
Sistema Operativo della macchina e i servizi di rete che consentono
la comunicazione, messi a disposizione dal software di rete.
Il software di rete (servizi di rete) dipende dal protocollo di
comunicazione comune tra i nodi della rete.
• Si considera il protocollo di comunicazione TCP/IP (Transfer Control Protocol
/ Interconnect Protocol).
L’insieme delle chiamate di funzioni di sistema utilizzabili dai
programmi applicativi viene comunemente definito API (Application
Program Interface):
• le API forniscono il canale (o supporto) di comunicazione;
• i programmi comunicano invocando opportune funzioni della API di rete;
• il protocollo applicativo rappresenta le regole di comunicazione, e
considera il contenuto della comunicazione.
- 5 -
Interfacce e protocolli
Programma
applicativo P
Sistema operativo
e SW di rete
Programma
applicativo Q
Protocollo applicativo
Rete
Sistema operativo
e SW di rete
Applicazione distribuita
Chiamate al S.O.
e al software di rete
(API)
- 6 -
L’interfaccia socket
Obbiettivo: specificare l’interfaccia tra programma
applicativo e software del protocollo di comunicazione:
– La API standard per TCP/ IP si chiama “interfaccia socket”, ed è
stata definita a Berkley agli albori di internet (circa 1980).
– Per utilizzarla, e cioè utilizzare le funzioni di rete relative, è necessario includere un insieme di file di libreria (sys/types.h,
sys/socket.h, netinet/in.h).
– Il socket costituisce la struttura dati che rappresenta il canale di
rete.
- 7 -
L’interfaccia socket
L’interfaccia socket è scritta in linguaggio C ed è
definita per calcolatori Unix (LINUX): per calcolatori
Windows si utilizza l’interfaccia WinSocket.
– Socket funziona anche per altri protocolli differenti da TCP / IP.
TCP / IP garantisce l’interoperabilità tra calcolatori
anche se equipaggiati con sistemi operativi differenti.
Calcolatori Unix e calcolatori Windows possono
comunicare utilizzando le interfacce Socket e
WinSocket, rispettivamente.
- 8 -
Connessione tra calcolatori Unix
e Windows
Programma C
API di Unix + socket
Unix e TCP / IP
Programma C
API di Win + Winsocket
Windows e TCP / IP
Canale di comunicazione
- 9 -
Il modello Cliente / Servente (Client / Server)
Il software di rete TCP / IP consente a due processi residenti su due nodi della rete di comunicare tra loro, trasferendo dati. Non definisce a priori nessuno schema o modello di cooperazione (applicativa) tra i processi (comunicazione peer-to-peer).
Il modello di cooperazione tipico di un’applicazione distribuita è il modello cliente / servente (client / server).
– processo servente (server): offre servizi ad altri processi, ne accetta le richieste, esegue il servizio e fornisce un risultato. Il servente viene pertanto attivato prima del cliente;
– processo cliente (client): richiede dei servizi a un servente e ne attende la risposta.
• Generalmente un processo conserva lo stesso ruolo (è o sempre Cliente o sempre Servente) – non è però un obbligo.
- 10 -
Identificazione dei processi - Indirizzamento
Siano P e Q i due processi che devono comunicare: Q (cliente)
deve richiedere un servizio a P (servente).
È necessario che Q sappia come raggiungere P (per inviargli la
richiesta) e che poi P sappia come raggiungere Q (per
restituire quanto richiesto o segnalare il completamento
dell’operazione richiesta dallo stesso Q).
Un processo viene identificato, e quindi indirizzato, ai fini
della comunicazione specificando:
– il calcolatore sul quale il processo è in esecuzione;
– il numero di porta, che identifica il processo sul suo calcolatore ai fini
della comunicazione.
- 11 -
Indirizzamento
Il calcolatore è specificato tramite l’indirizzo IP (Internet Protocol):
– l’indirizzo IP è composto da 4 byte (quindi 32 bit) (per esempio
“131.175.21.8”).
La porta è un valore intero che può essere specificato dal processo o
assegnato automaticamente:
– alcune porte (comprese tra 0 e 1023) sono assegnate a servizi standard e
non possono essere utilizzate per sviluppare propri serventi.
L’indirizzamento TCP completo è quindi specificato fornendo l’indirizzo
IP della macchina e la porta TCP:
Es. <131.175.21.8, 80> significa:
• porta 80 (di solito è la porta del server Web);
• della macchina di indirizzo 131.175.21.8.
- 12 -
Il modello di comunicazione e la connessione
La comunicazione TCP è “connection-oriented”
(orientata alla connessione):
– perché due processi comunichino è necessario prima stabilire una
connessione, quindi scambiare i dati.
Perché una connessione possa essere stabilita è
necessario che un processo (generalmente il servente P)
sia in attesa di una richiesta di connessione da parte di
un altro processo (il cliente Q).
- 13 -
Il modello di comunicazione e la connessione
Sequenza per la connessione di due processi P e Q:
– P e Q rappresentano i punti terminali della connessione considerata;
– ogni punto terminale è identificato dalla coppia <indirizzo IP, numero di porta>;
– ogni connessione è quindi identificata da 4 numeri: <indirizzo IP di P, numero di porta di P>, <indirizzo IP di Q, numero di porta di Q>;
– dopo la connessione, il canale di comunicazione è bidirezionale, affidabile e orientato al flusso (stream).
Si noti che uno stesso processo può partecipare a diverse connessioni contemporaneamente. Le diverse connessioni sono infatti distinte dagli altri punti terminali.
- 14 -
Il modello di comunicazione e la connessione
Le principali caratteristiche del canale di comunicazione
sono:
– bidirezionale: P colloquia con Q e Q colloquia con P;
– orientato al flusso (stream): consente una trasmissione continua
di byte (o di gruppi di byte);
– affidabile: se il destinatario (Q o P) non riceve un byte (o un
gruppo di byte), il mittente (P o Q) se ne accorge. È la
caratteristica di TCP, non presente in IP.
- 15 -
Tipi di calcolatori e formato dei dati
I calcolatori che si affacciano sulla rete (host) possono essere di
tipi e con sistemi operativi differenti. I dati possono essere
rappresentati in modi differenti.
TCP / IP prevede un formato di rete dei dati unico: ogni
calcolatore dispone di routine per convertire i dati dal formato
TCP/IP al proprio formato locale e viceversa.
Indirizzo IP:
u_long inet_addr (char * stringa)
char * inet_ntoa (u_long addr)
Porta TCP:
u_short htons (u_short port)
u_short ntohs (u_short port)
- 17 -
Il meccanismo accept-connect
La creazione di una connessione tra due processi deve seguire le seguenti
regole:
il processo P (server) si pone in attesa di richieste di connessione
(apertura passiva).
– Si utilizza la funzione accept (bloccante).
il processo Q (client) formula una richiesta a P di apertura di
connessione (apertura attiva):
– Si utilizza la funzione connect.
Le richieste si attuano sull’interfaccia di socket tramite le funzioni
accept / connect.
Per stabilire una connessione è necessario, dal punto di vista
programmativo, inizializzare opportune variabili che descrivono i punti
terminali della connessione. Lo vedremo con degli esempi.
- 18 -
Il meccanismo accept-connect
La connessione può fallire perché:
– il processo P (servente) non esiste;
– P esiste ma non ha eseguito la accept;
– P esiste ma è occupato in un’altra connessione e la coda dei
processi che richiedono connessioni a P è già piena;
– Q (cliente) non conosce gli esatti valori dell’indirizzo IP del
calcolatore di P e della porta TCP di P;
– ...
- 19 -
Un cliente semplice
Le operazioni svolte dal processo Q (cliente) che effettua una
richiesta di connessione a un altro processo (apertura attiva) sono:
– definisce una opportuna variabile di tipo struct sockaddr_in per
contenere le informazioni sul punto terminale che identifica il
servente a cui vuole connettersi (il tipo struct sockaddr_in è
definito per contenere informazioni di un punto terminale);
– inizializza la variabile relativa con dati validi;
– può definire una seconda variabile di tipo struct sockaddr_in per
contenere le informazioni del punto terminale che rappresenta se
stesso;
– crea un socket della connessione (descrittore);
– esegue la connect specificando il proprio socket.
- 20 -
Descrizione del punto terminale -
struct sockaddr_in
struct sockaddr_in {
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero [8];
} /* end sockaddr_in */
Dove:
sin_family: famiglia di indirizzi AF_INET (Address Family InterNet)
altre famiglie: AF_UP (Xerox), AF_APPLETALK (AppleTalk), AF_UNIX (UNIX)
sin_port: numero di porta TCP, a 16 bit (0-65535)
sin_addr: indirizzo IP, 32 bit
sin_zero: non utilizzato
- 21 -
Inizializzazione del punto terminale
#include <sys/type.h>
#include <sys/socket.h>
#include <netinet/in.h>
/* costanti utili */
#define IND_SERVER “127.0.0.1” /* NB è una stringa */
#define PORTA_SERVER 2000 /* NB è un numero */
. . . . .
struct sockaddr_in server_addr; /* punto terminale */
int server_len = sizeof (server_addr);
/* azzera il punto terminale */
bzero ((char *) &server_addr, server_len);
server_addr.sin_family = AF_INET;
/* htons: host to network conversion, short */
server_addr.sin_port = htons ((u_short) PORTA_SERVER);
/* inet_addr: converte da stringa a formato rete */
server_addr.sin_addr.s_addr = inet_addr (IND_SERVER);
- 22 -
Procedura addr_initialize
#include <sys/type.h>
#include <sys/socket.h>
#include <netinet/in.h>
void addr_initialize (struct sockaddr_in * indirizzo,
int port, long IPaddr) {
indirizzo->sin_family = AF_INET;
indirizzo->sin_port = htons ((u_short) port);
/* htons: host to network conversion, short */
indirizzo->sin_addr.s_addr = IPaddr;
} /* end addr_initialize */
- 23 -
Un cliente semplice
inizializza il punto terminale servente
crea un socket tramite l’istruzione
sd = socket (AF_INET, SOCK_STREAM, 0);
esegue la richiesta di connessione
error = connect (sd, (struct sockaddr *)
&server_addr, sizeof (server_addr));
– Tramite la funzione connect gli viene assegnata una porta di cui può conoscere il valore tramite la funzione getsockname.
al termine, chiude il socket rilasciando la porta che così potrà essere utilizzata da altri programmi:
close (sd);
- 24 -
socket ( )
int socket (int family, int type, int protocol)
/ Crea un socket e ne restituisce il descrittore, -1 =
errore. Parametri:
family, definisce la famiglia di protocolli (AF_INET per
TCP/IP, AF_UP per Xerox, ...);
type, specifica il tipo di comunicazione (SOCK_STREAM per
servizio di consegna affidabile TCP, SOCK_DGRAM per
datagramma senza connessione UDP, ...);
protocol, specifica quale protocollo utilizzare se nella
famiglia utilizzata ne esiste più di uno, normalmente vale
0.
Nota bene: il socket da solo non è ancora il canale di rete, è
soltanto una struttura dati che serve per gestire il canale di
rete; per aprire un canale di rete associandolo al socket occorre
chiamare la primitiva connect oppure la primitiva accept /
- 25 -
connect ( )
int connect (int sd, struct sockaddr server_ep,
int ep_len)
/ Invia una richiesta di collegamento in qualità di cliente,
restituisce 0 se successo, -1 se errore. Parametri:
sd, specifica il socket (che deve essere già stato creato) da
associare al canale di rete;
server_ep, specifica il punto terminale (endpoint) del
destinatario della richiesta di collegamento, che è il server;
ep_len, specifica la lunghezza in byte del punto terminale /
Chiamata tipica: error = connect (sd, (sockaddr *) &server_ep,
sizeof (server_ep));
- 26 -
Passaggio di parametri a “main”
argc: contiene il numero dei parametri ricevuti.
argv: è un vettore di puntatori a stringhe, ognuna delle quali è un parametro. Per convenzione argv[0] contiene sempre il nome del programma in esecuzione.
#include <stdio.h>
void main (int argc, char * argv[]) {
int i;
printf (“il valore di argc e’ %d \n \n”, argc);
for (i = 0; i < argc; i++) {
printf (“parametro %i = %s\n”, i, argv[i]);
} / end for */
} /* end main */
>prova1 131.175.23.1
il valore di argc e’ 2
parametro 0 = prova1
parametro 1 = 131.175.23.1
- 27 -
Codice di UClient1 (I)
/* programma UCLIENT1 */
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define PORT 4000
void addr_initialize ( );
void main (int argc, char * argv[]) {
/* legge sulla linea di comando l’indirizzo di IP del calcolatore
dove c’è il server */
int sd;
struct sockaddr_in server_addr;
struct sockaddr_in mio_addr;
int mio_addr_len = sizeof (mio_addr);
int error;
- 28 -
Codice di UClient1 (II)
addr_initialize (&server_addr, PORT, inet_addr (argv[1]));
sd = socket (AF_INET, SOCK_STREAM, 0);
error = connect (sd, (struct sockaddr *) &server_addr,
sizeof (server_addr));
if (error == 0) {
printf ("Ho eseguito la connessione\n");
getsockname (sd, &mio_addr, &mio_addr_len);
printf (“la mia porta e': %d.\n\n",
ntohs (mio_addr.sin_port));
close (sd);
} /* end if */
else printf ("%s","\nErrore di connect.\n\n");
close (sd);
} /* end main */
- 29 -
Esecuzione di UClient1
Se il servente non è attivo, il cliente riporta il fallimento
della connessione:
- 30 -
Un servente semplice (I)
Le operazioni svolte dal processo P che si mette in attesa di richieste di connessione da altri processi (apertura passiva) sono analoghe a quelle svolte da Q:
– predispone una variabile struct sockaddr_in client_addr per memorizzare l’indirizzo IP e la porta del cliente per potergli rispondere;
– predispone una variabile struct sockaddr_in server_addr per inizializzare il proprio punto terminale, specificando indirizzo e numero di porta. Generalmente un servente specifica che intende accettare connessioni sulla propria porta, indipendentemente dall’indirizzo IP (del servente stesso) sul quale arrivano le richieste (INADDR_ANY);
– crea un socket sd per accettare connessioni.
- 31 -
Un servente semplice (II)
– indica a TCP/IP che l’indirizzo locale associato al socket sd è quello contenuto in server_addr tramite la funzione bind:
bind (sd,(struct sockaddr *) &server_addr,
sizeof (server_addr));
– si pone in attesa di una richiesta di connessione tramite la funzione accept:
new_sd = accept (sd, (struct sockaddr *) &client_addr,
&client_len);
– Poiché la funzione accept è bloccante (con time-out), il programma eseguito dal servente può proseguire solo quando arriva una richiesta di connessione;
– Quando la accept è stata eseguita la connessione è associata al nuovo socket new_sd e quindi le operazioni di trasmissione e/o ricezione dati fanno riferimento a questo socket. Il socket sd può essere usato per accettare altre richieste di connessione.
- 32 -
Un servente semplice (III)
– il servente chiude la connessione con il cliente tramite la
close (new_sd);
In aggiunta il servente:
– può stabilire il numero massimo di richieste di connessione che
può accodare (MAXCONN), servendone però sempre una sola per
volta:
listen (sd, MAXCONN);
– La funzione listen serve anche per sincronizzare le richieste tra cliente
e servente ed elimina il problema di connect fallita nel caso in cui il
servente non abbia ancora eseguito la accept quando il cliente esegue la
connect.
- 33 -
bind ( )
int bind (int sd, struct sockaddr server_ep,
int ep_len)
/ Associa un numero di porta TCP a un socket, restituisce 0
se successo, -1 se errore. Parametri:
sd, specifica il socket da associare al numero di porta TCP;
server_ep, specifica il punto terminale (endpoint) contenente
il numero di porta da associare (l'indirizzo ha funzione di
filtro);
ep_len, specifica la lunghezza in byte del punto terminale /
- 34 -
accept ( )
int accept (int sd, struct sockaddr client_ep, int ep_len)
/ Accetta una richiesta di collegamento in qualità di servente, restituisce un nuovo socket (sempre >= 0) se successo, -1 se
errore; il nuovo socket restituito è quello su cui portare avanti
il dialogo con il cliente richiedente; il vecchio socket è
disponibile per ulteriori accettazioni. Parametri:
sd, specifica il socket (che deve essere già stato creato) su cui
ricevere la richiesta di collegamento proveniente dal cliente;
client_ep, specifica la locazione in cui memorizzare il punto
terminale (endpoint) del cliente;
ep_len, specifica la locazione in cui memorizzare la lunghezza in
byte del punto terminale /
Chiamata tipica: new_sd = accept (sd, (sockaddr *)&client_ep,
&ep_len);
- 35 -
Codice di UServer1 (I)
/* programma USERVER1 */
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define PORT 4000
#define MAXCONN 5
void addr_initialize ( );
void main (int argc, char * argv[]) {
int sd, new_sd;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
int client_len = sizeof (client_addr);
- 36 -
Codice di UServer1 (II)
addr_initialize (&server_addr, PORT, INADDR_ANY);
sd = socket (AF_INET, SOCK_STREAM, 0);
bind (sd, (struct sockaddr *) &server_addr, sizeof (server_addr));
listen (sd, MAXCONN);
printf ("Mi pongo in attesa di richieste di connessione\n");
printf ("sulla mia porta: %d.\n", ntohs (server_addr.sin_port));
new_sd = accept (sd, (struct sockaddr *) &client_addr, &client_len);
printf ("\n\nHo accettato una connessione\n");
printf ("dal client con porta: %d\n\n", ntohs (client_addr.sin_port));
close (new_sd);
close (sd);
} / * end main */
- 38 -
lato cliente lato servente
Indirizzo IP
socket della connessione (lato cliente)
connect( , con chi …)
numero porta del client
Indirizzo/i IP
numero porta del server
socket per accettare connessioni
(bind ..,
listen ….)
new_sd=accept( , da chi …)
socket della connessione accettata
(lato servente)
Connessione lato cliente e lato servente
- 40 -
La trasmissione
Dopo aver stabilito la connessione bidirezionale (accept
/ connect), i due processi possono scambiarsi dati
(caratteri) tramite le funzioni send e receive:
– send: spedisce dati all’altro punto terminale della connessione;
– receive: riceve dati inviati dall’altro punto terminale della
connessione.
- 41 -
Trasmissione
send
all’invocazione della funzione i byte da trasferire, presenti nelle variabili
del processo, vengono copiati in un’area di sistema (buffer di sistema) e il
processo può proseguire nell’elaborazione anche se i dati non sono ancora
arrivati a destinazione. È sospensiva per il processo che la invoca solo se il
buffer di sistema è pieno.
receive
sospensiva: all’invocazione della funzione il processo che la esegue non può
proseguire nell’elaborazione finché i dati non siano stati ricevuti e copiati
nel buffer di sistema del ricevente (a meno di chiusura di connessione o
errore). I byte ricevuti nel buffer di sistema vengono quindi copiati nelle
variabili del processo;
se sono stati ricevuti più dati di quelli copiabili nelle variabili del processo,
questi rimangono disponibili nel buffer di sistema per chiamate successive.
- 42 -
Sintassi delle primitive
int send (int sd, char message, int len, int flags)
/ Spedisce, attraverso il canale identificato da sd, len
byte memorizzati nella stringa message. Restituisce il
numero di byte effettivamente inviati, -1 se errore. Altri
param.: flags, specifica funzioni speciali, di solito 0 /
int recv (int sd, char message, int len, int flags)
/ Riceve, attraverso il canale identificato da sd, len byte e
li memorizza nella stringa message. Restituisce il numero
di byte effettivamente ricevuti, -1 se errore. Altri
param.: flags, specifica funzioni speciali, di solito 0 /
- 43 -
Esempio d’uso
Send . . .
char dati [12] = "abcdefghilm";
int num; /* numero byte da inviare */
int inviati; /* numero byte trasmessi */
num = 7;
inviati = send (sd, dati, num, 0);
Receive. . .
#define DIM . . .
char dati_ricevuti [DIM + 1];
int ricevuti; /* numero byte ricevuti */
ricevuti = recv (sd, dati_ricevuti, DIM, 0);
- 44 -
Esempio d’uso 2 - Cliente con ricezione carattere,
eco e ritrasmissione di carattere (I)
#include <stdio.h>
#include <sys/socket.h>
void main (int argc, char * argv[]) {
int sd;
struct sockaddr_in server;
int error;
char c;
/* inizializzazione pto terminale del server */
server.sin_family = AF_INET;
server.sin_port = htons ((u_short) 4500);
server.sin_addr.s_addr = inet_addr (“131.175.25.1”);
sd = socket (AF_INET, SOCK_STREAM, 0);
error = connect (sd, (struct sockaddr *) &server_addr,
sizeof (server_addr));
- 45 -
Esempio d’uso 2 - Cliente con ricezione carattere,
eco e ritrasmissione di carattere (II)
if (error == 0) {
recv (sd, &c, 1, 0);
printf (”%c\n”, c);
send (sd, &c, 1, 0);
printf ("\nChiudo la connessione.\n");
close (sd);
} else {
printf ("%s","\nErrore di connect.\n\n");
close (sd);
} /* end if */
} /* end main */
- 46 -
Ancora sul funzionamento: send
int send (int sd, char message, int len, int flags)
copia in memoria di sistema del trasmittente len byte prelevati da
message (variabile utente);
non bloccante se la memoria di sistema è sufficiente a contenere i byte da
spedire;
valore restituto: n° byte effettivamente spediti (< 0 se errore).
- 47 -
Ancora sul funzionamento - receive
int recv (int sd, char message, int len, int flags)
bloccante fino all’arrivo dei dati (o alla segnalazione dell’evento di
chiusura della connessione o d’errore);
riceve i dati depositandoli nel buffer di sistema del ricevente e copia len byte in message (variabile utente);
valore restituito:
- se n° byte effettivamente ricevuti <= len, allora valore restituito = n° byte
effettivamente ricevuti;
- se n° byte ricevuti > len,allora solo len copiati in message (i rimanenti
sono disponibili per receive successive) e valore restituito = n° byte copiati;
- se chiusura connessione valore restituito = 0, se errore valore restituito < 0.
- 48 -
Un esempio - UClient2 e Userver2
UClient2 e Userver2:
Invio da parte di un cliente di un n° variabile di caratteri
che vengono ricevuti da un servente che li visualizza.
Il numero di caratteri da inviare è specificato dall’utente
del cliente (se viene specificato 0, l’applicazione deve
terminare).
- 49 -
UClient2
UClient2:
– stabilisce la connessione con UServer2;
– chiede all’utente di inserire il numero di caratteri che si vogliono
inviare (num);
– preleva da un buffer (char dati [12]) tali caratteri e li invia al
servente;
– ripete la richiesta di num. Termina se l’utente ha indicato che
non si vogliono spedire ulteriori caratteri (num = 0), chiudendo la
connessione.
- 50 -
Codice di UClient2 (I)
#include <stdio.h>
#include <sys/socket.h>
#define PORT 4000
void addr_initialize ( );
void main (int argc, char * argv[]) {
int sd;
struct sockaddr_in server_addr;
struct sockaddr_in mio_addr;
int mio_addr_len = sizeof (mio_addr);
int error, num, inviati;
char dati [12] = "abcdefghilm";
addr_initialize (&server_addr, PORT, inet_addr (argv[1]));
sd = socket (AF_INET, SOCK_STREAM, 0);
error = connect (sd, (struct sockaddr *) &server_addr,
sizeof (server_addr));
- 51 -
Codice di UClient2 (II)
if (error == 0) {
printf ("Ho eseguito la connessione\n");
printf ("\n inserire il numero di caratteri
da trasmettere: ");
scanf ("%d", &num);
while (num > 0) {
inviati = send (sd, dati, num, 0);
printf (“ inserire il numero di caratteri
da trasmettere:");
scanf ("%d", &num);
} /* end while */
printf ("\nChiudo la connessione\n");
close (sd);
} /* end if */
else printf ("%s","\nErrore di connect\n\n");
close (sd);
} /* end main */
- 52 -
UServer2
UServer2:
– accetta la connessione (da UClient2);
– riceve un numero variabile di caratteri (ric), con un limite
massimo di DIMBUF;
– stampa a video il numero di caratteri ricevuti e i caratteri stessi.
Poi:
• si prepara per una nuova ricezione di caratteri dalla stessa
connessione;
• se non ha ricevuto caratteri (ric = 0) chiude la connessione con
UClient2 e si prepara per una nuova connessione da un nuovo
cliente.
- 53 -
Codice di UServer2 (II)
#include <stdio.h>
#include <sys/socket.h>
#define PORT 4000
#define MAXCONN 5
#define DIMBUF 6
void addr_initialize ( );
void main (int argc, char * argv[]) {
int sd,new_sd;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
int client_len = sizeof (client_addr);
int ric, i;
char buf [DIMBUF];
addr_initialize (&server_addr, PORT, INADDR_ANY);
- 54 -
Codice di UServer2 (II)
sd = socket (AF_INET, SOCK_STREAM, 0);
bind (sd, (struct sockaddr *) &server_addr, sizeof (server_addr));
listen (sd, MAXCONN);
while (1) {
printf ("\nMi pongo in attesa di richieste di connessione\n");
new_sd = accept (sd, (struct sockaddr *) &client_addr,
&client_len);
printf ("Ho accettato una connessione\n");
ric = 1;
while (ric > 0) {
ric = recv (new_sd, buf, DIMBUF, 0);
printf ("\nHo ricevuto %d caratteri: ", ric);
for (i = 0; i < ric; i++) {
printf ("%c", buf[i]);
} /* end for */
} /* end while */
close (new_sd);
printf ("chiudo la connessione\n");
} /* fine del ciclo perenne */
} /* end main*/
- 56 -
Ricezione di sequenze di caratteri da memorizzare
in una stringa (array di caratteri)
La ricezione di sequenze di caratteri da memorizzare in stringhe (array di caratteri terminate da ‘\0’) deve garantire la correttezza dell’operazione, e cioè nell’array devono venire copiati tutti e soli i caratteri della sequenza.
– Si deve inoltre ricordare che i buffer di sistema di mittente e destinatario di una trasmissione sono indipendenti tra loro. Per es., in un determinato istante il buffer di sistema del destinatario può contenere byte che provengono da due send successive.
La ricezione può avvenire:
– carattere per carattere, fino a completare la sequenza (più semplice);
– a gruppi di caratteri, fino a completare la sequenza (più veloce).
Inoltre la sequenza da ricevere può essere:
– di lunghezza fissata;
– di lunghezza variabile terminata da un carattere terminatore di fine_seq.
- 57 -
Sequenza di caratteri di lunghezza fissata
da memorizzare in una stringa
Ricezione di un singolo carattere alla volta:
char seq [DIM + 1];
int i;
i = 0;
while (i < DIM) {
recv (sd, &seq[i], 1, 0);
i++;
} /* end while */
seq[i] = ‘\0’;
0 1 DIM
\0
- 58 -
Sequenza di caratteri di lunghezza fissata
da memorizzare in una stringa
Ricezione di più caratteri alla volta:
char seq [DIM + 1];
int i;
i = 0;
while (i < DIM) {
ric = recv (sd, &seq[i], DIM - i, 0);
i = i + ric;
} /* end while */
seq[i] = ‘\0’;
- 59 -
Sequenza di caratteri terminata da carattere
terminatore da memorizzare in una stringa
Ricezione di un singolo carattere alla volta:
char seq [MAXDIM];
char term = ...;
int i;
i = 0;
do {
recv (sd, &seq[i], 1, 0);
i++;
} while (seq[i - 1] != term);
/* end do */
seq[i - 1] = ‘\0’;
0 1 MaxDIM-1
\0
- 60 -
Sequenza di caratteri terminata da carattere
terminatore da memorizzare in una stringa
Ricezione di più caratteri alla volta:
char seq [MAXDIM];
char term = ...;
int i;
i = 0;
do {
ric = recv (sd, &seq[i], MAXDIM - i, 0);
i = i + ric;
} while ((seq[i - 1] != term) && (i < MAXDIM));
/* end do */
seq[i - 1] = ‘\0’;