Strumenti Utente

Strumenti Sito


roberto.alfieri:user:reti:socket

Programmazione dei Socket

Riferimenti

I Berkeley Socket

Berkeley_sockets Introdotti nel 1983 dall'Universita' Berkeley della Califonia, in Unix BSD 4.2 intesi come astrazione fornita dal sistema operativo per gestire lo scambio di dati tramite le Reti con protocolli standard.

L'interfaccia dei Berkeley Socket

E' un'API, realizzata attraverso una libreria ( Socket Library), che permette la comunicazione tra processi sullo stesso host o host diversi utilizzando l'astrazione dei Socket. Sul canale di comunicazione tra 2 processi si possono leggere e scrivere dati analogamente a quanto avviene per le Pipe, ma non e' limitato a processi dello stesso host.

Tipi di comunicazione

I socket supportano 3 principali tipi di comunicazione:

  • SOCK_STREAM: canale bidirezionale, sequenziale, affidabile (flusso di byte). Gestito a livello di trasporto con il protocollo TCP
  • SOCK_DGRAM: Spedizione di pacchetti di lunghezza massima fissata (datagrammi). Gestito a livello di trasporto con il protocollo UDP
  • SOCK_RAW: accesso a basso livello delle interfacce di rete (utile ad esempio per debug)

(Altri tipi sono SOCK_SEQPACKET e SOCK_RDM)

Protocol Family (PF) e Address Family (AF)

Esistono diverse famiglie di socket. Ogni famiglia riunisce i socket che utilizzano gli stessi protocolli sottostanti, supporta un sottoinsieme di stili di comunicazione e possiede un proprio formato di indirizzamento (Address Family - AF). Esempi principali di PF e protocolli:

PF AF TIPI DESCRIZIONE
PF_UNIX AF_UNIX SOCK_STREAM stream loopback
PF_UNIX AF_UNIX SOCK_DGRAM datagram loopback
PF_INET AF_INET SOCK_STREAM TCP over IPv4
PF_INET AF_INET SOCK_DGRAM UDP over IPv4
PF_INET AF_INET SOCK_RAW IPv4 raw socket
PF_INET6 AF_INET6 SOCK_STREAM TCP over IPv6
PF_INET6 AF_INET6 SOCK_DGRAM UDP over IPv6
PF_INET6 AF_INET6 SOCK_RAW IPv6 raw socket
Indirizzi IPv4

Il socket della famiglia PF_INET utilizzano il formato di indirizzamento AF_INET a 32 bit. La struttura per memorizzare un indirizzo e':

struct in_addr{uint32_t  s_addr;}               

dove il tipo uint32_t e' un long unsigned int memorizzato in network order.

Esercizio: dig -x 160.78.48.10

Indirizzi IPv6

Il socket della famiglia PF_INET6 utilizzano il formato di indirizzamento AF_INET6 a 128 bit. La struttura per memorizzare un indirizzo e':

struct in6_addr{u_char  s6_addr[16];}          

memorizzato in network order.

Esercizio: dig -x 2001:760:ffff:ffff::aa

Endianess
  • Little Endian: Processori Intel, Digital, bus PCI
  • BIG Endian: Altri processori (IBM, Sun, Motorola, ..) , Network

Esercizio: endtest.c

Funzioni di Conversione

Funzioni di conversione del Byte Order per la comunicazione in rete. I Long servono per gli indirizzi Ipv4, gli Short per gli indirizzi di Porta.

ntohs( ) Network → Host ( uint16_t, 2 Byte)
htons( ) Host → Network ( uint16_t, 2 Byte)
ntohl( ) Network → Host ( uint32_t, 4 Byte)
htonl( ) Host → Network ( uint32_t, 4 Byte)
inet_aton(const char *src, struct in_addr *dest) ASCII → Numeric
char *inet_ntoa(struct in_addr *src) Numeric → ASCII
inet_pton(AF_INET,”1.2.3.4”, &mynet.sin_addr) Presentation → Numeric (32 bit network order)
inet_ntop(AF_INET, &mynet.sin_addr, stringa,sizeof(string)) Numeric → Presentation (stringa tipo "1.2.3.4")

Le funzioni inet_pton, inet_ntop sono piu' generali perche' non limitate ai soli indirizzi IPv4

