X-Git-Url: https://gapil.gnulinux.it/gitweb/?p=gapil.git;a=blobdiff_plain;f=socket.tex;h=0d35c3ec43bea60c21f1bf3707fd05f192864f5a;hp=ba5c608d8b4fc67232600790b109ee0d39180132;hb=520fa6e7cd289a93a0955f3f91848ebd5b424250;hpb=06661f47754a536098afe2b30cb04469918f2fa3 diff --git a/socket.tex b/socket.tex index ba5c608..0d35c3e 100644 --- a/socket.tex +++ b/socket.tex @@ -1,6 +1,6 @@ %% socket.tex %% -%% Copyright (C) 2000-2002 Simone Piccardi. Permission is granted to +%% Copyright (C) 2000-2003 Simone Piccardi. Permission is granted to %% copy, distribute and/or modify this document under the terms of the GNU Free %% Documentation License, Version 1.1 or any later version published by the %% Free Software Foundation; with the Invariant Sections being "Prefazione", @@ -13,8 +13,8 @@ In questo capitolo inizieremo a spiegare le caratteristiche salienti della principale interfaccia per la programmazione di rete, quella dei -\textit{socket}, che, pur essendo nata in ambiente Unix è usata ormai da tutti -i sistemi operativi. +\textit{socket}, che, pur essendo nata in ambiente Unix, è usata ormai da +tutti i sistemi operativi. Dopo una breve panoramica sulle caratteristiche di questa interfaccia vedremo come creare un socket e come collegarlo allo specifico protocollo di rete che @@ -446,7 +446,7 @@ si usa IPv4) L'indirizzo di un socket internet (secondo IPv4) comprende l'indirizzo internet di un'interfaccia più un \textsl{numero di porta} (affronteremo in -dettaglio il significato di questi numeri in \secref{sec:TCPel_port_num}). Il +dettaglio il significato di questi numeri in \secref{sec:TCP_port_num}). Il protocollo IP non prevede numeri di porta, che sono utilizzati solo dai protocolli di livello superiore come TCP e UDP. Questa struttura però viene usata anche per i socket RAW che accedono direttamente al livello di IP, nel @@ -458,15 +458,15 @@ specifica il \textsl{numero di porta}. I numeri di porta sotto il 1024 sono chiamati \textsl{riservati} in quanto utilizzati da servizi standard e soltanto processi con i privilegi di amministratore (con user-ID effettivo uguale a zero) o con la capability \texttt{CAP\_NET\_BIND\_SERVICE} possono -usare la funzione \func{bind} (che vedremo in \secref{sec:TCPel_func_bind}) su +usare la funzione \func{bind} (che vedremo in \secref{sec:TCP_func_bind}) su queste porte. Il membro \var{sin\_addr} contiene un indirizzo internet, e viene acceduto sia come struttura (un resto di una implementazione precedente in cui questa era una \direct{union} usata per accedere alle diverse classi di indirizzi) che -direttamente come intero. In \file{netinet/in.h} vengono definiti anche alcune -costanti per alcuni indirizzi speciali, che vedremo in -\tabref{tab:TCPel_ipv4_addr}. +direttamente come intero. In \file{netinet/in.h} vengono definite anche alcune +costanti che identificano alcuni indirizzi speciali, riportati in +\tabref{tab:TCP_ipv4_addr}. Infine occorre sottolineare che sia gli indirizzi che i numeri di porta devono essere specificati in quello che viene chiamato \textit{network order}, cioè @@ -732,12 +732,11 @@ cos \label{sec:sock_addr_func} In questa sezione tratteremo delle varie funzioni usate per manipolare gli -indirizzi, limitandoci però agli indirizzi internet. - -Come accennato gli indirizzi e i numeri di porta usati nella rete devono -essere forniti in formato opportuno (il \textit{network order}). Per capire -cosa significa tutto ciò occorre introdurre un concetto generale che tornerà -utile anche in seguito. +indirizzi, limitandoci però agli indirizzi internet. Come accennato gli +indirizzi e i numeri di porta usati nella rete devono essere forniti in +formato opportuno (il \textit{network order}). Per capire cosa significa tutto +ciò occorre introdurre un concetto generale che tornerà utile anche in +seguito. \subsection{La \textit{endianess}\index{endianess}} @@ -749,15 +748,24 @@ due modi, chiamati rispettivamente \textit{big endian} e \textit{little variabili intere (ed in genere in diretta corrispondenza a come sono poi in realtà cablati sui bus interni del computer). -Per capire meglio il problema si consideri un intero a 16 bit scritto in una -locazione di memoria posta ad un certo indirizzo. I singoli bit possono essere -disposti un memoria in due modi: a partire dal più significativo o a partire -dal meno significativo. Così nel primo caso si troverà il byte che contiene i -bit più significativi all'indirizzo menzionato e il byte con i bit meno -significativi nell'indirizzo successivo; questo ordinamento è detto -\textit{little endian} dato che il dato finale è la parte ``piccola'' del -numero. Il caso opposto, in cui si parte dal bit meno significativo è detto -per lo stesso motivo \textit{big endian}. +Per capire meglio il problema si consideri un intero a 32 bit scritto in una +locazione di memoria posta ad un certo indirizzo. Come illustrato in +\figref{fig:sock_endianess} i singoli bit possono essere disposti un memoria +in due modi: a partire dal più significativo o a partire dal meno +significativo. Così nel primo caso si troverà il byte che contiene i bit più +significativi all'indirizzo menzionato e il byte con i bit meno significativi +nell'indirizzo successivo; questo ordinamento è detto \textit{big endian}, +dato che si trova per prima la parte più grande. Il caso opposto, in cui si +parte dal bit meno significativo è detto per lo stesso motivo \textit{little + endian}. + +\begin{figure}[htb] + \centering + \includegraphics[height=3cm]{img/endianess} + \caption{Schema della disposizione dei dati in memoria a seconda della + \textit{endianess}\index{endianess}.} + \label{fig:sock_endianess} +\end{figure} La \textit{endianess}\index{endianess} di un computer dipende essenzialmente dalla architettura hardware usata; Intel e Digital usano il \textit{little @@ -779,15 +787,11 @@ questi cambiamenti. 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 delle funzioni di conversione che servono a tener -conto automaticamente della possibile differenza fra l'ordinamento usato sul -computer e quello che viene usato nelle trasmissione sulla rete; queste -funzioni sono \funcd{htonl}, \funcd{htons}, \funcd{ntonl} e \funcd{ntons} ed i -rispettivi prototipi sono: +con i due byte in cui è suddiviso scambiati di posto. Per questo motivo si +usano delle funzioni di conversione che servono a tener conto automaticamente +della possibile differenza fra l'ordinamento usato sul computer e quello che +viene usato nelle trasmissione sulla rete; queste funzioni sono \funcd{htonl}, +\funcd{htons}, \funcd{ntohl} e \funcd{ntohs} ed i rispettivi prototipi sono: \begin{functions} \headdecl{netinet/in.h} \funcdecl{unsigned long int htonl(unsigned long int hostlong)} @@ -798,11 +802,11 @@ rispettivi prototipi sono: Converte l'intero a 16 bit \param{hostshort} dal formato della macchina a quello della rete. - \funcdecl{unsigned long int ntonl(unsigned long int netlong)} + \funcdecl{unsigned long int ntohl(unsigned long int netlong)} Converte l'intero a 32 bit \param{netlong} dal formato della rete a quello della macchina. - \funcdecl{unsigned sort int ntons(unsigned short int netshort)} + \funcdecl{unsigned sort int ntohs(unsigned short int netshort)} Converte l'intero a 16 bit \param{netshort} dal formato della rete a quello della macchina. @@ -957,230 +961,6 @@ Il formato usato per gli indirizzi in formato di presentazione \index{socket|)} -\section{Un esempio di applicazione} -\label{sec:sock_appplication} - -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, ritorniamo con maggiori dettagli -su una caratteristica delle funzioni di I/O, già accennata in -\secref{sec:file_read} e \secref{sec:file_write}, che nel caso dei socket è -particolarmente rilevante, e che ci tornerà utile anche in seguito. - - -\subsection{Il comportamento delle funzioni di I/O} -\label{sec:sock_io_behav} - -Una cosa che si tende a dimenticare quando si ha a che fare con i socket è che -le funzioni di input/output non sempre hanno lo stesso comportamento che -avrebbero con i normali file di dati (in particolare questo accade 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 per l'I/O su file, ma con i normali file di dati il -problema si avverte solo quando si incontra la fine del file, in generale non -è così, e con i socket questo è particolarmente evidente. - -Quando ci si trova ad affrontare questo comportamento tutto quello che si deve -fare è semplicemente ripetere la lettura (o la scrittura) per la quantità di -byte restanti, tenendo conto che 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] - \footnotesize \centering - \begin{minipage}[c]{15cm} - \includecodesample{listati/FullRead.c} - \end{minipage} - \normalsize - \caption{Funzione \func{FullRead}, legge esattamente \var{count} byte da un - file descriptor, iterando opportunamente le letture.} - \label{fig:sock_FullRead_code} -\end{figure} - -Per questo motivo, seguendo l'esempio di W. R. Stevens in \cite{UNP1}, si sono -definite due funzioni, \func{FullRead} e \func{FullWrite}, che eseguono -lettura e scrittura tenendo conto di questa caratteristica, ed in grado di -ritornare dopo avere letto o scritto esattamente il numero di byte -specificato; il sorgente è riportato rispettivamente in -\figref{fig:sock_FullRead_code} e \figref{fig:sock_FullWrite_code} ed è -disponibile fra i sorgenti allegati alla guida nei file \file{FullRead.c} e -\file{FullWrite.c}. - -\begin{figure}[htb] - \centering - \footnotesize \centering - \begin{minipage}[c]{15cm} - \includecodesample{listati/FullWrite.c} - \end{minipage} - \normalsize - \caption{Funzione \func{FullWrite}, scrive \var{count} byte su un socket.} - \label{fig:sock_FullWrite_code} -\end{figure} - -Come si può notare le funzioni ripetono la lettura/scrittura in un ciclo fino -all'esaurimento del numero di byte richiesti, in caso di errore viene -controllato se questo è \errcode{EINTR} (cioè un'interruzione della system call -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 (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. - - - -\subsection{Un primo esempio di client} -\label{sec:net_cli_sample} - -Lo scopo di questo esempio è fornire un primo approccio alla programmazione di -rete e vedere come si usano le funzioni descritte in precedenza, alcune delle -funzioni usate nell'esempio saranno trattate in dettaglio nel capitolo -successivo; qui ci limiteremo a introdurre la nomenclatura senza fornire -definizioni precise e dettagli di funzionamento che saranno trattati -estensivamente più avanti. - -In \figref{fig:net_cli_code} è riportata la sezione principale del codice del -nostro client elementare per il servizio \textit{daytime}, un servizio -standard che restituisce l'ora locale della macchina a cui si effettua la -richiesta. - -\begin{figure}[!htb] - \footnotesize \centering - \begin{minipage}[c]{15cm} - \includecodesample{listati/ElemDaytimeTCPClient.c} - \end{minipage} - \normalsize - \caption{Esempio di codice di un client elementare per il servizio daytime.} - \label{fig:net_cli_code} -\end{figure} - -Il sorgente completo del programma (\file{ElemDaytimeTCPClient.c}, che -comprende il trattamento delle opzioni e una funzione per stampare un -messaggio di aiuto) è allegato alla guida nella sezione dei codici sorgente e -può essere compilato su una qualunque macchina Linux. - -Il programma anzitutto include gli header necessari (\texttt{\small 1--5}); -dopo la dichiarazione delle variabili (\texttt{\small 9--12}) si è omessa -tutta la parte relativa al trattamento degli argomenti passati dalla linea di -comando (effettuata con le apposite routine illustrate in -\capref{sec:proc_opt_handling}). - -Il primo passo (\texttt{\small 14--18}) è creare un \textit{socket} IPv4 -(\const{AF\_INET}), di tipo TCP \const{SOCK\_STREAM}. La funzione -\func{socket} ritorna il descrittore che viene usato per identificare il -socket in tutte le chiamate successive. Nel caso la chiamata fallisca si -stampa un errore con la relativa routine e si esce. - -Il passo seguente (\texttt{\small 19--27}) è quello di costruire un'apposita -struttura \struct{sockaddr\_in} in cui sarà inserito l'indirizzo del server ed -il numero della porta del servizio. Il primo passo è inizializzare tutto a -zero, per poi inserire il tipo di protocollo e la porta (usando per -quest'ultima la funzione \func{htons} per convertire il formato dell'intero -usato dal computer a quello usato nella rete), infine si utilizza la funzione -\func{inet\_pton} per convertire l'indirizzo numerico passato dalla linea di -comando. - -Usando la funzione \func{connect} sul socket creato in precedenza -(\texttt{\small 28--32}) si provvede poi a stabilire la connessione con il -server specificato dall'indirizzo immesso nella struttura passata come secondo -argomento, il terzo argomento è la dimensione di detta struttura. Dato che -esistono diversi tipi di socket, si è dovuto effettuare un cast della -struttura inizializzata in precedenza, che è specifica per i socket IPv4. Un -valore di ritorno negativo implica il fallimento della connessione. - -Completata con successo la connessione il passo successivo (\texttt{\small - 34--40}) è leggere la data dal socket; il server invierà sempre una stringa -di 26 caratteri della forma \verb|Wed Apr 4 00:53:00 2001\r\n|, che viene -letta dalla funzione \func{read} e scritta su \file{stdout}. - -Dato il funzionamento di TCP la risposta potrà tornare in un unico pacchetto -di 26 byte (come avverrà senz'altro nel caso in questione) ma potrebbe anche -arrivare in 26 pacchetti di un byte. Per questo nel caso generale non si può -mai assumere che tutti i dati arrivino con una singola lettura, pertanto -quest'ultima deve essere effettuata in un ciclo in cui si continui a leggere -fintanto che la funzione \func{read} non ritorni uno zero (che significa che -l'altro capo ha chiuso la connessione) o un numero minore di zero (che -significa un errore nella connessione). - -Si noti come in questo caso la fine dei dati sia specificata dal server che -chiude la connessione; questa è una delle tecniche possibili (è quella usata -pure dal protocollo HTTP), ma ce ne possono essere altre, ad esempio FTP marca -la conclusione di un blocco di dati con la sequenza ASCII \verb|\r\n| -(carriage return e line feed), mentre il DNS mette la lunghezza in testa ad -ogni blocco che trasmette. Il punto essenziale è che TCP non provvede nessuna -indicazione che permetta di marcare dei blocchi di dati, per cui se questo è -necessario deve provvedere il programma stesso. - -\subsection{Un primo esempio di server} -\label{sec:net_serv_sample} - -Dopo aver illustrato il client daremo anche un esempio di un server -elementare, in grado di rispondere al precedente client. Il listato è -nuovamente mostrato in \figref{fig:net_serv_code}, il sorgente completo -(\file{ElemDaytimeTCPServer.c}) è allegato insieme agli altri file nella -directory \file{sources}. - -\begin{figure}[!htbp] - \footnotesize \centering - \begin{minipage}[c]{15cm} - \includecodesample{listati/ElemDaytimeTCPServer.c} - \end{minipage} - \normalsize - \caption{Esempio di codice di un semplice server per il servizio daytime.} - \label{fig:net_serv_code} -\end{figure} - -Come per il client si includono gli header necessari a cui è aggiunto quello -per trattare i tempi, e si definiscono alcune costanti e le variabili -necessarie in seguito (\texttt{\small 1--18}), come nel caso precedente si -sono omesse le parti relative al trattamento delle opzioni da riga di comando. - -La creazione del socket (\texttt{\small 22--26}) è analoga al caso precedente, -come pure l'inizializzazione della struttura \struct{sockaddr\_in}, anche in -questo caso si usa la porta standard del servizio daytime, ma come indirizzo -IP si il valore predefinito \const{INET\_ANY} che corrisponde ad un indirizzo -generico (\texttt{\small 27--31}). - -Si effettua poi (\texttt{\small 32--36}) la chiamata alla funzione -\func{bind} che permette di associare la precedente struttura al socket, in -modo che quest'ultimo possa essere usato per accettare connessioni su una -qualunque delle interfacce di rete locali. - -Il passo successivo (\texttt{\small 37--41}) è mettere ``in ascolto'' il -socket, questo viene effettuato con la funzione \func{listen} che dice al -kernel di accettare connessioni per il socket specificato, la funzione indica -inoltre, con il secondo parametro, il numero massimo di connessioni che il -kernel accetterà di mettere in coda per il suddetto socket. - -Questa ultima chiamata completa la preparazione del socket per l'ascolto (che -viene chiamato anche \textit{listening descriptor}) a questo punto il processo -è mandato in sleep (\texttt{\small 44--47}) con la successiva chiamata alla -funzione \func{accept}, fin quando non arriva e viene accettata una -connessione da un client. - -Quando questo avviene \func{accept} ritorna un secondo descrittore di socket, -che viene chiamato \textit{connected descriptor} che è quello che viene usato -dalla successiva chiamata alla \func{write} per scrivere la risposta al -client, una volta che si è opportunamente (\texttt{\small 48--49}) costruita -la stringa con la data da trasmettere. Completata la trasmissione il nuovo -socket viene chiuso (\texttt{\small 54}). Il tutto è inserito in un ciclo -infinito (\texttt{\small 42--55}) in modo da poter ripetere l'invio della data -ad una successiva connessione. - -È importante notare che questo server è estremamente elementare, infatti a -parte il fatto di essere dipendente da IPv4, esso è in grado di servire solo -un client alla volta, è cioè un \textsl{server iterativo}, inoltre esso è -scritto per essere lanciato da linea di comando, se lo si volesse utilizzare -come demone di sistema (che è in esecuzione anche quando non c'è nessuna shell -attiva e il terminale da cui lo si è lanciato è stato sconnesso), -occorrerebbero delle opportune modifiche. - - %%% Local Variables: %%% mode: latex