possibilità di gestire più canali di comunicazione fra due macchine
utilizzando le porte. In questo caso il server dovrà usare comunque la
funzione \func{bind} per scegliere la porta su cui ricevere i dati, e come nel
-caso dei socket TCP si potrà usare il comando \cmd{netstat}\footnote{per
- ottenere il risultato mostrato occorre usare le opzioni \texttt{-anu}.} per
+caso dei socket TCP si potrà usare il comando \cmd{netstat} per
verificare quali socket sono in ascolto:
\begin{verbatim}
+[piccardi@gont gapil]# netstat -anu
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State
udp 0 0 0.0.0.0:32768 0.0.0.0:*
sorgente e destinazione dei dati.
Per questo motivo nel caso di UDP diventa essenziale utilizzare queste due
-funzioni, che sono comunque usabili in generale per la trasmissione di dati
-attraverso qualunque tipo di socket. Esse hanno la caratteristica di prevedere
-tre argomenti aggiuntivi attraveso i quali è possibile specificare la
-destinazione o l'origine dei dati trasmessi. La prima di queste funzioni è
+funzioni, che sono comunque utilizzabili in generale per la trasmissione di
+dati attraverso qualunque tipo di socket. Esse hanno la caratteristica di
+prevedere tre argomenti aggiuntivi attraveso i quali è possibile specificare
+la destinazione o l'origine dei dati trasmessi. La prima di queste funzioni è
\funcd{sendto} ed il suo prototipo\footnote{il prototipo illustrato è quello
utilizzato dalle \acr{glibc}, che seguono le \textit{Single Unix
Specification}, l'argomento \param{flags} era di tipo \type{int} nei vari
\end{functions}
I primi tre argomenti sono identici a quelli della funzione \func{write} e
-specificano il socket \param{sockfd} a cui si fa riferimento ed il buffer
-\param{buf} (e relativa lunghezza \param{len}) che contiene i dati da inviare.
+specificano il socket \param{sockfd} a cui si fa riferimento, il buffer
+\param{buf} che contiene i dati da inviare e relativa lunghezza \param{len}.
Come per \func{write} la funzione ritorna il numero di byte inviati; nel caso
di UDP però questo deve sempre corrispondere alla dimensione totale
specificata da \param{len} in quanto i dati vengono sempre inviati in forma di
pacchetto e non possono essere spezzati in invii successivi. Qualora non ci
sia spazio nel buffer di uscita la funzione si blocca (a meno di non avere
aperto il socket in modalità non bloccante), se invece non è possibile inviare
-il messaggio all'interno di un unico pacchetto essa fallisce con l'errore di
-\errcode{EMSGSIZE}.
+il messaggio all'interno di un unico pacchetto (ad esempio perché eccede le
+dimensioni massime del protocollo sottostante utilizzati) essa fallisce con
+l'errore di \errcode{EMSGSIZE}.
I due argomenti \param{to} e \param{tolen} servono a specificare la
-destinazione del messaggio da inviare, e prendono l'indirizzo di quest'ultima,
-nella stessa forma in cui lo si specificherebbe per \func{connect}: \param{to}
-deve cioè contenere l'indirizzo IP e la porta di destinazione cui si vogliono
-inviare i dati e \param{tolen} la relativa dimensione.
-
-Se il socket è di un tipo che prevede le connessioni, questo deve essere già
-connesso prima di eseguire la funzione (altrimenti si avrà un errore di
-\errcode{ENOTCONN}) ed inoltre questi due ultimi argomenti devono essere
+destinazione del messaggio da inviare, e indicano rispettivamente la struttura
+contentento l'indirizzo di quest'ultima e la relativa lunghezza. Questi
+argomenti vanno specificati stessa forma in cui lo si userebbero con
+\func{connect}: \param{to} deve cioè puntare alla struttura contenente
+l'indirizzo IP e la porta di destinazione verso cui si vogliono inviare i dati
+mentre \param{tolen} specifica la dimensione di quest'ultima.
+
+Se il socket è di un tipo che prevede le connessioni (ad esempio qualora si
+usi la funzione con un socket TCP), questo deve essere già connesso prima di
+eseguire la funzione, altrimenti si avrà un errore di \errcode{ENOTCONN};
+inoltre in questo caso gli argomenti \param{to} e \param{tolen} devono essere
inizializzati rispettivamente a \const{NULL} e 0 (di solito vengono ignorati,
-ma si potrebbe ricevere altrimenti anche un errore di \errcode{EISCONN}).
+ma si potrebbe anche ricevere un errore di \errcode{EISCONN}).
Infine l'argomento \param{flags} è un intero usato come maschera binaria che
permette di impostare una serie di modalità di funzionamento della
usare sempre un valore nullo.
La seconda funzione utilizzata nella comunicazione fra socket UDP è
-\funcd{recvfrom} che serve invece a ricevere i dati inviati da un altro
-socket, il suo prototipo\footnote{il prototipo è quello delle \acr{glibc} che
- seguono le \textit{Single Unix Specification}, i vari BSD4.*, le \acr{libc4}
- e le \acr{libc5} usano un \type{int} come valore di ritorno; per gli
- argomenti \param{flags} e \param{len} vale quanto detto a proposito di
- \func{sendto}; infine l'argomento \param{fromlen} è \type{int} per i vari
- BSD4.*, le \acr{libc4} e le \acr{libc5}.} è:
+\funcd{recvfrom} che serve a ricevere i dati inviati da un altro socket, il
+suo prototipo\footnote{il prototipo è quello delle \acr{glibc} che seguono le
+ \textit{Single Unix Specification}, i vari BSD4.*, le \acr{libc4} e le
+ \acr{libc5} usano un \type{int} come valore di ritorno; per gli argomenti
+ \param{flags} e \param{len} vale quanto detto a proposito di \func{sendto};
+ infine l'argomento \param{fromlen} è \type{int} per i vari BSD4.*, le
+ \acr{libc4} e le \acr{libc5}.} è:
\begin{functions}
\headdecl{sys/types.h}
\headdecl{sys/socket.h}
\funcdecl{ssize\_t recvfrom(int sockfd, const void *buf, size\_t len, int
flags, const struct sockaddr *from, socklen\_t *fromlen)}
- Riceve un messaggio ad un altro socket.
+ Riceve un messaggio ad un socket.
\bodydesc{La funzione restituisce il numero di byte ricevuti in caso di
successo e -1 in caso di errore; nel qual caso \var{errno} assumerà il
I due argomenti \param{from} e \param{fromlen} sono utilizzati per ottenere
l'indirizzo del mittente del pacchetto che è stato ricevuto, e devono essere
-opportunamente inizializzati con i puntatori alle variabili dove quest'ultimo
-e la sua lunghezza saranno memorizzati (si noti che \param{fromlen} è un
-valore intero ottenuto come \textit{value return argoment}). Se non si è
-interessati a questa informazione, entrambi gli argomenti devono essere
-inizializzati al valore \const{NULL}.
+opportunamente inizializzati con i puntatori alle variabili dove la struttura
+contenente quest'ultimo e la relativa lunghezza saranno scritti (si noti che
+\param{fromlen} è un valore intero ottenuto come \textit{value return
+ argoment}). Se non si è interessati a questa informazione, entrambi gli
+argomenti devono essere inizializzati al valore \const{NULL}.
Un'altra differenza fondamentale di queste funzioni rispetto alle usuali
\func{read} e \func{write} che abbiamo usato con i socket TCP è che in questo
caso è perfettamente legale inviare con \func{sendto} un pacchetto vuoto (che
nel caso conterrà solo le intestazioni di IP e di UDP), scrivendo 0 byte. Allo
-stesso è possibile ricevere con \func{recvfrom} un valore di ritorno di 0
+stesso modo è possibile ricevere con \func{recvfrom} un valore di ritorno di 0
byte, senza che questo possa configurarsi come una chiusura della
connessione\footnote{dato che la connessione non esiste, non ha senso parlare
- di chiusura della connessione, pertanto con i socket UDP non è necessario
- usare \func{close} per terminare la cominicazione.} o come una cessazione
-delle comunicazioni.
+ di chiusura della connessione, questo significa anche che con i socket UDP
+ non è necessario usare \func{close} o \func{shutdown} per terminare la
+ cominicazione.} o come una cessazione delle comunicazioni.
+
\subsection{Un client elementare}
\label{sec:UDP_simple_server}
-Vediamo allora come implementare un primo server elementare su UDP; ricalcando
-quanto fatto nel caso dei socket TCP prenderemo come primo esempio l'uso del
-servizio \textit{time}, utilizzanto questa volta UDP.
+Vediamo allora come implementare un primo client elementare con dei socket
+UDP; ricalcando quanto fatto nel caso dei socket TCP prenderemo come primo
+esempio l'uso del servizio \textit{daytime}, utilizzando questa volta UDP. Il
+servizio è definito nell'\href{http://www.ietf.org/rfc/rfc0862.txt}{RFC~867},
+che nel caso di uso di UDP prescrive che il client debba inviare un pacchetto
+UDP al server (di contenuto non specificato), il quale risponderà a inviando a
+sua volta un pacchetto UDP contenente la data.
+
+\begin{figure}[!htb]
+ \footnotesize \centering
+ \begin{minipage}[c]{15.6cm}
+ \includecodesample{listati/UDP_daytime.c}
+ \end{minipage}
+ \normalsize
+ \caption{Sezione principale del client per il servizio \textit{daytime} su
+ UDP.}
+ \label{fig:UDP_daytime}
+\end{figure}
+In \figref{fig:UDP_daytime} è riportato la sezione principale del codice del
+nostro client, il contenuto completo si trova nel file \file{UDP\_daytime.c}
+dei sorgenti allegati; al solito si è tralasciata la gestione delle opzioni a
+riga di comando (nel caso praticamente assenti).
+
+Il programma inizia (\texttt{\small 9--12}) con la creazione del socket, al
+solito uscendo dopo aver stampato un messaggio in caso errore. Si noti come in
+questo caso, rispetto all'analogo client basato su socket TCP di
+\figref{fig:TCP_daytime_client_code} si sia usato per il tipo di socket il
+valore \const{SOCK\_DGRAM}, pur mantenendosi nella stessa famiglia data da
+\const{AF\_INET}.
+
+Il passo successivo (\texttt{\small 13--21}) è l'inizializzazione della
+struttura degli indirizzi; prima (\texttt{\small 14}) si cancella
+completamente la stessa con \func{memset}, (\texttt{\small 15}) poi si imposta
+la famiglia dell'indirizzo ed infine (\texttt{\small 16} la porta. Infine
+(\texttt{\small 18--21}) si ricava l'indirizzo del server da contattare dal
+parametro passato a riga di comando, convertendolo con \func{inet\_pton}. Si
+noti come questa sezione sia identica a quella del client TCP di
+\figref{fig:TCP_daytime_client_code}, in quanto la determinazione dell'uso di
+UDP al posto di TCP è stata effettuata quando si è creato il socket.
+
+Una volta completate le inizializzazioni inizia il corpo principale del
+programma, il primo passo è inviare, come richiesto dal protocollo, un
+pacchetto al server. Questo lo si fa (\texttt{\small 16}) inviando un
+pacchetto vuoto (si ricordi quanto detto in \secref{sec:UDP_sendto_recvfrom})
+con \func{sendto}, avendo cura di passare un valore nullo per il puntatore al
+buffer e la lunghezza del messaggio. In realtà il protocollo non richiede che
+il pacchetto sia vuoto, ma dato che il server comunque ne ignorerà il
+contenuto, è inutile inviare dei dati.
+
+Verificato (\texttt{\small 24--27}) che non ci siano stati errori nell'invio
+si provvede (\texttt{\small 28}) ad invocare \func{recvfrom} per ricevere la
+risposta del server. Si controlla poi (\texttt{\small 29--32}) che non vi
+siano stati errori in ricezione (uscendo con un messaggio in caso contrario);
+se è tutto a posto la variabile \var{nread} conterrà la dimensione del
+messaggio di risposta inviato dal server memorizzato su \var{buffer}, se
+(\texttt{\small 34}) pertanto il valore è positivo si provvederà
+(\texttt{\small 35}) a terminare la stringa contenuta nel buffer di
+lettura\footnote{si ricordi che, come illustrato in
+ \secref{sec:TCP_daytime_client}, il server invia in risposta una stringa
+ contenente la data, terminata dai due carratteri CR e LF, che pertanto prima
+ di essere stampata deve essere opportunamente terminata con NUL.} e a
+stamparla (\texttt{\small 36}) sullo standard output, controllando
+(\texttt{\small 36--38}) anche in questo caso l'esito dell'operazione ed
+uscendo in caso di errore.
+
+Se pertanto si è avuto cura di attivare il server del servizio
+\textit{daytime}\footnote{di norma questo è un servizio standard fornito dal
+ \textsl{superdemone} \cmd{inetd}, per cui basta abilitarlo nel file di
+ configurazione di quest'ultimo, avendo cura di predisporre il servizio su
+ UDP.} potremo verificare il funzionamento del nostro client interrogando
+quest'ultimo con:
+\begin{verbatim}
+[piccardi@gont sources]$ ./daytime 127.0.0.1
+Sat Mar 20 23:17:13 2004
+\end{verbatim}%$
+ed osservando il traffico con uno sniffer potremo effettivamente vedere lo
+scambio dei due pacchetti, quello vuoto di richiesta, e la risposta del
+server:
+\begin{verbatim}
+[root@gont gapil]# tcpdump -i lo
+tcpdump: listening on lo
+23:41:21.645579 localhost.32780 > localhost.daytime: udp 0 (DF)
+23:41:21.645710 localhost.daytime > localhost.32780: udp 26 (DF)
+\end{verbatim}
+
%%% Local Variables:
%%% mode: latex
%%% TeX-master: "gapil"
--- /dev/null
+/* UDP_daytime.c
+ *
+ * Copyright (C) 2004 Simone Piccardi
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+/****************************************************************
+ *
+ * Program daytime:
+ * Elementary UDP client for daytime service (port 13)
+ *
+ * Author: Simone Piccardi
+ * Mar. 2004
+ *
+ * Usage: daytime -h give all info's
+ *
+ * $Id: UDP_daytime.c,v 1.1 2004/03/20 22:42:07 piccardi Exp $
+ *
+ ****************************************************************/
+/*
+ * Include needed headers
+ */
+#include <sys/types.h> /* predefined types */
+#include <unistd.h> /* include unix standard library */
+#include <arpa/inet.h> /* IP addresses conversion utiliites */
+#include <sys/socket.h> /* socket library */
+#include <stdio.h> /* include standard I/O library */
+
+#define MAXLINE 80
+/* Program begin */
+void usage(void);
+int main(int argc, char *argv[])
+{
+/*
+ * Variables definition
+ */
+ int sock;
+ int i, nread;
+ struct sockaddr_in addr;
+ char buffer[MAXLINE];
+ /*
+ * Input section: decode parameters passed in the calling
+ * Use getopt function
+ */
+ opterr = 0; /* don't want writing to stderr */
+ while ( (i = getopt(argc, argv, "h")) != -1) {
+ switch (i) {
+ /*
+ * Handling options
+ */
+ case 'h':
+ printf("Wrong -h option use\n");
+ usage();
+ return(0);
+ break;
+ case '?': /* unrecognized options */
+ printf("Unrecognized options -%c\n",optopt);
+ usage();
+ default: /* should not reached */
+ usage();
+ }
+ }
+ /* ***********************************************************
+ *
+ * Options processing completed
+ *
+ * Main code beginning
+ *
+ * ***********************************************************/
+ /* create socket */
+ if ( (sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
+ perror("Socket creation error");
+ return -1;
+ }
+ /* initialize address */
+ memset((void *) &addr, 0, sizeof(addr)); /* clear server address */
+ addr.sin_family = AF_INET; /* address type is INET */
+ addr.sin_port = htons(13); /* daytime port is 13 */
+ /* build address using inet_pton */
+ if ( (inet_pton(AF_INET, argv[optind], &addr.sin_addr)) <= 0) {
+ perror("Address creation error");
+ return -1;
+ }
+ /* send request packet */
+ nread = sendto(sock, NULL, 0, 0, (struct sockaddr *)&addr, sizeof(addr));
+ if (nread < 0) {
+ perror("Request error");
+ return -1;
+ }
+ nread = recvfrom(sock, buffer, MAXLINE, 0, NULL, NULL);
+ if (nread < 0) {
+ perror("Read error");
+ return -1;
+ }
+ /* print results */
+ if (nread > 0) {
+ buffer[nread]=0;
+ if (fputs(buffer, stdout) == EOF) { /* write daytime */
+ perror("fputs error");
+ return -1;
+ }
+ }
+ /* normal exit */
+ return 0;
+}
+/*
+ * routine to print usage info and exit
+ */
+void usage(void) {
+ printf("Take daytime from a remote host \n");
+ printf("Usage:\n");
+ printf(" daytime [-h] [-v] [host in dotted decimal form] \n");
+// printf(" -v set verbosity on\n");
+ printf(" -h print this help\n");
+ exit(1);
+}