Esercizi inaddr.c

La struttura HOSTENT: i dati dell'host

Le informazioni relative ad un host:

  • Hostname
  • Host aliases
  • Address type (AF_UNIX, AF_INET o AF_INET6)
  • Lunghezza dell’indirizzo in byte (AF_INET=4, AF_INET6=16)
  • Indirizzo (o indirizzi) (struttura in_addr)

sono memorizzate in una opportuna struttura hostent:

#include <netdb.h>

struct hostent {
char  *h_name;     //hostname
char **h_aliases;  //alias list
int   h_addrtype;  //address type
int   h_length;    //address length
char **h_addr_list;//address list 
}

#define h_addr  h_addr_list[0] 
// indirizzo principale dell’host

Funzioni:

gethostbyname(<hostname>) interroga il DNS e ritorna la struttura hostent (IPv4)
gethostbyname2(<hostname>,AF_INET6) interroga il DNS e ritorna la struttura hostent (IPv4 o IPv6)
gethostbyaddr(<IPaddr>,4,AF_INET) ritorna hostent a partire dall’indirizzo IP

Esempi:

gethostent.cpp - gethostbyaddr.c

gethostent.php: sorgente - Esegui

gethostbyname.py

GetIPAddess.java

Standard POSIX 1003.1-2001

In questo standard sono state indicate come deprecate le varie funzioni gethostbyaddr, gethostbyname, ecc ed è stata introdotta una interfaccia completamente nuova, con la struttura addrinfo che sostituisce hostent, aggiungendo campi per le informazioni relative ai socket della comunicazione (socktype, sockaddr, ..):

(vedi http://en.wikibooks.org/wiki/C_Programming/POSIX_Reference/netdb.h/getaddrinfo )

 struct addrinfo 
 { 
   int ai_flags;                 /* Input flags.  */ 
   int ai_family;                /* Protocol family for socket.  */ 
   int ai_socktype;              /* Socket type.  */ 
   int ai_protocol;              /* Protocol for socket.  */ 
   socklen_t ai_addrlen;         /* Length of socket address.  */ 
   struct sockaddr *ai_addr;     /* Socket address for socket.  */ 
   char *ai_canonname;           /* Canonical name for service location.  */ 
   struct addrinfo *ai_next;     /* Pointer to next in list.  */ 
 }; 

Le nuove funzioni getaddrinfo e getnameinfo sostituiscono gethostbyname, getaddrbyname, ecc)

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
 
int getaddrinfo(const char *hostname,   //puo' essere un  nome ("nice.com") o un indirizzo ("127.0.0.1")
                const char *service,    //puo' essere un nome ("http") o un numero ("80")
                const struct addrinfo *hints, // struttura con info aggiuntive per la richiesta
                struct addrinfo **res);       // struttura addrinfo con le info richieste

int getnameinfo(const struct sockaddr *sa, 
                          socklen_t salen,
                               char *host, 
                           size_t hostlen,
                               char *serv, 
                           size_t servlen, 
                               int flags);

Esempi:

addrinfo.c (da Wikipedia ))

showip.c (da Bjee )

addrinfo.py

addrinfo2.py

Gli altri linguaggi forniscono strumenti analoghi:

Programmazione

Modelli di programmazione

Client - Server

E' caratterizzato dalla presenza di due categorie di soggetti, i programmi di servizio, chiamati server, che ricevono le richieste e forniscono le risposte, ed i programmi di utilizzo, detti client.

In generale un server può (di norma deve) essere in grado di rispondere a più di un client, per cui è possibile che molti programmi possano interagire contemporaneamente, quello che contraddistingue il modello però è che l'architettura dell'interazione è sempre nei termini di molti verso uno, il server, che viene ad assumere un ruolo privilegiato.

Questo e' il modello nativo fornito dalla libreria Socket di Berkeley.

La comunicazione avviene tra 2 end-point (un client e un server) attraverso la rete. Ogni endpoint della comunicazione e' detto Socket ed e' identificato dai numeri dell'indirizzo di rete (IP) e dell'indirizzo logico (porta).

Peer-to-peer

Non c'e' nessun programma che svolga un ruolo preminente.

