Section 12.6. Interprocess Communication


[Page 500 (continued)]

12.6. Interprocess Communication

Interprocess communication (IPC) is the generic term describing how two processes may exchange information with each other. In general, the two processes may be running on the same machine or different machines, although some IPC mechanism may only support local usage (e.g., signals and pipes). This communication may be an exchange of data, where two or more processes are cooperatively processing the data or synchronization information to help two independent, but related, processes schedule work so they do not destructively overlap.


[Page 501]

12.6.1. Pipes

Pipes are an interprocess communication mechanism allowing two or more processes to send information to each other. They are commonly used from within shells to connect the standard output of one utility to the standard input of another. For example, here's a simple shell command that determines how many users are on the system:

$ who | wc -1


The who utility generates one line of output per user. This output is then "piped" into the wc utility, which, when invoked with the -1 option, outputs the total number of lines in its input. Thus, the pipelined command craftily calculates the total number of users by counting the number of lines that who generates. Figure 12-52 is a diagram of the pipeline.

Figure 12-52. A simple pipe.


It's important to realize that both the writer process and the reader process of a pipeline execute concurrently; a pipe automatically buffers the output of the writer and suspends the writer if the pipe gets too full. Similarly, if a pipe empties, the reader is suspended until some more output becomes available.

Linux provides two types of pipes. The simplest is the unnamed pipe, which is what the shell uses to pipe data between processes. A named pipe allows two independent processes to find the pipe. In this section, I'll show you how to construct each kind of pipe, starting with unnamed pipes.

12.6.1.1. Unnamed Pipes: pipe ()

An unnamed pipe is a unidirectional communications link that automatically buffers its input and may be created using the pipe () system call. Each end of a pipe has an associated file descriptor. The "write" end of the pipe may be written to using write (), and the "read" end may be read from using read (). When a process has finished with a pipe's file descriptor, it should close it using close (). Figure 12-53 describes how pipe () works.


[Page 502]

Figure 12-53. Description of the pipe () system call.

System Call: int pipe (int fd [2])

pipe () creates an unnamed pipe and returns two file descriptors; the descriptor associated with the "read" end of the pipe is stored in fd[0], and the descriptor associated with the "write" end of the pipe is stored in fd[1].

The following rules apply to processes that read from a pipe:

  • If a process reads from a pipe whose write end has been closed, the read () returns a 0, indicating end-of-input.

  • If a process reads from an empty pipe whose write end is still open, it sleeps until some input becomes available.

  • If a process tries to read more bytes from a pipe than are present, all of the current contents are returned and read () returns the number of bytes actually read.

The following rules apply to processes that write to a pipe:

  • If a process writes to a pipe whose read end has been closed, the write fails and the writer is sent a SIGPIPE signal. The default action of this signal is to terminate the writer.

  • If a process writes fewer bytes to a pipe than the pipe can hold, the write () is guaranteed to be atomic; that is, the writer process will complete its system call without being preempted by another process. If a process writes more bytes to a pipe than the pipe can hold, no similar guarantees of atomicity apply.

Since access to an unnamed pipe is via the file descriptor mechanism, typically only the process that creates a pipe and its descendants may use the pipe.[a] lseek () has no meaning when applied to a pipe.

If the kernel cannot allocate enough space for a new pipe, pipe () returns -1; otherwise, it returns 0.


[a] In advanced situations, it is actually possible to pass file descriptors to unrelated processes via a pipe.

If the following code were executed:

int fd [2]; pipe (fd); 


then the data structures shown in Figure 12-54 would be created.


[Page 503]

Figure 12-54. An unnamed pipe.


Unnamed pipes are usually used for communication between a parent process and its child, with one process writing and the other process reading. The typical sequence of events is as follows:

  1. The parent process creates an unnamed pipe using pipe ().

  2. The parent process forks.

  3. The writer closes its read end of the pipe, and the designated reader closes its write end of the pipe.

  4. The processes communicate by using write () and read () calls.

  5. Each process closes its active pipe descriptor when finished with it.

Bidirectional communication is only possible by using two pipes.

Here's a small program that uses a pipe to allow the parent to read a message from its child:

$ cat talk.c                        ...list the program. #include <stdio.h> #define READ   0       /* The index of the read end of the pipe */ #define WRITE  1       /* The index of the write end of the pipe */ char* phrase = "Stuff this in your pipe and smoke it"; main () {  int fd [2], bytesRead;  char message [100]; /* Parent process' message buffer */   pipe (fd); /*Create an unnamed pipe */  if (fork () == 0) /* Child, writer */    {      close(fd[READ]); /* Close unused end */      write (fd[WRITE],phrase, strlen (phrase) + 1); /* include NULL*/      close (fd[WRITE]); /* Close used end*/    }  else /* Parent, reader*/    {      close (fd[WRITE]); /* Close unused end */ 
[Page 504]
bytesRead = read (fd[READ], message, 100); printf ("Read %d bytes: %s\n", bytesRead, message); /* Send */ close (fd[READ]); /* Close used end */ } } $ ./talk ...run the program. Read 37 bytes: Stuff this in your pipe and smoke it $ _


Notice that the child included the phrase's NULL terminator as part of the message so that the parent could easily display it. When a writer process sends more than one variable-length message into a pipe, it must use a protocol to indicate to the reader an end-of-message. Methods for doing this include:

  • sending the length of a message (in bytes) before sending the message itself

  • ending a message with a special character such as a newline or a NULL

