[Contents] [Index] [Help] [Retrace] [Browse <] [Browse >]

The basic outline of most client/server model applications written with
UDP look something like this:

Server gets a socket with socket(), gets a port number with
getservbyname(), builds a sockaddr_in structure describing the server, and
gives that structure to bind() in order to attach a specific address to
the socket.  It then loops, waiting to recvfrom() any incoming datagrams
and responding to any requests in those datagrams.

    int recvfrom(int socket, char *buf, int buflength, int flags,
                 struct sockaddr *clientname, int namelength)

The recvfrom() function is similar to the recv() function except it has
two extra parameters.  clientname is a buffer for a sockaddr_in structure.
When recvfrom() receives a datagram from some application on the network,
it fills in clientname with the transport address of that application.
The size of the structure is namelength.

The client gets a server hostname from the user, gets a socket(), gets the
server's port number with getservbyname(), builds a sockaddr_in structure
describing the server, and sendto()s a datagram to the server's well-known
address:

int sendto(int socket, char *buf, int buflength, int flags, struct
sockaddr *servername, int namelength) The client either waits for the
server to send back a datagram to the client's socket, or give up because
the server took too much time to reply.  The client does this by calling
select().  This function puts the client to sleep until either a
particular set of sockets is ready for reading, writing, or exception
processing, or after a timeout period has passed without any activity.

    int select(int numsocks, fd_set *readsocks, fd_set *writesocks,
               fd_set *exceptsocks, struct timeval *timeout)

The numsocks parameter is 1 plus the number of sockets select() is waiting
on. The readsocks, writesocks, and exceptsocks parameters are each a
bitmask which tells select() which socket (or sockets) to wait for
activity on.  The fd_set structure (defined in <sys/types.h>), is
basically a handle to one of these bitmasks.  Programs cannot directly
manipulate the bits in these masks. Instead, there are functions to do
this:

    FD_ZERO(struct fd_set *mymask)                /* clear all bits in
                                                     mymask */
    FD_SET(int mysocket, struct fd_set *mymask)   /* turn on the bit for
                                                     mysocket in mymask */
    FD_CLR(int mysocket, struct fd_set *mymask)   /* turn off the bit for
                                                     mysocket in mymask */
    FD_ISSET(int mysocket, struct fd_set *mymask) /* test if mysocket's bit
                                                     in mymask is set */

For the purposes of this article, the only relevant mask is readsocks,
because the client is only waiting to read from a socket.  Since the
client isn't interested in the other masks, it makes writesocks and
exceptsocks NULL.

The last parameter, timeout, sets the maximum amount of time that select()
should wait for activity on the sockets it is watching.

When select() returns, its return value is either -1 if it failed, 0 if
there was a timeout, or a positive number, which is the number of sockets
that became ready.  When select returns, it sets the bits in the bitmasks
according to which socket (or sockets) became ready.

When select() returns, if a socket became ready, the client calls
recvfrom() to get the datagram the server sent back.  On timeout, the
client might try to re-send the datagram since it may have been lost or
corrupted.  Datagrams can be also be received in an order different from
that in which they were sent and can be received in duplicate.