Questo vuol dire che in generale ciascun programma viene ad agire come un nodo in una rete potenzialmente paritetica; ciascun programma si trova pertanto a ricevere ed inviare richieste ed a ricevere ed inviare risposte, e non c'è più la separazione netta dei compiti che si ritrova nelle architetture client-server.

In realtà, possono essere presenti server centralizzati o nodi con funzionalità diverse rispetto agli altri (supernodi), con il compito di facilitare l'interazione tra i peer (servizio di localizzazione).

Caratteristiche: sistema altamente distribuito, nodi dinamici e autonomi.

Applicazioni principali: distribuzione di contenuti (CDN), collaborazione e comunicazione (chat, voip, ..)

Problematiche: pubblicazione, ricerca e recupero.

Approfondimenti

Nel modello client-server della Socket library di Berkeley un nodo p2p e' composto almeno da un socket client e da un socket server.

Multi Tier

Si struttura, come dice il nome, su tre o piu' livelli. Questo consente di progettare e sviluppare applicazioni modulari distribuite in rete.

In una tipica architettura a 3 livelli (Three tier) il primo livello e' quello dei client che eseguono le richieste e gestiscono l'interfaccia con l'utente mentre la parte server viene suddivisa in due livelli, introducendo un middle-tier, su cui deve appoggiarsi tutta la logica di analisi delle richieste dei client per ottimizzare l'accesso al terzo livello, che è quello che si limita a fornire i dati dinamici che verranno usati dalla logica implementata nel middle-tier per eseguire le operazioni richieste dai client.

La modularita' dell'architettura richiede la progettazione di specifiche interfacce basate su tecnologie standard come RPC e Web service, come ad esempio il servizio Portmap di linux utilizzato da applicazioni come NFS.

Vedi: rpc

Il middle-tier e' un server verso il primo livello e un client verso il terzo.

I dati del socket: la struttura SOCKADDR

Nella libreria Berkeley Socket le informazioni relative al socket sono memorizzate nella struttura sockaddr:

struct sockaddr
{ sa_family_t sa_family;    //Address Family: AF
char  sa_data[14];      }      // port, IP address, ..

sockaddr e’ una struttura generica, a cui fanno riferimento le primitive di gestione dei socket (bind, connect, listen, accept, ..) indipendentemente dall’Address Family utilizzato. Il programmatore C dovra’ invece utilizzare le strutture specifiche, per poi fare casting al momento della chiamata:

IPv4:	struct sockaddr_in  
	{ 
	sa_family_t     sin_family;   // Address Family: AF_INET
	in_port_t       sin_port;     // port, 2 byte network-order
	struct in_addr  sin_addr;     // IPv4 address network-order
	} 
IPv6:	struct sockaddr_in6  
	{ 
	sa_family_t     sin6_family;   // Address Family: AF_INET6
	in_port_t       sin6_port;     // port, 2 byte network-order
	uint32_t	        sin6_flowinfo; //
	struct in6_addr sin6_addr;     // IPv6 address network-order
	uint32_t        sin6_scpe_id;  // 
	} 

Esempio di codifica in C:

struct in_addr myaddr;
struct sockaddr_in servsockaddr;
int port 80; 
inet_aton("172.28.33.1", &myaddr);

bzero(&servsockaddr, sizeof(servsockaddr));
servsockaddr.sin_family = AF_INET;
servsockaddr.sin_port = htons(port);    
servsockaddr.sin_addr = myaddr; 

Vedi esempio: mysockaddr.c

Socketcall

Il supporto di Linux per la programmazione dei socket avviene con la chiamata socketcall(102) che e’ un multiplexer per le API:

socket() bind() listen() accept() sendto()/ recvfrom() connect() send() recv() getsockname() ..

socket ()

La socket() restituisce un File Descriptor che verra' utilizzato come handle. Si limita ad allocare le opportune strutture nella file table del kernel.

Il socket deve essere creato sia dal client che dal server.

Prototipo della funzione:

#include <sys/socket.h>
int socket(int domain, int type, int protocol)

Esempio di codifica in C:

int sockfd;
sockfd = socket(AF_INET, SOCK_DGRAM, 0)