The Linux shells use unnamed pipes to build pipelines. They use a trick similar to the redirection mechanism described in "Process Management" on page 473 to connect the standard output of one process to the standard input of another. To illustrate this approach, here's the source code of a program that executes two named programs, connecting the standard output of the first to the standard input of the second. It assumes that neither program is invoked with options, and that the names of the programs are listed on the command line.

$ cat connect.c                     ...list the program. #include <stdio.h> #define READ   0 #define WRITE  1 main (argc, argv) int argc; char* argv []; {  int fd [2];   pipe (fd); /* Create an unnamed pipe */   if (fork () != 0) /* Parent, writer */    {      close (fd[READ]); /* Close unused end */      dup2 (fd[WRITE], 1); /* Duplicate used end to stdout */      close (fd[WRITE]); /* Close original used end */      execlp (argv[1], argv[1], NULL); /* Execute writer program */      perror ("connect");  /* Should never execute */    }  else /* Child, reader */    {      close (fd[WRITE]); /* Close unused end */      dup2 (fd[READ], 0); /* Duplicate used end to stdin */ 
[Page 505]
close (fd[READ]); /* Close original used end */ execlp (argv[2], argv[2], NULL); /* Execute reader program */ perror ("connect"); /* Should never execute */ } } $ who ...execute "who" by itself. glass pts/1 Feb 15 18:45 (:0.0) $ ./connect who wc ...pipe "who" through "wc". 1 6 42 ...1 line, 6 words, 42 chars. $ _


12.6.1.2. Named Pipes

Named pipes (referred to in Linux as FIFOs, meaning "first in, first out") are less restricted than unnamed pipes, and offer the following advantages:

  • They have a name that exists in the file system.

  • They may be used by unrelated processes.

  • They exist until explicitly deleted.

All of the pipe rules mentioned in the "Unnamed Pipes" section apply to named pipes.

Because named pipes exist as special files in the file system, processes using them to communicate need not have a common ancestry as they do when using unnamed pipes. A named pipe (FIFO) may be created in one of two ways:

  • by using the Linux mkfifo utility

  • by using the mkfifo () system call

Figure 12-55 describes the mkfifo utility.

Figure 12-55. Description of the mkfifo utility.

Utility: mkfifo fileName

mkfifo creates a named pipe called fileName.


UNIX used mknod and mknod () to create named pipes, and this still works on Linux, but mkfifo and mkfifo () are the preferred methods.

The mode of the named pipe may be set using chmod, allowing others to access the pipe that you create. Here's an example of this procedure:

$ mkfifo myPipe              ...create pipe. $ chmod ug+rw myPipe         ...update permissions. $ ls -l myPipe               ...examine attributes.  prw-rw----   1 glass   cs    0 Feb 27 12:38 myPipe $ _ 



[Page 506]

Note that the type of the named pipe is "p" in the ls listing.

Here's a line of C code that creates a named pipe with read and write permission for the owner and group:

mkfifo ("myPipe", 0660);   /* Create a named pipe */


Regardless of how you go about creating a named pipe, the end result is the same: a special file is added into the file system. Once a named pipe is opened using open (), write () adds data at the start of the FIFO queue, and read () removes data from the end of the FIFO queue. When a process has finished using a named pipe, it should close it using close (), and when a named pipe is no longer needed, it should be removed from the file system using unlink ().

Like an unnamed pipe, a named pipe is intended for use only as a unidirectional link. Writer processes should open a named pipe for write-only, and reader processes should open for read-only. Although a process could open a named pipe for both reading and writing, this doesn't have much practical application. Before I show you an example program that uses named pipes, here are a couple of special rules concerning their use:

  • If a process tries to open a named pipe for read-only and no process currently has it open for writing, the reader will wait until a process opens it for writing, unless O_NONBLOCK or O_NDELAY is set, in which case open () succeeds immediately.

  • If a process tries to open a named pipe for write-only and no process currently has it open for reading, the writer will wait until a process opens it for reading, unless O_NONBLOCK or O_NDELAY is set, in which case open () fails immediately.

  • Named pipes will not work across a network.

The following example uses two programs, "reader" and "writer", and they work like this:

  • A single reader process is executed, which creates a named pipe called "aPipe". It then reads and displays NULL-terminated lines from the pipe until the pipe is closed by all of the writing processes.

  • One or more writer processes are executed, each of which opens the named pipe called "aPipe" and sends three messages to it. If the pipe does not exist when a writer tries to open it, the writer retries every second until it succeeds. When all of a writer's messages are sent, the writer closes the pipe and exits.

The following are the source code for each file and some sample output:

$ ./reader & ./writer & ./writer &  ...start 1 reader, 2 writers. [1] 4698           ...reader process. [2] 4699           ...first writer process. 
[Page 507]
[3] 4700 ...second writer process. Hello from PID 4699 Hello from PID 4700 Hello from PID 4699 Hello from PID 4700 Hello from PID 4699 Hello from PID 4700 [2] Done ./writer ...first writer exits. [3] + Done ./writer ...second writer exits. [1] Done ./reader ...reader exits. $ _


The reader.c program looks like this:

#include <stdio.h> #include <sys/types.h> #include <fcntl.h> /********************************************************************/ main () {  int fd;  char str[100];  mkfifo ("aPipe", 0660); /* Create named pipe */  fd = open ("aPipe", O_RDONLY); /* Open it for reading */   while (readLine (fd, str)) /* Display received messages */    printf ("%s\n", str);   close (fd); /* Close pipe */ } /********************************************************************/ readLine (fd, str) int fd; char* str; /* Read a single NULL-terminated line into str from fd */ /* Return 0 when the end-of-input is reached and 1 otherwise */ {  int n;   do /* Read characters until NULL or end-of-input */    {      n = read (fd, str, 1); /* Read one character */    }  while (n > 0 && *str++ != 0);  return (n > 0); /* Return false if end-of-input */ } 



