From df52b4351217a5ca93506df97a34f6fba69474f0 Mon Sep 17 00:00:00 2001 From: Simone Piccardi Date: Sun, 8 Dec 2002 13:44:13 +0000 Subject: [PATCH] Revisione di alcune funzioni e rilettura generale. --- elemtcp.tex | 83 +++++++++++++++++----------- socket.tex | 155 ++++++++++++++++++++++++++++++---------------------- 2 files changed, 142 insertions(+), 96 deletions(-) diff --git a/elemtcp.tex b/elemtcp.tex index 3e2b0c7..111b0f2 100644 --- a/elemtcp.tex +++ b/elemtcp.tex @@ -489,7 +489,7 @@ relative tabelle. \begin{figure}[!htb] \centering - \includegraphics[width=10cm]{img/port_alloc} + \includegraphics[width=15cm]{img/port_alloc} \caption{Allocazione dei numeri di porta} \label{fig:TCPel_port_alloc} \end{figure} @@ -649,10 +649,7 @@ ci si porr \begin{prototype}{sys/socket.h} {int bind(int sockfd, const struct sockaddr *serv\_addr, socklen\_t addrlen)} - Il primo argomento è un file descriptor ottenuto da una precedente chiamata - a \func{socket}, mentre il secondo e terzo argomento sono rispettivamente - l'indirizzo (locale) del socket e la dimensione della struttura che lo - contiene, secondo quanto già trattato in \secref{sec:sock_sockaddr}. + Assegna un indirizzo ad un socket. \bodydesc{La funzione restituisce zero in caso di successo e -1 per un errore; in caso di errore la variabile \var{errno} viene impostata secondo @@ -666,6 +663,11 @@ ci si porr \end{errlist}} \end{prototype} +Il primo argomento è un file descriptor ottenuto da una precedente chiamata a +\func{socket}, mentre il secondo e terzo argomento sono rispettivamente +l'indirizzo (locale) del socket e la dimensione della struttura che lo +contiene, secondo quanto già trattato in \secref{sec:sock_sockaddr}. + Con il TCP la chiamata \func{bind} permette di specificare l'indirizzo, la porta, entrambi o nessuno dei due. In genere i server utilizzano una porta nota che assegnano all'avvio, se questo non viene fatto è il kernel a @@ -676,7 +678,8 @@ per il server\footnote{un'eccezione a tutto ci viene registrata presso il \textit{portmapper}; quest'ultimo è un altro demone che deve essere contattato dai client per ottenere la porta effimera su cui si trova il server.} che in genere viene identificato dalla porta su -cui risponde. +cui risponde (l'elenco di queste porte, e dei relativi servizi, è in +\file{/etc/services}). Con \func{bind} si può assegnare un IP specifico ad un socket, purché questo appartenga ad una interfaccia della macchina. Per un client TCP questo @@ -697,34 +700,50 @@ Per specificare un indirizzo generico con IPv4 si usa il valore è pari a zero, nell'esempio \figref{fig:net_serv_code} si è usata un'assegnazione immediata del tipo: -\footnotesize \begin{lstlisting}[labelstep=0,frame=,indent=1cm]{} - serv_add.sin_addr.s_addr = htonl(INADDR_ANY); /* connect from anywhere */ +serv_add.sin_addr.s_addr = htonl(INADDR_ANY); /* connect from anywhere */ \end{lstlisting} -\normalsize Si noti che si è usato \func{htonl} per assegnare il valore -\const{INADDR\_ANY}; benché essendo questo pari a zero il riordinamento sia -inutile; ma dato che tutte le costanti \val{INADDR\_} sono definite -secondo l'ordinamento della macchina è buona norma usare sempre la funzione -\func{htonl}. - -L'esempio precedete funziona con IPv4 dato che l'indirizzo è rappresentabile -anche con un intero a 32 bit; non si può usare lo stesso metodo con IPv6, -in cui l'indirizzo è specificato come struttura, perché il linguaggio C non -consente l'uso di una struttura costante come operando a destra in una -assegnazione. - -Per questo nell'header \file{netinet/in.h} è definita una variabile +\const{INADDR\_ANY}, benché essendo questo pari a zero il riordinamento sia +inutile. Si tenga presente comunque che tutte le costanti \val{INADDR\_} +(riportate in ) sono definite secondo l'ordinamento della macchina, ed anche +se esse possono essere invarianti rispetto all'ordinamento, è comunque buona +norma usare sempre la funzione \func{htonl}. + +\begin{table}[htb] + \centering + \footnotesize + \begin{tabular}[c]{|l|l|} + \hline + \textbf{Costante} & \textbf{Significato} \\ + \hline + \hline + \const{INADDR\_ANY} & Indirizzo generico (\texttt{0.0.0.0})\\ + \const{INADDR\_BROADCAST}& Indirizzo di \textit{broadcast}.\\ + \const{INADDR\_LOOPBACK} & Indirizzo di \textit{loopback} + (\texttt{127.0.0.1}).\\ + \const{INADDR\_NONE} & Indirizzo errato.\\ + \hline + \end{tabular} + \caption{Costanti di definizione di alcuni indirizzi generici per IPv4.} + \label{tab:TCPel_ipv4_addr} +\end{table} + +L'esempio precedente funziona correttamente con IPv4 poiché che l'indirizzo è +rappresentabile anche con un intero a 32 bit; non si può usare lo stesso +metodo con IPv6, in cui l'indirizzo deve necessariamente essere specificato +con una struttura, perché il linguaggio C non consente l'uso di una struttura +costante come operando a destra in una assegnazione. + +Per questo motivo nell'header \file{netinet/in.h} è definita una variabile \type{in6addr\_any} (dichiarata come \ctyp{extern}, ed inizializzata dal sistema al valore \const{IN6ADRR\_ANY\_INIT}) che permette di effettuare una -assegnazione del tipo: +assegnazione del tipo: -\footnotesize \begin{lstlisting}[labelstep=0,frame=,indent=1cm]{} - serv_add.sin6_addr = in6addr_any; /* connect from anywhere */ +serv_add.sin6_addr = in6addr_any; /* connect from anywhere */ \end{lstlisting} -\normalsize \subsection{La funzione \func{connect}} @@ -735,14 +754,10 @@ connessione con un server TCP, il prototipo della funzione \begin{prototype}{sys/socket.h} {int connect(int sockfd, const struct sockaddr *servaddr, socklen\_t addrlen)} - Il primo argomento è un file descriptor ottenuto da una precedente chiamata - a \func{socket}, mentre il secondo e terzo argomento sono rispettivamente - l'indirizzo e la dimensione della struttura che contiene l'indirizzo del - socket, già descritta in \secref{sec:sock_sockaddr}. + Stabilisce una connessione fra due socket. \bodydesc{La funzione restituisce zero in caso di successo e -1 per un - errore, in caso di errore la variabile \var{errno} viene impostata secondo - i seguenti codici di errore: + errore, in caso di errore \var{errno} assumerà i valori: \begin{errlist} \item[\errcode{ECONNREFUSED}] non c'è nessuno in ascolto sull'indirizzo remoto. @@ -766,6 +781,12 @@ connessione con un server TCP, il prototipo della funzione \errval{ENOTSOCK}, \errval{EISCONN} e \errval{EADDRINUSE}.} \end{prototype} +Il primo argomento è un file descriptor ottenuto da una precedente chiamata a +\func{socket}, mentre il secondo e terzo argomento sono rispettivamente +l'indirizzo e la dimensione della struttura che contiene l'indirizzo del +socket, già descritta in \secref{sec:sock_sockaddr}. + + La struttura dell'indirizzo deve essere inizializzata con l'indirizzo IP e il numero di porta del server a cui ci si vuole connettere, come mostrato nell'esempio \secref{sec:net_cli_sample} usando le funzioni illustrate in diff --git a/socket.tex b/socket.tex index 54e7d48..687ccc2 100644 --- a/socket.tex +++ b/socket.tex @@ -560,7 +560,7 @@ cosa significa tutto ci utile anche in seguito. -\subsection{La \textit{endianess}} +\subsection{La \textit{endianess}\index{endianess}} \label{sec:sock_endianess} La rappresentazione di un numero binario in un computer può essere fatta in @@ -579,12 +579,12 @@ significativi nell'indirizzo successivo; questo ordinamento numero. Il caso opposto, in cui si parte dal bit meno significativo è detto per lo stesso motivo \textit{big endian}. -La \textit{endianess} di un computer dipende essenzialmente dalla architettura -hardware usata; Intel e Digital usano il \textit{little endian}, Motorola, -IBM, Sun (sostanzialmente tutti gli altri) usano il \textit{big endian}. Il -formato della rete è anch'esso \textit{big endian}, altri esempi sono quello -del bus PC, che è \textit{little endian}, o quello del bus VME che è -\textit{big endian}. +La \textit{endianess}\index{endianess} di un computer dipende essenzialmente +dalla architettura hardware usata; Intel e Digital usano il \textit{little + endian}, Motorola, IBM, Sun (sostanzialmente tutti gli altri) usano il +\textit{big endian}. Il formato della rete è anch'esso \textit{big endian}, +altri esempi sono quello del bus PCI, che è \textit{little endian}, o quello +del bus VME che è \textit{big endian}. Esistono poi anche dei processori che possono scegliere il tipo di formato all'avvio e alcuni che, come il PowerPC o l'Intel i860, possono pure passare @@ -596,11 +596,12 @@ questi cambiamenti. \subsection{Le funzioni per il riordinamento} \label{sec:sock_func_ord} -Il problema connesso all'endianess è che quando si passano dei dati da un tipo -di architettura all'altra i dati vengono interpretati in maniera diversa, e ad -esempio nel caso dell'intero a 16 bit ci si ritroverà con i due byte in cui è -suddiviso scambiati di posto, e ne sarà quindi invertito l'ordine di lettura -per cui, per riavere il valore originale dovranno essere rovesciati. +Il problema connesso all'endianess\index{endianess} è che quando si passano +dei dati da un tipo di architettura all'altra i dati vengono interpretati in +maniera diversa, e ad esempio nel caso dell'intero a 16 bit ci si ritroverà +con i due byte in cui è suddiviso scambiati di posto, e ne sarà quindi +invertito l'ordine di lettura per cui, per riavere il valore originale +dovranno essere rovesciati. Per questo motivo si usano le seguenti funzioni di conversione che servono a tener conto automaticamente della possibile differenza fra l'ordinamento usato @@ -647,8 +648,8 @@ assicurare la portabilit \label{sec:sock_func_ipv4} Un secondo insieme di funzioni di manipolazione serve per passare dal formato -binario usato nelle strutture degli indirizzi alla rappresentazione dei numeri -IP che si usa normalmente. +binario usato nelle strutture degli indirizzi alla rappresentazione simbolica +dei numeri IP che si usa normalmente. Le prime tre funzioni di manipolazione riguardano la conversione degli indirizzi IPv4 da una stringa in cui il numero di IP è espresso secondo la @@ -656,29 +657,44 @@ cosiddetta notazione \textit{dotted-decimal}, (cio \texttt{192.160.0.1}) al formato binario (direttamente in \textit{network order}) e viceversa; in questo caso si usa la lettera \texttt{a} come mnemonico per indicare la stringa. Dette funzioni sono: -\begin{prototype}{arpa/inet.h} - {int inet\_aton(const char *src, struct in\_addr *dest)} - Converte la stringa puntata da \var{src} nell'indirizzo binario da - memorizzare all'indirizzo puntato da \var{dest}, restituendo 0 in caso di - successo e 1 in caso di fallimento (è espressa in questa forma in modo da - poterla usare direttamente con il puntatore usato per passare la struttura - degli indirizzi). Se usata con \var{dest} inizializzato a \val{NULL} - effettua la validazione dell'indirizzo. -\end{prototype} -\begin{prototype}{arpa/inet.h}{in\_addr\_t inet\_addr(const char *strptr)} - Restituisce l'indirizzo a 32 bit in network order a partire dalla stringa - passata come parametro, in caso di errore restituisce il valore - \const{INADDR\_NONE} che tipicamente sono trentadue bit a uno; questo - comporta che la stringa \texttt{255.255.255.255}, che pure è un indirizzo - valido, non può essere usata con questa funzione; per questo motivo essa è - generalmente deprecata in favore della precedente. -\end{prototype} -\begin{prototype}{arpa/inet.h}{char *inet\_ntoa(struct in\_addr addrptr)} - Converte il valore a 32 bit dell'indirizzo (espresso in \textit{network - order}) restituendo il puntatore alla stringa che contiene l'espressione - in formato dotted decimal. Si deve tenere presente che la stringa risiede in - memoria statica, per cui questa funzione non è rientrante. -\end{prototype} +\begin{functions} + \headdecl{arpa/inet.h} + + \funcdecl{in\_addr\_t inet\_addr(const char *strptr)} Converte la stringa + dell'indirizzo \textit{dotted decimal} in nel numero IP in network order. + + \funcdecl{int inet\_aton(const char *src, struct in\_addr *dest)} Converte + la stringa dell'indirizzo \textit{dotted decimal} in un indirizzo IP. + + \funcdecl{char *inet\_ntoa(struct in\_addr addrptr)} + Converte un indirizzo IP in una stringa \textit{dotted decimal}. + + \bodydesc{Tutte queste le funzioni non generano codice di errore.} +\end{functions} + +La prima funcione, \func{inet\_addr}, restituisce l'indirizzo a 32 bit in +network order (del tipo \type{in\_addr\_t}) a partire dalla stringa passata +nellargomento \param{strptr}. In caso di errore (quando la stringa non esprime +un indirizzo valido) restituisce invece il valore \const{INADDR\_NONE} che +tipicamente sono trentadue bit a uno. Questo però comporta che la stringa +\texttt{255.255.255.255}, che pure è un indirizzo valido, non può essere usata +con questa funzione; per questo motivo essa è generalmente deprecata in favore +di \func{inet\_aton}. + +La funzione \func{inet\_aton} converte la stringa puntata da \param{src} +nell'indirizzo binario che viene memorizzato nell'opportuna struttura +\var{in\_addr} (si veda \secref{fig:sock_sa_ipv4_struct}) situata +all'indirizzo dato dall'argomento \param{dest} (è espressa in questa forma in +modo da poterla usare direttamente con il puntatore usato per passare la +struttura degli indirizzi). La funzione restituesce 0 in caso di successo e 1 +in caso di fallimento. Se usata con \var{dest} inizializzato a \val{NULL} +effettua la validazione dell'indirizzo. + +L'ultima funzione, \func{inet\_ntoa}, converte il valore a 32 bit +dell'indirizzo (espresso in \textit{network order}) restituendo il puntatore +alla stringa che contiene l'espressione in formato dotted decimal. Si deve +tenere presente che la stringa risiede in memoria statica, per cui questa +funzione non è rientrante. \subsection{Le funzioni \func{inet\_pton} e \func{inet\_ntop}} @@ -709,7 +725,7 @@ prototipi delle suddette funzioni sono i seguenti: Converte l'indirizzo espresso tramite una stringa nel valore numerico. \bodydesc{La funzione restituisce un valore negativo se \var{af} specifica - una famiglia di indirizzi non valida, settando \var{errno} a + una famiglia di indirizzi non valida, con \var{errno} che assume il valore \errcode{EAFNOSUPPORT}, un valore nullo se \param{src} non rappresenta un indirizzo valido, ed un valore positivo in caso di successo.} \end{prototype} @@ -724,21 +740,26 @@ indirizzi non valida. \begin{prototype}{sys/socket.h} {char *inet\_ntop(int af, const void *addr\_ptr, char *dest, size\_t len)} - Converte la struttura dell'indirizzo puntata da \var{addr\_ptr} in una - stringa che viene copiata nel buffer puntato dall'indirizzo \var{dest}; - questo deve essere preallocato dall'utente e la lunghezza deve essere almeno - \const{INET\_ADDRSTRLEN} in caso di indirizzi IPv4 e - \const{INET6\_ADDRSTRLEN} per indirizzi IPv6; la lunghezza del buffer deve - comunque venire specificata attraverso il parametro \var{len}. + Converte l'indirizzo dalla relativa struttura in una stringa simbolica. - \bodydesc{La funzione restituisce un puntatore non nullo a \var{dest} in - caso di successo e un puntatore nullo in caso di fallimento, in - quest'ultimo caso viene impostata la variabile \var{errno} con il valore - \errval{ENOSPC} in caso le dimensioni dell'indirizzo eccedano la lunghezza - specificata da \var{len} o \errval{ENOAFSUPPORT} in caso \var{af} non sia - una famiglia di indirizzi valida.} + \bodydesc{La funzione restituisce un puntatore non nullo alla stringa + convertita in caso di successo e \val{NULL} in caso di fallimento, nel + qual caso \var{errno} assume i valor: + \begin{errlist} + \item[\errcode{ENOSPC}] le dimensioni della stringa con la conversione + dell'indirizzo eccedono la lunghezza specificata da \var{len}. + \item[\errcode{ENOAFSUPPORT}] la famiglia di indirizzi \var{af} non è una + valida. + \end{errlist}} \end{prototype} +La funzione converte la struttura dell'indirizzo puntata da \var{addr\_ptr} in +una stringa che viene copiata nel buffer puntato dall'indirizzo \var{dest}; +questo deve essere preallocato dall'utente e la lunghezza deve essere almeno +\const{INET\_ADDRSTRLEN} in caso di indirizzi IPv4 e \const{INET6\_ADDRSTRLEN} +per indirizzi IPv6; la lunghezza del buffer deve comunque venire specificata +attraverso il parametro \var{len}. + Gli indirizzi vengono convertiti da/alle rispettive strutture di indirizzo (\var{struct in\_addr} per IPv4, e \var{struct in6\_addr} per IPv6), che devono essere precedentemente allocate e passate attraverso il puntatore @@ -746,7 +767,7 @@ devono essere precedentemente allocate e passate attraverso il puntatore nullo e deve essere allocato precedentemente. Il formato usato per gli indirizzi in formato di presentazione è la notazione -\textit{dotted decimal} per IPv4 e quella descritta in +\textit{dotted decimal} per IPv4 e quello descritto in \secref{sec:IP_ipv6_notation} per IPv6. @@ -756,8 +777,9 @@ Il formato usato per gli indirizzi in formato di presentazione Per evitare di rendere questa introduzione ai socket puramente teorica iniziamo con il mostrare un esempio di un client TCP elementare. Prima di -passare agli esempi del client e del server, esamineremo una caratteristica -delle funzioni di I/O sui socket che ci tornerà utile anche in seguito. +passare agli esempi del client e del server, ritorniamo con maggiori dettagli +su una caratteristica delle funzioni di I/O che nel caso dei socket è +particolarmente rilevante, e che ci tornerà utile anche in seguito. \subsection{Il comportamento delle funzioni di I/O} @@ -771,13 +793,15 @@ per i socket di tipo stream). Infatti con i socket è comune che funzioni come \func{read} o \func{write} possano restituire in input o scrivere in output un numero di byte minore di quello richiesto. Come già accennato in \secref{sec:file_read} questo è un -comportamento normale anche per l'I/O su file, e succede -perché si eccede in lettura o scrittura il limite di buffer del kernel. +comportamento normale per l'I/O su file; con i normali file di dati il +problema si avverte solo quando si incontra la fine del file, ma in generale +non è così. In questo caso tutto quello che il programma chiamante deve fare è di ripetere -la lettura (o scrittura) per la quantità di byte rimanenti (lo stesso può -avvenire scrivendo più di 4096 byte in una pipe, dato che quello è il limite -di solito adottato per il buffer di trasmissione del kernel). +la lettura (o scrittura) per la quantità di byte rimanenti (e le funzioni si +possono bloccare se i dati non sono disponibili): è lo stesso comportamento +che si può avere scrivendo più di \const{PIPE\_BUF} byte in una pipe (si +riveda quanto detto in \secref{sec:ipc_pipes}). \begin{figure}[htb] \centering @@ -811,11 +835,11 @@ ssize_t SockRead(int fd, void *buf, size_t count) \label{fig:sock_SockRead_code} \end{figure} -Per questo motivo seguendo l'esempio di W. R. Stevens si sono definite due -funzioni \func{SockRead} e \func{SockWrite} che eseguono la lettura da un -socket tenendo conto di questa caratteristica, ed in grado di ritornare dopo -avere letto o scritto esattamente il numero di byte specificato; il sorgente è -riportato in \figref{fig:sock_SockRead_code} e +Per questo motivo, seguendo l'esempio di W. R. Stevens in \cite{UNP1}, si sono +definite due funzioni \func{SockRead} e \func{SockWrite} che eseguono la +lettura da un socket tenendo conto di questa caratteristica, ed in grado di +ritornare dopo avere letto o scritto esattamente il numero di byte +specificato; il sorgente è riportato in \figref{fig:sock_SockRead_code} e \figref{fig:sock_SockWrite_code} ed è disponibile fra i sorgenti allegati alla guida nei files \file{SockRead.c} e \file{SockWrite.c}. @@ -856,8 +880,9 @@ dovuta ad un segnale), nel qual caso l'accesso viene ripetuto, altrimenti l'errore viene ritornato interrompendo il ciclo. Nel caso della lettura, se il numero di byte letti è zero, significa che si è -arrivati alla fine del file e pertanto si ritorna senza aver concluso la -lettura di tutti i byte richiesti. +arrivati alla fine del file (per i socket questo significa in genere che +l'altro capo è stato chiuso, e non è quindi più possibile leggere niente) e +pertanto si ritorna senza aver concluso la lettura di tutti i byte richiesti. -- 2.30.2