NOTA: Grazie al protocollo ICMP il socket può riportare può riportare errori (attraverso ERRNO) relativi alla connessione. Ad esempio:

  • [ENETUNREACH] No route to the network is present
  • [ENETDOWN] The local network interface used to reach the destination is down reach the destination is down
  • [EHOSTUNREACH] The destination address specifies an unreachable host.
int sockfd;
sockfd = socket(AF_INET, SOCK_DGRAM, 0)
if (sockfd == -1) printf("error: %s\n", strerror(errno));

BUFFER

Ogni socket ha un buffer di ricezione ed uno di trasmissione. Il buffer di ricezione è usato per mantenere i dati nel kernel prima di passarli all’applicazione.

Con TCP, lo spazio disponibile è quello pubblicizzato nella finestra di TCP

Con UDP, eventuali pacchetti in overflow vengono cancellati

SO_RCVBUF e SO_SNDBUF permettono di cambiare la grandezza dei buffer

Esempio:

sysctl -a | grep tcp
net.ipv4.tcp_rmem= 4096 87380 174760 (buffer ric.  min-init-max)
net.ipv4.tcp_wmem= 4096 16384 131072 (buffer sped. min-init-max)

La dimensione dei buffer puo' essere modificata  con setsockopt(). 
SO_RCVBUF SO_SNDBUF       Imposta dimensioni del Buffer di Ricezione/Trasm

 //Esempio di raddoppio del buffer del ricevente
 int rcvBufSize;
 int sockOptSize;
 sockOptSize = sizeof(rcvBuffersize);                  //preleva la dimensione di origine del buffer
 if(getsockopt(m_socket, SOL_SOCKET, SO_RCVBUF, &rcvBufSize, &sockOptSize) < 0)
 /*gestione errore*/
 printf(“initial receive buffer size: %d\n”, rcvBufSize);        
 RcvBufSize *=2;                                                    //raddoppia la dimensione del buffer
 if(setsockopt(m_socket, SOL_SOCKET, SO_RCVBUF, &rcvBufSize, sizeof(rcvBufSize)) < 0)
 /*gestione errore*/

send()e recv() bloccante e non bloccante

La send() e' normalmente bloccante; si blocca quando il buffer in trasmissione e' pieno. Con un socket Non Bloccante di tipo TCP, se l’operazione di scrittura non può essere effettuata nemmeno in parte perché manca spazio nei buffer del TCP in cui andare a scrivere i dati, la funzione ritorna immediatamente al chiamante restituendo il codice d’errore EWOULDBLOCK. Se invece e’ rimasto spazio, viene effettuata una scrittura di una porzione di dati minore o uguale alla dimensione del buffer libero, e la write restituisce il numero di byte scritti.

  • La recv() e' per default bloccante; si blocca quando il buffer in ricezione associato al socket è vuoto.
  • Ritorna quando ci sono dati nel buffer e il numero di byte letti puo' essere inferiore al numero di byte richiesto.
  • Ritorna 0 quando non ci sono dati nel buffer e l'altro peer ha chiuso la connessione.
  • Se il buffer e' vuoto e il socket è impostato come non bloccante non ci sarà nessun blocco ma ritornerà un -1 settando la variabile di errore EWOULDBLOCK Il programma dovra' ripetere successivamente la recv() per vedere se i dati sono

effettivamente arrivati.

Esempio di codice per impostare il socket non bloccante:

 int flags, sockfd;
 sockfd = socket(....);
 if ( (flags=fcntl(sockfd,F_GETFL,0)) <0 ) exit(1);
 flags |= O_NONBLOCK;
 if ( fcntl(sockfd,F_SETFL,flags) <0 ) exit(1);

bind()

La funzione bind assegna un indirizzo locale ad un socket. Viene usata sul lato server per specificare la porta (e gli eventuali indirizzi locali) su cui poi ci si porrà in ascolto. Il prototipo della funzione è il seguente:

#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen)

Esempio di programmazione in C:

bind(sockfd, (struct sockaddr *)&servaddr,sizeof(servaddr)); 

Comunicazione a Datagrammi

Le due funzioni principali usate per la trasmissione di dati attraverso i socket UDP sono sendto e revcform()

Prototipi:

//scrive i datagrammi sul socket
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen)

//Legge i datagrammi dal socket (*)
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *from, socklen_t *fromlen)