[Page 508]

The writer.c program looks like this:

#include <stdio.h> #include <fcntl.h> /********************************************************************/ main () {  int fd, messageLen, i;  char message [100];   /* Prepare message */  sprintf (message, "Hello from PID %d", getpid ());  messageLen = strlen (message) + 1;   do /* Keep trying to open the file until successful */    {      fd = open ("aPipe", 0_WRONLY); /* Open named pipe for writing */      if (fd == -1) sleep (1); /* Try again in 1 second */    }  while (fd == -1);   for (i = 1; i <= 3; i++) /* Send three messages */    {      write (fd, message, messageLen); /* Write message down pipe */      sleep (3); /* Pause a while */    }   close (fd); /* Close pipe descriptor */ } 


12.6.2. Sockets

A socket is an interprocess communication mechanism that allows processes to talk to each other even if they're on different machines. It is this cross-network capability that makes them so useful. For example, the rlogin utility, which allows a user on one machine to log into a remote host, is implemented using sockets. Other common uses of sockets include:

  • printing a file on one machine from another machine

  • transferring files from one machine to another machine

Process communication via sockets is based on the client-server model. One process, known as a server process, creates a socket whose name is known by other client processes. These client processes can talk to the server process via a connection to its named socket. To do this, a client process first creates an unnamed socket and then requests that it be connected to the server's named socket. A successful connection returns one file descriptor to the client and one to the server, both of which may be used for reading and writing. Note that, unlike pipes, socket connections are bidirectional. Figure 12-56 illustrates the process.


[Page 509]

Figure 12-56. The socket connection.


Once a socket connection is made, it's quite common for the server process to fork a child process to converse with the client, while the original parent process continues to accept other client connections. A typical example of this is a remote print server: the server process accepts a client that wishes to send a file for printing, and then forks a child to perform the file transfer. The parent process meanwhile waits for more client print requests.

In the coming sections, we'll take a look at the following topics:

  • the different kinds of sockets

  • how a server creates a named socket and waits for connections

  • how a client creates an unnamed socket and requests a connection from a server

  • how a server and client communicate after a socket connection is made

  • how a socket connection is closed.

  • how a server can create a child process to converse with the client

12.6.2.1. Socket Types

The various kinds of sockets may be classified according to three attributes:

  • the domain

  • the type

  • the protocol


[Page 510]
Domains

The domain of a socket indicates where the server and client sockets may reside; the domains that are currently supported include:

  • PF_LOCAL (the clients and server must be in the same machine, also called PF_UNIX)

  • PF_INET (the clients and server are on the network)

  • PF_INET6 (the clients and server are on an IPv6 network)

Other domains are listed in the socket man page. PF stands for "Protocol Family." This book contains examples of PF_LOCAL and PF_INET sockets.

Types

The type of a socket determines the type of communication that can exist between the client and server; the two main types that are currently supported are:

  • SOCK_STREAM: sequenced, reliable, two-way connection based, variable-length streams of bytes

  • SOCK_DGRAM: like telegrams; connectionless, unreliable, fixed-length messages

Other types that are either in the planning stages or implemented only in some domains include:

  • SOCK_SEQPACKET: sequenced, reliable, two-way connection based, fixed-length packets of bytes

  • SOCK_RAW: provides access to internal network protocols and interfaces

This book contains information only on how to use SOCK_STREAM sockets, which are the most common. They are both intuitive and easy to use.

Protocols

The protocol value specifies the low-level means by which the socket type is implemented. System calls that expect a protocol parameter accept 0 as meaning "the correct protocol"; in other words, the protocol value is something that you generally won't have to worry about. Most systems support only protocols other than 0 as an optional extra, so I'll use the default protocol in all the examples.

Writing Socket Programs

Any program that uses sockets must include "/usr/include/sys/types.h" and "/usr/include/sys/socket.h".

To illustrate the way a program that uses sockets is written I'll build my description of the socket-oriented system calls around a small client/server example that uses PF_LOCAL sockets. Once I've done this, I'll show you another example that uses PF_INET sockets. The PF_LOCAL example is comprised of two programs:

  • "chef," the server, which creates a named socket called "recipe" and writes the recipe to any clients who request it. The recipe is a collection of variable-length NULL-terminated strings.

  • "cook," the client, which connects to the named socket called "recipe" and reads the recipe from the server. It displays the recipe to standard output as it reads it, and then terminates.

The chef server process runs in the background. Any client cook processes that connect to the server cause the server to fork a duplicate server to handle the recipe transfer, allowing the original server to accept other incoming connections. Here's some sample output from the chef/cook example:


[Page 511]

$ ./chef &           ...run the server in the background. [1] 5684 $ ./cook             ...run a client--display the recipe. spam, spam, spam, spam, spam, and spam. $ ./cook             ...run another client--display the recipe. spam, spam, spam, spam, spam, and spam. $ kill %1            ...kill the server. [1]    Terminated           chef $ _ 


12.6.2.2. Chef/Cook Listings

This section contains the complete listing of the chef and cook programs. I suggest that you quickly skim through the code and then read the sections that follow for details on how they both work. I have purposely left out a great deal of error checking in the interest of space. This code may be downloaded from the Prentice Hall web site, please see the Preface for details.

Here is the code for the "chef.c" server program:

1   #include <stdio.h> 2  #include <signal.h> 3  #include <sys/types.h> 4  #include <sys/socket.h> 5  #include <sys/un.h>       /* for sockaddr_un struct */ 6 7  #define DEFAULT_PROTOCOL   0 8 9  /****************************************************************/ 10 11  main  () 12 13  { 14    int serverFd, clientFd, serverLen, clientLen; 15    struct sockaddr_un serverAddress;/* Server address */ 16    struct sockaddr_un clientAddress; /* Client address */ 17    struct sockaddr* serverSockAddrPtr; /* Ptr to server address */ 18    struct sockaddr* clientSockAddrPtr; /* Ptr to client address */ 19 20    /* Ignore death-of-child signals to prevent zombies */ 21    signal (SIGCHLD, SIG_IGN); 22 23    serverSockAddrPtr = (struct sockaddr*) &serverAddress; 24    serverLen = sizeof (serverAddress); 
[Page 512]
25 26 clientSockAddrPtr = (struct sockaddr*) &clientAddress; 27 clientLen = sizeof (clientAddress); 28 29 /* Create a socket, bidirectional, default protocol */ 30 serverFd = socket (PF_LOCAL, SOCK_STREAM, DEFAULT_PROTOCOL); 31 serverAddress.sun_family = PF_LOCAL; /* Set domain type */ 32 strcpy (serverAddress.sun_path, "recipe"); /* Set name */ 33 unlink ("recipe"); /* Remove file if it already exists */ 34 bind (serverFd, serverSockAddrPtr, serverLen); /* Create file */ 35 listen (serverFd, 5); /* Maximum pending connection length */ 36 37 while (1) /* Loop forever */ 38 { 39 /* Accept a client connection */ 40 clientFd = accept (serverFd, clientSockAddrPtr, &clientLen); 41 42 if (fork () == 0) /* Create child to send recipe */ 43 { 44 writeRecipe (clientFd); /* Send the recipe */ 45 close (clientFd); /* Close the socket */ 46 exit (/* EXIT_SUCCESS */ 0); /* Terminate */ 47 } 48 else 49 close (clientFd); /* Close the client descriptor */ 50 } 51 } 52 53 /****************************************************************/ 54 55 writeRecipe (fd) 56 57 int fd; 58 59 { 60 static char* line1 = "spam,spam,spam,spam,"; 61 static char* line2 = "spam, and spam."; 62 write (fd, line1, strlen (line1) + 1); /* Write first line */ 63 write (fd, line2, strlen (line2) + 1); /* Write second line */ 64 }



[Page 513]

Here is the code for the "cook.c" client program:

1   #include <stdio.h> 2   #include <signal.h> 3   #include <sys/types.h> 4   #include <sys/socket.h> 5   #include <sys/un.h>            /* for sockaddr_un struct*/ 6 7   #define DEFAULT_PROTOCOL    0 8 9   /****************************************************************/ 10 11   main  () 12 13   { 14     int clientFd, serverLen, result; 15     struct sockaddr_un serverAddress; 16     struct sockaddr* serverSockAddrPtr; 17 18     serverSockAddrPtr = (struct sockaddr*) &serverAddress; 19     serverLen = sizeof (serverAddress); 20 21     /* Create a socket, bidirectional, default protocol */ 22     clientFd = socket (PF_LOCAL, SOCK_STREAM, DEFAULT_PROTOCOL); 23     serverAddress.sun_family = PF_LOCAL; /* Server domain */ 24     strcpy (serverAddress.sun_path, "recipe"); /* Server name */ 25 26     do /* Loop until a connection is made with the server */ 27       { 28         result = connect (clientFd, serverSockAddrPtr, serverLen); 29         if (result == -1) sleep (1); /* Wait and then try again */ 30       } 31     while (result == -1); 32 33     readRecipe (clientFd); /* Read the recipe */ 34     close (clientFd); /* Close the socket */ 35     exit (/* EXIT_SUCCESS */ 0); /* Done */ 36   } 37 38   /**************************************************************/ 
[Page 514]
39 40 readRecipe (fd) 41 42 int fd; 43 44 { 45 char str[200]; 46 47 while (readLine (fd, str)) /* Read lines until end-of-input */ 48 printf ("%s\n", str); /* Echo line from socket */ 49 } 50 51 /**************************************************************/ 52 53 readLine (fd, str) 54 55 int fd; 56 char* str; 57 58 /* Read a single NULL-terminated line */ 59 60 { 61 int n; 62 63 do /* Read characters until NULL or end-of-input */ 64 { 65 n = read (fd,str, 1); /* Read one character */ 66 } 67 while (n > 0 && *str++ != 0); 68 return (n > 0); /* Return false if end-of-input */ 69 }


12.6.2.3. Analyzing the Source Code

Now that you've glanced at the program, it's time to go back and analyze it. We will examine each of the following steps that are performed when our programs are executed:

  • create a server socket

  • name a server socket

  • specify the maximum number of pending connections to a server socket

  • accept connections on a server socket

  • create a client socket


  • [Page 515]
  • connect a client socket to the server socket

  • communicate via sockets

12.6.2.4. The Server

A server is the process that is responsible for creating a named socket and accepting connections to it. To accomplish this, it must use the system calls listed in Figure 12-57 in their presented order.

Figure 12-57. System calls used by a typical Linux daemon process.

Name

Meaning

socket

Creates an unnamed socket.

bind

Gives the socket a name.

listen

Specifies the maximum number of pending connections.

accept

Accepts a socket connection from a client.


12.6.2.5. Creating a Socket: socket ()

A process may create a socket by using socket () (Figure 12-58).

Figure 12-58. Description of the socket () system call.

System Call: int socket (int domain, int type, int protocol)

socket () creates an unnamed socket of the specified domain, type, and protocol. The legal values of these parameters were described earlier in this section.

