Unix Network Programming Manual
Asynchronous Broadcast Example
The following example demonstrates an example of an asynchronous
broadcast. The server sets itself up to handle the receipt of both
a broadcast are you there type of message and connections from
clients that want service. Since there are two sources of I/O, it
is necessary to use asynchronous non-blocking I/O. The client and
server don't do much else to keep the example simplified.
This program has been simplified considerably to make it easier to
follow. Normally, you would do more error checking and modularizing of
code. A number of service routines are used that simplify the code
and provide common network programming services. They are:
- MakeAddr - produces an Unix
socket-style address for binding
- MakeSocket -
obtains a socket for further communication
/* ===================================================> broadcast_server.c
* Simple server to handle broadcasts from clients trying to find the
* servers. This is simplified somewhat and contains large sections of
* code that are replaced with comments to make it small enought to
* be a reasonable example.
*
* Open a udp port and listen for broadcasts of potential
* client. Should get "Are You There?" and respond with
* "Here-ip_address-port".
*
* Also, handle connections from client over TCP.
*
* ====================================================================
*/
#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include <math.h>
#define ERROR_TYPE 1
#define OK_TYPE 0
#define BUFLEN 80
#define STRLEN 32
#define AREYOUTHERE "Are You There?"
#define IMHERE "Yup I'm Here"
#define SORT_SERVICE "test_service" /* special service in /etc/services */
/*
* Global variables.
*/
struct sockaddr_in app_address;
int query_socket, client_sock, process_done;
unsigned int auth_key, client_key;
/*
* Prototypes
*/
void AreYouThereCheck ();
void SigioNull ();
void ProcessClient (int, char *);
struct sockaddr_in *MakeAddr (int, char *, char *, char *);
int MakeSocket (int, struct sockaddr_in *);
void main
(
int argc,
char *argv[]
)
{
int app_socket, clientsock;
int len, ret, addrlen, err, sockopt;
int flags;
void (*oldsigio)();
char ch, buf[80];
struct hostent *hostp;
struct sockaddr_in addr, *paddr;
char filename [132];
/*
* Make sure that that the service name was passed in.
*/
if (argc < 2)
{
fprintf (stderr, "broadcast_server servicename\n");
exit (0);
}
/*
* Before anything can be returned to the client, the server has to know
* what port and address are being used. First, open a socket on some
* random port, and then store the data in app_address so that is can
* be found later and returned to the client. The client only has to
* know what well-known port the server is using for the broadcasts.
*/
paddr = MakeAddr (AF_INET, NULL, NULL, "tcp");
if (paddr == NULL)
{
perror ("Could not build connection address");
exit (0);
}
memcpy (&app_address, paddr, sizeof (app_address));
app_socket = MakeSocket (SOCK_STREAM, &app_address);
if (app_socket < 0)
{
perror ("Could not make connection socket\n");
exit (0);
}
/*
* At this time, the port has been assigned, but it is not necessarily
* returned in the address by bind. Use getsockname to get the port
* and gethostbyname to get the IP address for app_address, which stores
* the data for passing on to the requestors. All data is kept in
* host order form.
*/
len = sizeof (struct sockaddr_in);
if (getsockname (app_socket, &app_address, &len) < 0)
{
perror ("getting socket name");
exit (0);
};
app_address . sin_port = ntohs (app_address . sin_port);
if (gethostname (buf, 80) < 0)
{
perror ("getting local host name");
exit (0);
}
hostp = gethostbyname (buf);
app_address . sin_addr . s_addr =
ntohl (((struct in_addr *)hostp -> h_addr) -> s_addr);
/*
* Build an address for the broadcast query requests. It must be on a
* well known port. If unsuccessful, call it a day.
*/
paddr = MakeAddr (AF_INET, argv[1], NULL, "udp");
if (paddr == NULL)
{
perror ("Trying to open advertising port.");
exit (0);
}
memcpy (&addr, paddr, sizeof (struct sockaddr_in));
query_socket = MakeSocket (SOCK_DGRAM, &addr);
/*
* Since there are two sources of I/O, we have a problem, since blocking
* while we wait for one could cause the other to go unanswered. The
* solution is to make all the I/O asynchronous and use a signal handler
* or to use non-blocking I/O. In this case, non-blocking I/O will
* be used and you can see how that is process_done below. However, to make
* the non-blocking I/O work well we also need to use asynrchronous I/O.
*
* fcntl has to perform a SET OWNERSHIP operation on the query socket,
* so that the SIGURG signals that might be generated can be directed to
* this process. This is too complicated to explain fully here.
*/
fcntl (query_socket, F_SETOWN, getpid ());
flags = fcntl (query_socket, F_GETFL, 0);
fcntl (query_socket, F_SETFL, flags | FASYNC | O_NDELAY);
fcntl (app_socket, F_SETOWN, getpid ());
flags = fcntl (app_socket, F_GETFL, 0);
fcntl (app_socket, F_SETFL, flags | FASYNC | O_NDELAY);
/*
* Set up the signal handler, but have it do nothing. More on this
* later when things get interesting.
*/
oldsigio = signal (SIGIO, SigioNull, -1);
/*
* Set the socket to handle broadcast messages.
*/
sockopt = 1;
if (setsockopt(query_socket, SOL_SOCKET, SO_BROADCAST,
(char *) &sockopt, sizeof(int)) < 0)
{
perror ("on set socket options");
exit(-1);
}
/*
* Make the application socket available and get ready for client
* connections. For each connection, create a child process to handle
* the requests.
*/
if (listen(app_socket, 1) == -1)
{ perror("on listen");
exit(-1);
}
do
{
/*
* Clean up terminated children and wait some sort of I/O. pause
* will return when some assignable I/O happens. Then just try each
* of the sources, which are non-blocking.
*/
ret = wait3 (NULL, WNOHANG, 0);
pause ();
/*
* Some sort of I/O has happened, since pause has returned. First,
* clear any child processes, and then check to see if there is
* I/O on the UDP broadcast port, and then on the listen port.
*/
ret = wait3 (NULL, WNOHANG, 0);
AreYouThereCheck ();
addrlen = sizeof (struct sockaddr_in);
/*
* See if a connection awaits. If the clientsock is less than 0,
* it could be an I/O error that should be handled like any
* catastrophe, but it could be an EWOULDBLOCK, which says that there
* has been no activity (pause was broken by I/O on the other
* socket), or an EINTR condition, which means that a connection
* was interrupted and so no processing can take place now.
*/
clientsock = accept(app_socket, &addr, &addrlen);
if (clientsock < 0)
if ((errno != EWOULDBLOCK) && (errno != EINTR))
{ perror("on accept");
exit(0);
}
else
;
else
{
/*
* Create a child process to handle the client communications.
*/
if (( err = fork ()) < 0)
perror ("Creating child process\n");
else
if (err == 0)
{
/*
* Child process code. Handle the transactions for one client.
*/
close (app_socket); /* Not needed by child */
ProcessClient (clientsock, filename);
close (clientsock);
exit (0);
}
close (clientsock); /* Not needed by parent */
}
/*
* Parent part - if no error, continue
*/
} while (1);
close (app_socket);
close (query_socket);
}
/* ====================================================> AreYouThereCheck
*
* void AreYouThereCheck (void)
*
* Handle a broadcast for an "are you there" from a potential client.
* check the message for legality
* return the IP address and port number
*
* =====================================================================
*/
void AreYouThereCheck ()
{
static char buf [80];
int nch, slen;
struct sockaddr_in csock;
/*
* Get the message and then just to make sure, stick a null on the
* end. Clients are notoriously unreliable. Note that I/O is
* non-blocking, so if there is nothing there, it returns with
* zero characters.
*/
slen = sizeof (struct sockaddr);
nch = recvfrom (query_socket, buf, 15, 0, &csock, &slen);
if (nch <= 0)
return;
buf [nch] = '\0';
/*
* If its not a correct message, ignore it.
*/
if (strcmp (buf, AREYOUTHERE) != 0)
return;
/*
* Send a response that tells the client where to connect.
*/
sprintf (buf, "%s-%d-%ld", IMHERE, (unsigned int) app_address . sin_port,
(unsigned int) app_address . sin_addr . s_addr);
sendto (query_socket, buf, strlen (buf) + 1, 0, &csock, slen);
return;
}
/* ==============================================================> MakeAddr
*
* socket = MakeAddr (family, service, host, protocol)
*
* Create an internet address structure for the family, service, host and
* protocol.
*
* ======================================================================
*/
struct sockaddr_in *MakeAddr
(
int family,
char *service,
char *host,
char *protocol
)
{
static struct sockaddr_in addr;
struct servent *servp;
struct hostent *hostp;
memset (&addr, 0, sizeof (addr));
addr.sin_family = AF_INET;
if (service == NULL)
addr . sin_port = 0;
else
{
if ((servp = getservbyname (service, protocol)) == NULL)
{
perror ("Attempting to get the service entry");
return (NULL);
}
addr . sin_port = servp -> s_port;
}
if (host == NULL)
addr.sin_addr.s_addr = htonl (INADDR_ANY);
else
{
if ((hostp = gethostbyname (host)) == NULL)
{
perror ("Attempting to get the host entry");
return (NULL);
}
addr.sin_addr.s_addr = ((struct in_addr *) (hostp -> h_addr)) -> s_addr;
}
return (&addr);
}
/* ===================================================================>
*
* socket = MakeSocket (type, addr)
*
* Create a socket of type type and bind it to *addr.
*
* ======================================================================
*/
int MakeSocket
(
int type,
struct sockaddr_in *addr
)
{
int sock;
/*
* Build and bind the socket.
*/
sock = socket(addr -> sin_family, type,0);
if (sock == -1)
{ perror("opening socket");
return (-1);
}
if (bind(sock, addr, sizeof (struct sockaddr_in)) < 0)
{
perror("on bind");
return (-1);
}
return (sock);
}
/* ===========================================================> SigioNull
*
* void SigioNull (void)
*
* Do nothing signal handler for sigpause.
*
* ======================================================================
*/
void SigioNull ()
{
return;
}
void ProcessClient
(
int sock,
char *filename
)
{
/*
* Child processes all of the client requests here.
*/
return;
}
============================================================================
============================================================================
/* ======================================================> broadcast_client.c
*
* Simplified client for the broadcast client/server example. This
* client broadcasts for the server on a well-known port and when found,
* connects to the port returned.
*
* ====================================================================
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#include <math.h>
#define BUFLEN 80
typedef struct server_S
{
int sock;
/*
* Whatever else you might want to keep track of for each server.
*/
} server_T;
void Timeout ();
void main
(
int argc,
char *argv[]
)
{
int ct, bsock, csock, addrsize, mlen, sockopt, temp;
int nserver, timedout = 0;
struct sockaddr_in addr, caddr;
struct servent *servp;
struct hostent *host;
char *buf, *bp, *port, *ip;
server_T servers [8];
void (*oldsig) ();
buf = (char *) malloc (100 * sizeof (float));
/*
* The arguments should specify the service.
*/
if (argc < 2)
{
fprintf (stderr, "broadcast_client service\n");
exit (0);
}
/*
* Get the correct service port number and host address.
*/
if ((servp = getservbyname (argv[1], "udp")) == NULL)
{
perror ("Attempting to get the service entry");
exit (0);
}
bsock = socket(AF_INET, SOCK_DGRAM,0); /* open a socket */
if (bsock == -1)
{ perror("opening socket");
exit(-1);
}
memset (&addr, 0, sizeof (addr));
addr.sin_family = AF_INET;
addr.sin_port = 0;
addr.sin_addr.s_addr = htonl (INADDR_ANY);
bind (bsock, &addr, sizeof (addr));
/*
* Set the socket to handle broadcast messages.
*/
sockopt = 1;
if (setsockopt(bsock, SOL_SOCKET, SO_BROADCAST,
(char *) &sockopt, sizeof(int)) < 0)
{
perror ("on set socket options");
exit(-1);
}
/* Send the broadcast and wait for a response. */
memset (&addr, 0, sizeof (addr));
addr.sin_family = AF_INET;
servp = getservbyname (argv[1]);
addr.sin_port = servp -> s_port;
addr.sin_addr.s_addr = inet_addr ("153.90.192.0");
strcpy (buf, "Are You There?");
sendto (bsock, buf, strlen (buf) + 1, 0, &addr, sizeof (addr));
/*
* If there are 0-n servers, you need to set up a timer to wait in
* a loop until all are accounted for, within some reasonable time
* period. In this case, 3 seconds. Look at the signals and timers
* pages to understand this if you don't.
*/
oldsig = signal (SIGALRM, Timeout);
StartTimer (3);
nserver ++;
do
{
mlen = sizeof (addr);
recvfrom (bsock, buf, 80, 0, &addr, &mlen);
/*
* Extract the IP address and port number from the response and
* connect to the server.
*/
port = strchr (buf, '-');
ip = strchr (port+1, '-');
*port = 0;
*ip = 0;
temp = atoi (port+1);
addr . sin_port = htons(temp);
temp = atol (ip+1);
addr . sin_addr . s_addr = htonl(temp);
csock = socket (AF_INET, SOCK_STREAM, 0);
memset (&caddr, 0, sizeof (struct sockaddr_in));
caddr . sin_port = htons (0);
caddr . sin_addr . s_addr = htonl (INADDR_ANY);
caddr . sin_family = AF_INET;
/*
* Try to bind and connect to the server. If it fails, continue
* with the others. The connect is usually followed by some sort of
* message exchange to verify the connection, but that it foregone
* here.
*/
if (bind (csock, &caddr, sizeof (caddr)) < 0)
{
perror ("on db bind");
continue;
}
if (connect(csock, &addr, sizeof (struct sockaddr_in)) == -1)
{
perror ("on db connect");
continue;
}
servers [nserver] . sock = csock;
nserver ++;
} while (! timedout);
/*
* We are now connected to all the servers, go to work.
*/
close (bsock);
if (nserver == 0)
{
fprintf (stderr, "Unable to connect to any servers - terminated\n");
exit (0);
}
}
/* ============================================================> Timeout
*
* Syntax: Timeout ()
*
* Signal handler that set the flag necessary to escape from the
* wait for servers loop.
*
* =====================================================================
*/
void Timeout ()
{
timedout = 1;
return;
}