Esempi di codice in C:

sendto(sockfd, msg, NumByteToSend , 0, (struct sockaddr *)&servaddr, socklen);

int MaxBytes=2000; 
int Len= recvfrom(sockfd, msg, MaxBytes, 0, (struct sockaddr *)&servaddr, (socklen_t *)&socklen);

Programmazione UDP in Pyhton - PHP - Java

Esempi con UDP

DGRAM : Invia un datagramma al server

ECHO : Invia un datagramma al server; il server rimanda al client il datagramma ricevuto

ECHO TIMEOUT : Invia un datagramma al server e attiva un timeout; il server rimanda al client il datagramma ricevuto con probabilita' P, tra 0 e 1, e lo scarta con probabilita' 1-P (simulando un disturbo sulla rete).

Protocolli

simplex, per canali ideali. Trasmissione dati in una sola direzione, sorgente/destinazione sempre pronti, tempo di elaborazione dati trascurabile, memoria infinita.

Stop-and-wait Il destinatario richiede tempo per l'elaborazione. Prima di inviare il prossimo dato il mittente deve ricevere un riscontro (Ack), per cui il destinatario, se sovraccarico, può moderare il tasso di invio dei dati ritardando l'invio di Ack (controllo di flusso con riscontro).

Stop-and-wait ARQ : Per canali rumorosi. I datagrammi possono andare perduti, duplicati o consegnati fuori ordine. Ogni invio viene numerato (aggiungendo una intestazione con il numero <n> del blocco) e viene attivato un timeout. Prima di inviare il prossimo dato il mittente deve ricevere un riscontro (Ack <n>). Il server scarta alcuni datagrammi in modo casuale per simulare errori sulla linea.

tftp Client tftp (per tftp vedi Wiki): l'applicazione tftp consente di trasferire file tra client e server utilizzando il trasporto a datagrammi di UDP. Il protocollo e' simile a Stop-and-wait ARQ.

Algoritmo Leaky bucket: Vedi Wiki

Algoritmo Token bucket : Vedi Wiki

Peer-to-peer: DGRAM-P2P

Programma per l'invio di un datagramma in modalita' peer-to-peer, integrando DGRAM server e client in un unica applicazione. Il server e' gestito tramite thread, mentre il client e' interattivo. Componenti:

- un thread sempre in ascolto riceve messaggi UDP e visualizza a schermo coordinate del mittente (host, porta) e messaggio (come dgram_server).

- il programma principale raccoglie interattivamente le coordinate del server da contattare (host, porta) e messaggio, quindi invia il messaggio all'host (come dgram_client).

Possibili opzioni:

- logging: salvare su file (o DB) il logging delle attivita (Send | Receive, Host, port, Time, Message)

p2p-udp

Portmapper

Se i servizi di rete sono dinamici e' utile avere un registry server (portmapper) in cui vengono registrati i servizi attivi (tipo di servizio, indirizzo ip, porta). Il client esegue un lookup sul portmapper, quindi contatta il servizio desiderato.

Architettura della comunicazione:

1) Il server registra il proprio servizio sul portmapper, indicando IP, porta, NICname ecc.

2) Il client contatta il portmapper per conoscere la lista dei servizi disponibili.

3) Il client contatta il server utilizzando le informazioni ricevute dal portmapper.

4) Il server cancella la registrazione sul portmapper.

portmapper

p2p con directory centralizzata

I sistemi p2p con directory centralizzata dispongono di un "supernodo" che ha il mapping dei nodi p2p: discovery dei peer attivi e delle relative risorse (ad esempio file shared).

Programma DGRAM-p2p con directory centralizzata:

- un thread/processo in ascolto sulla porta PORT, riceve messaggi di testo unicast da altri nodi p2p-udp in internet. I messaggi ricevuti vengono inviati a stdout.

- un thread/processo invia periodicamente un messaggio di hello al servizio p2p-udp-server del tipo "hello NIC PORT" e riceve la lista dei nodi attivi (NIC, ora:minuti:secondi, IP, porta | NIC, ora:minuti:secondi , | … ). La lista viene memorizzata in una struttura dati interna.