If socket () is successful, it returns a file descriptor associated with the newly created socket; otherwise, it returns -1.


The chef server creates its unnamed socket on line 30:

30   serverFd = socket (PF_LOCAL, SOCK_STREAM, DEFAULT_PROTOCOL); 


12.6.2.6. Naming a Socket: bind ()

Once the server has created an unnamed socket, it must bind it to a name by using bind (), which works as described in Figure 12-59.


[Page 516]

Figure 12-59. Description of the bind () system call.

System Call: int bind (int fd, const struct sockaddr* address, size_t addressLen)

bind () associates the unnamed socket represented by file descriptor fd with the socket address stored in address. addressLen must contain the length of the address structure. The type and value of the incoming address depend on the socket domain.

If the socket is in the PF_LOCAL domain, a pointer to a sockaddr_un structure (defined in "/usr/include/sys/un.h") must be cast to a (sockaddr*) and passed in as address. This structure has two fields that should be set as follows:

FIELD

ASSIGN THE VALUE

sun_family

PF_LOCAL

sun_path

the full pathname of the socket (absolute or relative), up to 108 characters long


If the named PF_LOCAL socket already exists, an error occurs, so it's a good idea to unlink () a name before attempting to bind to it.

If the socket is in the PF_INET domain, a pointer to a sockaddr_in structure must be cast to a (sockaddr*) and passed in as address. This structure has four fields, which should be set as follows:

FIELD

ASSIGN THE VALUE

sin_family

PF_INET

sin_port

the port number of the Internet socket

sin_addr

a structure of type in_addr that holds the Internet address

sin_zero

leave empty


For more information about Internet ports and addresses, please consult the Internet-specific part of this section.

If bind () succeeds, it returns a 0; otherwise, it returns -1.


The chef server assigns the sockaddr_un fields and performs a bind () on lines 31..34:

31    serverAddress.sun_family = PF_LOCAL; /* Set domain type */ 32    strcpy (serverAddress.sun_path, "recipe"); /* Set name */ 33    unlink ("recipe"); /* Remove file if it already exists */ 34    bind (serverFd, serverSockAddrPtr, serverLen); /* Create file */ 


12.6.2.7. Creating a Socket Queue: listen ( )

When a server process is servicing a client connection, it's always possible that another client will also attempt a connection. The listen () system call allows a process to specify the number of pending connections that may be queued (Figure 12-60).


[Page 517]

Figure 12-60. Description of the listen () system call

System Call: int listen (int fd, int queueLength)

listen () allows you to specify the maximum number of pending connections on a socket. Right now, the maximum queue length is 5. If a client attempts a connection to a socket whose queue is full, it is denied.


The chef server listens to its named socket on line 35:

35   listen (serverFd, 5); /* Maximum pending connection length */


12.6.2.8. Accepting a Client: accept ()

Once a socket has been created, named, and its queue size specified, the final step is to accept client connection requests. To do this, the server must use accept () (Figure 12-61).

Figure 12-61. Description of the accept () system call.

System Call: int accept (int fd, struct sockaddr* address, int* addressLen)

accept () listens to the named server socket referenced by fd and waits until a client connection request is received. When this occurs, accept () creates an unnamed socket with the same attributes as the original named server socket, connects it to the client's socket, and returns a new file descriptor that may be used for communication with the client. The original named server socket may be used to accept more connections.

The address structure is filled with the address of the client, and is normally only used in conjunction with Internet connections. The addressLen field should be initially set to point to an integer containing the size of the structure pointed to by address. When a connection is made, the integer that it points to is set to the actual size, in bytes, of the resulting address.

If accept () succeeds, it returns a new file descriptor that may be used to talk with the client; otherwise, it returns -1.


The chef server accepts a connection on line 40:

40     clientFd = accept (serverFd, clientSockAddrPtr, &clientLen);


12.6.2.9. Serving a Client

When a client connection succeeds, the most common sequence of events is this:

  • The server process forks.

  • The parent process closes the newly formed client file descriptor and loops back to accept (), ready to service new client connection requests.

  • The child process talks to the client using read () and write (). When the conversation is complete, the child process closes the client file descriptor and exits.


[Page 518]

The chef server process follows this series of actions on lines 37..50:

37    while (1) /* Loop forever */ 38      { 39        /* Accept a client connection */ 40        clientFd = accept (serverFd, clientSockAddrPtr, &clientLen); 41 42        if (fork() == 0) /* Create child to send recipe */ 43          { 44            writeRecipe (clientFd); /* Send the recipe */ 45            close (clientFd); /* Close the socket */ 46            exit (/*EXIT_SUCCESS */ 0); /* Terminate */ 47          } 48        else 49          close (clientFd); /* Close the client descriptor */ 50      } 


Note that the server chose to ignore SIGCHLD signals on line 21 so that its children can die immediately without requiring the parent to accept their return codes. If the server had not done this, it would had to have installed a SIGCHLD handler, which would have been more tedious.

12.6.2.10. The Client

Now that you've seen how a server program is written, let's take a look at the construction of a client program. A client is a process that's responsible for creating an unnamed socket and then attaching it to a named server socket. To accomplish this, it must use the system calls listed in Figure 12-62 in their presented order.

Figure 12-62. System calls used by a typical Linux client process.

Name

Meaning

socket

Creates an unnamed socket.

connect

Attaches an unnamed client socket to a named server socket.


The way that a client uses socket () to create an unnamed socket is the same as the way that the server uses it. The domain, type, and protocol of the client socket must match those of the targeted server socket. The cook client process creates its unnamed socket on line 22:

22   clientFd = socket (PF_LOCAL, SOCK_STREAM, DEFAULT_PROTOCOL);


12.6.2.11. Making the Connection: connect ()

To connect to a server's socket, a client process must fill a structure with the address of the server's socket and then use connect () (Figure 12-63).


[Page 519]

Figure 12-63. Description of the connect () system call.

System Call: int connect (int fd, struct sockaddr* address, int addressLen)

connect () attempts to connect to a server socket whose address is contained within a structure pointed to by address. If successful, fd may be used to communicate with the server's socket. The type of structure that address points to must follow the same rules as those stated in the description of bind ():

  • If the socket is in the PF_LOCAL domain, a pointer to a sockaddr_un structure must be cast to a (sockaddr*) and passed in as address.

  • If the socket is in the PF_INET domain, a pointer to a sockaddr_in structure must be cast to a (sockaddr*) and passed in as address.

addressLen must be equal to the size of the address structure.

If the connection is made, connect () returns 0. If the server socket doesn't exist or its pending queue is currently filled, connect () returns -1.


The cook client process calls connect () until a successful connection is made in lines 26..31:

26    do /* Loop until a connection is made with the server */ 27      { 28        result = connect (clientFd, serverSockAddrPtr, serverLen); 29        if (result == -1) sleep (1); /* Wait and then try again */ 30      } 31    while (result == -1); 


12.6.2.12. Communicating via Sockets

Once the server socket and client socket have connected, their file descriptors may be used by write () and read (). In the example program, the server uses write () in lines 55..64:

55   writeRecipe (fd) 56 57   int fd; 58 59   { 60     static char* line1 = "spam, spam, spam, spam,"; 61     static char* line2 = "spam, and spam."; 62     write (fd, line1, strlen (line1) + 1); /* Write first line */ 63     write (fd, line2, strlen (line2) + 1); /* Write second line*/ 64   } 


and the client uses read () in lines 53..69:

53    readLine (fd, str) 54 
[Page 520]
55 int fd; 56 char* str; 57 58 /* Read a single NULL-terminated line */ 59 60 { 61 int n; 62 63 do /* Read characters until NULL or end-of-input */ 64 { 65 n = read (fd, str, 1); /* Read one character */ 66 } 67 while (n > 0 && *str++ != 0); 68 return (n > 0); /* Return false if end-of-input */ 69 }


The server and client should be careful to close their socket file descriptors when they are no longer needed.

12.6.2.13. Internet Sockets

The PF_LOCAL sockets that you've seen so far are OK for learning about sockets, but they aren't where the action is. Most of the useful stuff involves communicating between machines on the Internet, and so the rest of this chapter is dedicated to PF_INET sockets. If you haven't already read about networking in Chapter 9, "Networking and the Internet," now would be a good time to do so.

An Internet socket is specified by two values: a 32-bit IP (v4) address, which specifies a single unique Internet host, and a 16-bit port number, which specifies a particular port on the host. This means that an Internet client must know not only the IP address of the server, but also the server's port number.

As I mentioned in Chapter 9, "Networking and the Internet," several standard port numbers are reserved for system use. For example, port 13 is always served by a process that echoes the host's time of day to any client that's interested. The first Internet socket example allows you to connect to port 13 of any Internet host in the world and find out the "remote" time of day. It allows three kinds of Internet address:

  • If you enter "s", it automatically means the local host.

  • If you enter something that starts with a digit, it's assumed to be an A.B.C.D format IP address, and is converted into a 32-bit IP address by software.

  • If you enter a string, it's assumed to be a symbolic host name, and is converted into a 32-bit IP address by software.

Here's some sample output from the "Internet time" program. The third address that I entered is the IP address of "ddn.nic.mil," the national Internet database server. Notice the one-hour time difference between my local host's time and the database server host's time.


[Page 521]

$ ./inettime                         ...run the program. Host name (q= quit, s = self): s     ...what's my time? Self host name is csservr2 Internet Address = 129.110.42.1 The time on the target port is 09 FEB 2005 10:02:01 CST Host name (q = quit, s= self): wotan      ...time on "wotan"? Internet Address = 129.110.2.1 The time on the target port is 09 FEB 2005 10:03:45 CST Host name (q = quit, s = self): 192.112.36.5   ...IP address. The time on the target port is 09 FEB 2005 11:03:17 EST Host name (q = quit, s = self): q       ...quit program. $ _ 


Here is a listing of the code for the Internet time program:

  1  #include <stdio.h>   2  #include <signal.h>   3  #include <ctype.h>   4  #include <sys/types.h>   5  #include <sys/socket.h>   6  #include <netinet/in.h>           /* For PF_INET sockets */   7  #include <arpa/inet.h>   8  #include <netdb.h>   9  10  #define DAYTIME_PORT        13       /* Standard port # */  11  #define DEFAULT_PROTOCOL    0  12  13  unsigned long promptForINETAddress();  14  unsigned long nameToAddr ();  15  16 /*****************************************************************/  17  18  main  ()  19  20  {  21    int clientFd; /* Client socket file descriptor */  22    int serverLen; /* Length of server address structure */  23    int result; /* From connect () call */  24    struct sockaddr_in serverINETAddress; /* Server address */  25    struct sockaddr* serverSockAddrPtr; /* Pointer to address */  26    unsigned long inetAddress; /* 32-bit IP address */  27  28    /* Set the two server variables */  29    serverSockAddrPtr = (struct sockaddr*) &serverINETAddress;  30    serverLen = sizeof (serverINETAddress); /* Length of address */  31  32    while (1) /* Loop until break */ 
