La programmazione di rete - Intranet...

60
La programmazione di rete Introduzione alla programmazione di rete La connessione La trasmissione 20 febbraio 2004

Transcript of La programmazione di rete - Intranet...

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)

La programmazione di rete

La connessione

- 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 */

- 37 -

UClient1 e UServer1 in esecuzione

- 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

La programmazione di rete

La trasmissione

- 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*/

- 55 -

- 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’;