Strumenti Utente

Strumenti Sito


roberto.alfieri:user:reti:socket:tcp

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 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:

 netstat -an4 | grep LIESTEN              #lista i processi server in ascolto
 watch "netstat -an4 | grep TIME_WAIT"    # monitora i processi in stato TIM_WAIT

Per TIME_WAIT vedi 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:

programma echo con TCP

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 (uic.edu)

Esempi client

Sorgenti: http://didattica-linux.unipr.it/~roberto.alfieri@unipr.it/matdid/RETI/tcp/echo-tcp/

echotcp-client.cpp : 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()

Esempi: 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:

#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: echo-tcp

NOTE

Readn

Può accadere che la read() restituisca meno byte di quanti richiesti, anche se lo stream è ancora aperto. Ciò accade ad esempio 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
}

Programmi applicativi con TCP

SMTP

HTTP

Altro materiale

Socket in Python

Socket in PHP

Socket in Java

Socket in C++

roberto.alfieri/user/reti/socket/tcp.txt · Ultima modifica: 23/11/2021 20:12 da roberto.alfieri