[Page 522]
33 { 34 inetAddress = promptForINETAddress (); /* Get 32-bit IP */ 35 if (inetAddress == 0) break; /* Done */ 36 /* Start by zeroing out the entire address structure */ 37 memset ((char*)&serverINETAddress,0,sizeof(serverINETAddress)); 38 serverINETAddress.sin_family = PF_INET; /* Use Internet */ 39 serverINETAddress.sin_addr.s_addr = inetAddress; /* IP */ 40 serverINETAddress.sin_port = htons (DAYTIME_PORT); 41 /* Now create the client socket */ 42 clientFd = socket (PF_INET, SOCK_STREAM, DEFAULT_PROTOCOL); 43 do /* Loop until a connection is made with the server */ 44 { 45 result = connect (clientFd,serverSockAddrPtr,serverLen); 46 if (result == -1) sleep (1); /* Try again in 1 second */ 47 } 48 while (result == -1); 49 50 readTime (clientFd); /* Read the time from the server */ 51 close (clientFd); /* Close the socket */ 52 } 53 54 exit (/* EXIT_SUCCESS */ 0); 55 } 56 57 /****************************************************************/ 58 59 unsigned long promptForINETAddress () 60 61 { 62 char hostName [100]; /* Name from user: numeric or symbolic */ 63 unsigned long inetAddress; /* 32-bit IP format */ 64 65 /* Loop until quit or a legal name is entered */ 66 /* If quit, return 0 else return host's IP address */ 67 do 68 { 69 printf ("Host name (q = quit, s = self): "); 70 scanf ("%s", hostName); /* Get name from keyboard */ 71 if (strcmp (hostName, "q") == 0) return (0); /* Quit */ 72 inetAddress = nameToAddr (hostName); /* Convert to IP */ 73 if (inetAddress == 0) printf ("Host name not found\n"); 74 } 75 while (inetAddress == 0); 76 return (inetAddress); 77 } 78 /****************************************************************/ 79 80 unsigned long nameToAddr (name)
[Page 523]
81 82 char* name; 83 84 { 85 char hostName [100]; 86 struct hostent* hostStruct; 87 struct in_addr* hostNode; 88 89 /* Convert name into a 32-bit IP address */ 90 91 /* If name begins with a digit, assume it's a valid numeric */ 92 /* Internet address of the form A.B.C.D and convert directly */ 93 if (isdigit (name[0])) return (inet_addr (name)); 94 95 if (strcmp (name, "s") == 0) /* Get host name from database */ 96 { 97 gethostname (hostName,100); 98 printf ("Self host name is %s\n", hostName); 99 } 100 else /* Assume name is a valid symbolic host name */ 101 strcpy (hostName, name); 102 103 /* Now obtain address information from database */ 104 hostStruct = gethostbyname (hostName); 105 if (hostStruct == NULL) return (0); /* Not Found */ 106 /* Extract the IP Address from the hostent structure */ 107 hostNode = (struct in_addr*) hostStruct->h_addr; 108 /* Display a readable version for fun */ 109 printf ("Internet Address = %s\n", inet_ntoa (*hostNode)); 110 return (hostNode->s_addr); /* Return IP address */ 111 } 112 113 /****************************************************************/ 114 115 readTime (fd) 116 117 int fd; 118 119 { 120 char str [200]; /* Line buffer */ 121 122 printf ("The time on the target port is "); 123 while (readLine (fd, str)) /* Read lines until end-of-input */ 124 printf ("%s\n", str); /* Echo line from server to user */ 125 } 126 127 /****************************************************************/ 128
[Page 524]
129 readLine (fd, str) 130 131 int fd; 132 char* str; 133 134 /* Read a single NEWLINE-terminated line */ 135 136 { 137 int n; 138 139 do /* Read characters until NULL or end-of-input */ 140 { 141 n = read (fd, str, 1); /* Read one character */ 142 } 143 while (n > 0 && *str++ != '\n'); 144 return (n > 0); /* Return false if end-of-input */ 145 }


12.6.2.14. Analyzing the Source Code

Now that you've had a brief look through the Internet socket source code, it's time to examine the interesting bits. This program focuses mostly on the client side of an Internet connection, so I'll describe that portion first.

12.6.2.15. Internet Clients

The procedure for creating an Internet client is the same as that of a PF_LOCAL client, except for the initialization of the socket address. I mentioned earlier in this section during the discussion of bind () that an Internet socket address structure is of type struct sockaddr_in, and has four fields:

  • sin_family, the domain of the socket, which should be set to PF_INET

  • sin_port, the port number, which in this case is 13

  • sin_addr, the 32-bit IP number of the client/server

  • sin_zero, which is padding and is not set

When creating the client socket, the only tricky bit is determining the server's 32-bit IP address. promptForINETAddress () [59] gets the host's name from the user and then invokes nameToAddr () [80] to convert it into an IP address. If the user enters a string starting with a digit, inet_addr () is invoked to perform the conversion. Figure 12-64 shows how it works.

Figure 12-64. Description of the inet_addr () library function.

Library Function: in_addr_t inet_addr (const char* string)

inet_addr () returns the 32-bit IP address that corresponds to the A.B.C.D format string. The IP address is in network byte order.



[Page 525]

Network byte order is a host-neutral ordering of bytes in the IP address. This is necessary, because the order of multibyte values can differ from machine to machine, which would make IP addresses nonportable.

If the string doesn't start with a digit, the next step is to see if it's "s," which means the local host. The name of the local host is obtained by gethostname () [97], which works as shown in Figure 12-65.

Figure 12-65. Description of the gethostname () system call.

System Call: int gethostname (char* name, int nameLen)