- un client interattivo puo' listare l'elenco dei nodi attivi e inviare un mesaggio unicast ad un NIC di destinazione del tipo "NICdest testo". Il client deve consultare la struttura dati per recuperare la coppia IP/PORT associata al NICdest e inviare un messaggio "NICmitt testo".

P2P con directory centralizzata

Note

Per vedere quali socket UDP sono in ascolto: netstat -una

Per abilitare l'invio in broadcast in C:

int yes = 1;
setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &yes, sizeof(int) );

Comunicazione a socket Stream

Approfondimenti: I socket TCP (GaPiL)

Gli stream socket consentono di creare una connessione stabile tra 2 stazioni per lo scambio di flussi in modo affidabile.

Apertura della connessione

Three Way Handshake: Per creare un canale affidabile la comunicazione dati e' preceduta da una comunicazione di servizio ( Three way handshake ) in cui client e server predispongono il canale. Client e server inviano segmenti SYN e riscontrano con ACK i SYN ricevuti.

Le opzioni TCP : Ciascun segmento SYN contiene opzioni TCP che consentono a client e server di concordare preliminarmente alcuni parametri della comunicazione. Le principali opzioni, che vengono incluse nelle estensioni dell'intestazione TCP, sono:

  • MSS (Maximum Segment Size) option. Ogni capo della comunicazione annuncia la massima dimensione dei segmenti in base alla propria interfaccia di rete.
  • Window scale option: Scala per interpretare le dimensioni delle finestre che verranno scambiate nella comunicazione.
La programmazione

Il server dopo aver creato il socket ( socket() ) e fatto il bind sulla porta di ascolto ( bind() ) predispone la coda di attesa ( listen() ) quindi si mette in attesa ( accept() ) di una richiesta di connessione da parte di un client.

Il client crea il socket ( socket() ) quindi richiede la connessione al server ( connect() ) .

A connessione avvenuta client e server possono entrambi accedere al canale (in modo paritetico) e iniziano lo scambiano dati ( send() recv() ).

In ambiente Unix i socket sono visti come file aperti e per questo e' possibile accedere al canale anche con primitive read() write()

Le operazioni di I/O su un socket sono per default bloccanti ma possono essere rese non-bloccanti. Ad esempio in C:

    ioctl(sock, FIONBIO, &true)
    fcntl(sock, F_SETFL, O_NONBLOCK)   (solo per Unix)
La terminazione della connessione

Anche per la disconnessione e' necessario un accordo tra le due parti , che in TCP avviene con lo scambio di segmenti FIN, riscontrati con ACK. A differenza dell'apertura, che avviene su iniziativa del client, la chiusura puo' avvenire su iniziativa del client o del server.

  1. Chi vuole chiudere il canale realizza la chiusura attiva, con la chiamata della funzione close(), che comporta l'invio di un segmento FIN.
  2. Chi riceve il FIN dovra'
    1. riscontrarlo con ACK (come ogni altro segmento TCP)
    2. inviare un segnale di end-of-file verso il processo che legge dati dal socket
    3. chiamare la close() per inviare un FIN a sua volta.
  3. L'altro estremo riceve il FIN, invia l'ACK

Le chiusure di connessione sono soggette procedure non corrette ( come la terminazione forzata di un processo con ctrl/C), ma anche in caso di chiusure regolari possono sempre essere presenti in rete duplicati di segmenti che arrivano a destinazione dopo la chiusura del canale. Per questo motivo chi esegue la chiusura attiva rimane per un certo tempo nello stato di TIME_WAIT prima di passare alla chiusura definitiva ( e quindi al rilascio della porta).

Se il programma che fa la chiusura attiva viene rilanciato subito dopo la chiusura puo' incontrare l'errore "Address in use". Questo puo' essere ovviato mediante l'opzione REUSEADDR del socket.

Per monitorare i processi nello stato TIME_WAIT:

 watch "netstat -an | grep TIME_WAIT"

Vedi ad esempio http://hea-www.harvard.edu/~fine/Tech/addrinuse.html

Il Client in C

La funzione connect() è usata da un client TCP per stabilire la connessione con un server TCP

main()
{
 struct sockddr_in server;
 int connect_fd = socket(AF_INET,SOCK_STREAM,0); // SOCKET CREATE
 server.sin_family=AF_INET; // IPv4
 server.sin_port=htons(1234); // server port
 inet_pton(AF_INET, “172.28.124.1”,&server.sin_addr); // server IP num
 connect(connect_fd,(struct sockaddr *)&server,..); // SOCKET CONNECT
 appl_prot_cl(connect_fd); // SOCKET READ/WRITE 
 close(connect_fd);
}
 
appl_prot_cl(int sock)   // Appl prot. Client side
{
 .. 
 read(sock)/write(sock); 
 ..
}

Programmazione TCP (by Binarytides) in

Vedi anche:

Echo client e server in pyton (by pymotw) Notare la recv() che legge solo 16 byte per volta, quindi torna a rileggere il buffer di ricezione fin che ci sono dati da leggere

Echo client e server in Java

Esempi

echotcp-client.cpp (C) : echo-tcp client. Il client apre una connessione verso il server, quindi invia un messaggio, attende la risposta e chiude.

echotcp2-client.phps: (echotcp client versione 2): Il client apre una connessione verso il server (1) , legge una stringa da stdin (2), la invia al server e attende la risposta (3), quindi torna al punto (2). L'iterazione termina quando la stringa e' "quit" o "shutdown". -

Il server in C

Nel server TCP la funzione listen() mette il socket in modalita' server (ascolto), mentre la funzione accept() attende richieste di connessione.

Server iterativo: Nel server organizzato in modo iterativo il processo che accetta la connessione gestisce anche lo scambio dati, per cui puo' gestire un solo client per volta, iterando tra accept() e le send()/secv() del protocollo applicativo.

Server concorrente La funzione accept() dopo aver stabilito la connessione ritorna un nuovo file descriptor specifico per la comunicazione con il client, mentre il file descriptor utilizzato per gestire la richiesta puo' essere riutilizzato concorrentemente per andare a gestire nuove richieste.

La concorrenza puo' essere realizzata in diversi modi:

  • Socket in modalita' non bloccante: la accept() e la recv() non bloccanti consentono al processo di testare la presenza di nuove connessioni e l'arrivo di dati dalle connessioni esistenti senza bloccarsi.
  • Server multiprocesso / multithread. Il processo padre quando riceve una una nuova richiesta apre un nuovo processo ( o thread) a cui passa il nuovo file descriptor delegandogli la gestione della comunicazione.

Server Iterativo in C

main()
{
int fd1, fd2;
struct sockddr_in server,client;
fd1 = socket(AF_INET,SOCK_STREAM,0); // SOCKET CREATE
server.sin_family=AF_INET; // Ipv4
server.sin_addr.s_addr=htonl(INADDR_ANY); // any client
server.sin_port=htons(1234); // server port xxxx
bind(fd1,(struct sockaddr *)&server,sizeof(server));//SOCKET BIND
listen(fd1, MAX_NUM_PENDING_CONN); //SOCKET LISTEN
while(1){
   fd2=accept(fd1,(struct sockaddr *)&client,.. );//SOCK ACCEPT
   appl_prot_serv(fd2); // SOCKET READ/WRITE
   close(fd2); 
   }
close (fd1);
}
 
appl_prot_serv(int sock)   // Appl prot server side
{
 ....; 
 recv() / send() ; 
  ... ; 
} 
//nota: nella funzione appl_prot_serv() possiamo accedere alla
//struttura sockaddr_in utilizzando la funzione getsockname()

Esempio: echotcp-serv-iter.cpp

echotcp2-server.phps

Server concorrente con Processi in C

int fd1, fd2;
 
main()
{
 struct sockddr_in server,client;
 fd1 = socket(AF_INET,SOCK_STREAM,0); // SOCKET CREATE
 server.sin_family=AF_INET; // Ipv4
 server.sin_addr.s_addr=htonl(INADDR_ANY); // any client
 server.sin_port=htons(1234); // server port xxxx
 bind(fd1,(struct sockaddr *)&server,sizeof(server)); //SOCK BIND
 listen(fd1, MAX_NUM_PENDING_CONN); //SOCK LISTEN
 while(1)
   {
   fd2=accept(fd1,(struct sockaddr *)&client,.); //SOCK ACCEPT
   if ((pid=fork())==0) //FIGLIO
    {
     close(fd1);
     appl_prot_serv(fd2); //SOCK READ/WRITE
     close(fd2); 
     exit(0);
    }
   else close(fd2); //PADRE 
 
   }
close (fd1);
}
 