gethostname () sets the character array pointed to by name of length nameLen to a null-terminated string equal to the local host's name.


Once the symbolic name of the host is determined, the next stage is to look it up in the network host file, "/etc/hosts." This is performed by gethostbyname () [104] (Figure 12-66).

Figure 12-66. Description of the gethostbyname () library function.

Library Function: struct hostent* gethostbyname (const char* name)

gethostbyname () searches the "/etc/hosts" file (and/or DNS database if the host is a DNS client) and returns a pointer to a hostent structure that describes the file entry associated with the string name.

If name is not found in the "/etc/hosts" file, NULL is returned.


The hostent structure has several fields, but the only one we're interested in is a field of type (struct in_addr*) called h_addr. This field contains the host's associated IP number in a subfield called s_addr. Before returning this IP number, the program displays a string description of the IP address by calling inet_ntoa () [109] (Figure 12-67).

Figure 12-67. Description of the inet_ntoa () library function.

Library Function: char* inet_ntoa (struct in_addr address)

inet_ntoa () takes a structure of type in_addr as its argument and returns a pointer to a string that describes the address in the format A.B.C.D.


The final 32-bit address is then returned by line 110. Once the IP address inetAddress has been determined, the client's socket address fields are filled by lines 37..40.


[Page 526]

37  memset ((char*)&serverINETAddress,0,sizeof(serverINETAddress)); 38  serverINETAddress.sin_family = PF_INET; /* Use Internet */ 39  serverINETAddress.sin_addr.s_addr = inetAddress; /* IP */ 40  serverINETAddress.sin_port = htons (DAYTIME_PORT); 


memset () clears the socket address structure's contents before its fields are assigned (Figure 12-68).

Figure 12-68. Description of the memset () library function.

Library Function: void memset (void* buffer, int value, size_t length)

memset () fills the array buffer of size length with the value of value.


memset () is defined by the POSIX standard and was originally part of System V UNIX. If you port or look at BSD UNIX application code, you may also find a similar library function called bzero () (Figure 12-69).

Figure 12-69. Description of the bzero () library function.

Library Function: void bzero (void* buffer, size_t length)

bzero () fills the array buffer of size length with zeroes (ASCII NULL).


Both functions are supported by Linux, but bzero () is deprecated, thus memset () should be used in new code. memset () is preferable since it can be used to set values other than zero.

Like the IP address, the port number is also converted to a network byte ordering by htons () (Figure 12-70).

Figure 12-70. Description of the htonl (), htons (), ntohl (), and ntohs () library functions.

Library Function: in_addr_t htonl (in_addr_t hostLong)

in_port_t htons (in_port_t hostShort)

in_addr_t ntohl (in_addr_t networkLong)

in_port_t ntohs (in_port_t networkShort)

Each of these functions performs a conversion between a host-format number and a network-format number. For example, htonl () returns the network-format equivalent of the host-format unsigned long hostLong, and ntohs () returns the host-format equivalent of the network-format unsigned short networkShort.



[Page 527]

The final step is to create the client socket and attempt the connection. The code for this is almost the same as for PF_LOCAL sockets:

42      clientFd = socket (PF_INET, SOCK_STREAM, DEFAULT_PROTOCOL); 43      do /* Loop until a connection is made with the server */ 44        { 45          result = connect (clientFd,serverSockAddrPtr,serverLen); 46          if (result == -1) sleep (1); /* Try again in 1 second */ 47        } 48      while (result == -1); 


The rest of the program contains nothing new. Now it's time to look at how an Internet server is built.

12.6.2.16. Internet Servers

Constructing an Internet server is actually pretty easy. The sin_family, sin_port, and sin_zero fields of the socket address structure should be filled in, as they were in the client example. The only difference is that the s_addr field should be set to the network byte ordered value of the constant INADDR_ANY, which means "accept any incoming client requests." The following is an example of how to create a server socket address:

int serverFd; /* Server socket struct sockaddr_in serverINETAddress; /* Server Internet address */ struct sockaddr* serverSockAddrPtr; /* Pointer to server address */ struct sockaddr_in clientINETAddress; /* Client Internet address */ struct sockaddr* clientSockAddrPtr; /* Pointer to client address */ int port = 13; /* Set to the port that you wish to serve */ int serverLen; /* Length of address structure */ serverFd = socket (PF_INET, SOCK_STREAM, DEFAULT_PROTOCOL);/* Create */ serverLen = sizeof (serverINETAddress); /* Length of structure */ bzero ((char*) &serverINETAddress, serverLen); /* Clear structure */ serverINETAddress.sin_family = PF_INET; /* Internet domain */ serverINETAddress.sin_addr.s_addr = htonl (INADDR_ANY);/* Accept  all */ serverINETAddress.sin_port = htons (port); /* Server port number */ 


When the address is created, the socket is bound to the address, and its queue size is specified in the usual way:

serverSockAddrPtr = (struct sockaddr*) &serverINETAddress; bind (serverFd, serverSockAddrPtr, serverLen); listen (serverFd, 5); 



[Page 528]

The final step is to accept client connections. When a successful connection is made, the client socket address is filled with the client's IP address and a new file descriptor is returned:

clientLen = sizeof (clientINETAddress); clientSockAddrPtr = (struct sockaddr*) clientINETAddress; clientFd = accept (serverFd, clientSockAddrPtr, &clientLen); 


As you can see, an Internet server's code is very similar to that of a PF_LOCAL server.




Linux for Programmers and Users
Linux for Programmers and Users
ISBN: 0131857487
EAN: 2147483647
Year: 2007
Pages: 339

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net