appl_prot_serv(int sock)  //appl_prot server side
{
 ....; 
 recv() / send();
} 

Esempi: echotcp-serv-proc.cpp

Per gestire correttamente la terminazione dei processi figli occorre usare le signal:

http://users.lilik.it/~mirko/gapil/gapilsu150.html

#include <signal.h>
#include <sys/wait.h>
signal(SIGCHLD,HandSigCHLD);
void HandSigCHLD(int sig)
{
int status;
pid_t wpid;
wpid=waitpid(WAIT_ANY,&status,WNOHANG);
if (wpid>0) printf(“SIGCHLD: Child %d terminated. Status %x \n”; wpid,
status);
return;
}

Server concorrente con Thread in C

#include <pthread.h>
pthread_t mythread;
 
main()
{
 struct sockddr_in server,client;
 int fd1,fd2;
 fd1 = socket(AF_INET,SOCK_STREAM,0); // SOCKET CREATE
 server.sin_family=AF_INET; // IPv4
 server.sin_addr.s_addr=htonl(INADDR_ANY); // any client
 server.sin_port=htons(1234); // server port xxxx
 bind(fd1,(struct sockaddr *)&server,sizeof(server)); //SOCK BIND
 listen(fd1, MAX_NUM_PENDING_CONN); //SOCK LISTEN
 while(1)
  {
  fd2=accept(fd1,(struct sockaddr *)&client,.); //SOCK ACCEPT
  pthread_create(&mythread, NULL, appl_prot_serv, (void *)&fd2);
  }
 close (fd1);
}
 
void *appl_prot_serv(void *p)
{ 
  int sock=*(int *)p;
  .... ; 
  recv() / send(); 
  close (sock); 
}

Esempi: echotcp-serv-thread.cpp (C)

echotcp-serv-thread.cpp (C++ Boost)

server.java - Runner.java (echotcp server in java)

echotcp-server-thread.py

Programmi applicativi con TCP

HTTP

Il protocollo applicativo http puo' essere emulati con telnet.

Esempio:

telnet lpr-bastion.fis.unipr.it 80
Trying 2001:760:2e04:1033::a0...
Connected to lpr-bastion.fis.unipr.it.
Escape character is '^]'.
GET /index.html HTTP/1.0

HTTP/1.1 200 OK
Date: Mon, 29 Dec 2014 18:49:09 GMT
Server: Apache/2.2.3 (Scientific Linux)
Last-Modified: Sun, 06 Oct 2013 14:19:03 GMT
ETag: "113a31-f0-4e81337adc3c0"
Accept-Ranges: bytes
Content-Length: 240
Connection: close
Content-Type: text/html; charset=UTF-8

<head>
<META HTTP-EQUIV="expires" CONTENT="Mon, 24 Mar 2014 08:21:57 GMT">
</head>
<body>
This is the body
</body>
Connection closed by foreign host.

SMTP

Altri esercizi TCP

NOTE

Readn

Può accadere che la read() restituisca meno byte di quanti richiesti, anche se lo stream è ancora aperto. Ciò accade se il buffer a disposizione del socket nel kernel è stato esaurito. Sarà necessario ripetere la read (richiedendo il numero dei byte mancanti) fino ad ottenerli tutti

Esempio di funzione in C per leggere n byte:

ssize_t readn (int fd, char *buf, size_t n)
{
size_t nleft; ssize_t nread;
nleft = n;
while (nleft > 0) {
  if ( (nread = read(fd, buf+n-nleft, nleft)) < 0) {
    if (errno != EINTR)
        return(-1); // restituisco errore
  }
else if (nread == 0) {
         // EOF, connessione chiusa, termino
         // esce e restituisco il numero di byte letti
        break;
}

else // continuo a leggere
   nleft -= nread;
}
return(n - nleft); // return >= 0
}

Programmazione con altri linguaggi

Socket in Python

Socket in PHP

Socket in Java

Socket in C++

roberto.alfieri/user/reti/socket.txt · Ultima modifica: 08/11/2019 18:24 da roberto.alfieri