A Socket-based IPC Tutorial

This chapter covers the following topics:

Introduction

This chapter is adapted with permission from Stuart Sechrest's An Introductory Socket Communication Tutorial. In this chapter, we look at a few simple programs that show how to use pipes and socketpairs and how to do datagram and stream communication via sockets.

The sample programs show different ways of establishing channels of communication and how the data is actually transferred. These programs have been kept as simple as possible so you can use them as models.


Note: You can find the source for the examples in the /usr/demo/src/socket directory.

Design goals of socket-based IPC

The Berkeley UNIX 4.2BSD release introduced new facilities for interprocess communication (IPC) and networking. The basic idea behind these facilities (i.e. sockets) was to integrate them with file-descriptor-based I/O. The main reason for this is that most processes already use file descriptors to read and write data to files.

Typically, a process inherits its file descriptors from its parent. By using the new IPC interface to create file descriptors that represent sockets, parent processes can set up their children's file descriptors so that the children can communicate with other processes, even across the network. Although sockets aren't restricted to being set up by the parent process, this is a very powerful outcome of their design.

Previously, signals and pipes provided the principal mechanisms by which IPC was achieved. However, signals are of limited value since they can transmit only a signal type. And while pipes are more effective than signals-they allow file-descriptor-based I/O in a single direction between two process-both ends of the pipe must be set up by a common ancestor. Furthermore, in their initial implementations, pipes were limited to communication within a single computer.

New meanings for read() and write()

The new uses of file descriptors have affected what the terms ``read'' and ``write'' mean. Originally these terms were fairly simple: when you wrote something, it was delivered; when you read something, you were blocked until the data arrived.

But the new IPC has given rise to other interpretations of these terms. You can now write without full assurance of delivery, and you can check later to catch occasional failures. Messages can be kept as discrete units or merged into a stream. You can ask to read, but insist on not waiting if nothing is immediately available.

Pipes

Pipes are a unidirectional IPC mechanism, with one end of a pipe opened for reading and the other end opened for writing.

To illustrate a common use for pipes, let's look at the shell. When you enter the following command:

ls | more

the shell connects the standard output of ls to the standard input of more via a pipe. Data written into one end of the pipe by ls can be read from the other end by more.

Both the ls and more commands run without knowing they're connected through a pipe. They simply read from file descriptor 0 (standard input) and write to file descriptor 1 (standard output).

Example

In the following sample program, the parent creates a pipe for communication between its child and itself. The parent calls the pipe() function, which creates a pipe and places the file descriptors for the two ends of the pipe into the file-descriptor array (which we called sockets).

The file descriptor that's returned in the low word of the array, sockets[0], is open for reading only, while the file descriptor in the high end, sockets[1], is open for writing only.

The name sockets for the file-descriptor array is unnecessary. Nevertheless, as our discussion progresses, the file descriptors in this array will eventually represent sockets.

tut1.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

#define DATA "Bright star, would I were steadfast as thou art..."

/*
 *  This program creates a pipe, then forks.
 *  The child communicates to the parent over the pipe.
 *  Notice that a pipe is a one-way communications
 *  device. I can write to the output socket  
 *  (sockets[1], the second socket of the array returned 
 *  by  pipe()) and  read from the input socket (sockets[0]), 
 *  but not vice versa.
 */

main()
{
    int sockets[2], child;

    /* Create a pipe */
    if (pipe(sockets) < 0) {
        perror("opening stream socket pair");
        exit(10);
    }

    if ((child = fork()) == -1)
        perror("fork");
    else if (child) {
        char buf[1024];

        /*  This is still the parent.
            It reads the child's message. */
        close(sockets[1]);
        if (read(sockets[0], buf, sizeof(buf)) < 0)
            perror("reading message");
        printf("-->%s\n", buf);
        close(sockets[0]);
    } else {
        /* This is the child.
           It writes a message to its parent. */
        close(sockets[0]);
        if (write(sockets[1], DATA, sizeof(DATA)) < 0)
            perror("writing message");
        close(sockets[1]);
    }
}

If the parent and child need two-way communication via pipes, the parent creates two pipes, one for use in each direction. (In accordance with their plans, both parent and child in the above example close the socket they won't use. Unused descriptors don't have to be closed, but it's good practice.)

Note that a pipe is a stream-communication mechanism; that is, all messages sent through the pipe are placed in order and are reliably delivered.

When the reader asks for a certain number of bytes from this stream, all the available bytes are returned. This may be less than what's requested. Note that these bytes may have come from the same call to write() or from several concatenated calls to write().

Socketpairs

You can view socketpairs as an extension of pipes. Where a pipe can be viewed as a pair of connected sockets for one-way stream communication, a socketpair can be viewed as a pair of connected sockets for two-way stream communication.

Example

To obtain a pair of connected sockets for two-way stream communication, you call the socketpair() function. This function takes as arguments a domain, a style of communication, and a protocol; these arguments are shown in the following example.

Socketpairs have been implemented for only one domain, called the UNIX domain. (Briefly, a domain is a space of names that may be bound to sockets and that implies certain other conventions; we'll look at both domains and protocols in detail in the next section.)

The constants AF_UNIX and SOCK_STREAM are defined in <sys/socket.h>, which in turn requires the file <sys/types.h> for some of its definitions.

tut2.c

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

#define DATA1 "In Xanadu, did Kublai Khan . . ."
#define DATA2 "A stately pleasure dome decree . . ."

/*
 * This  program creates a  pair of connected sockets,
 * then forks and communicates over them.  This is very 
 * similar to communication with pipes, however, socketpairs
 * are  two-way  communications  objects.  Therefore I can
 * send messages in both directions.
 */

main()
{
    int sockets[2], child;
    char buf[1024];

    if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) < 0) {
        perror("opening stream socket pair");
        exit(1);
    }

    if ((child = fork()) == -1)
        perror("fork");
    else if (child) {       /* This is the parent. */
        close(sockets[0]);
        if (read(sockets[1], buf, sizeof(buf)) < 0)
            perror("reading stream message");
        printf("-->%s\n", buf);
        if (write(sockets[1], DATA2, sizeof(DATA2)) < 0)
            perror("writing stream message");
        close(sockets[1]);
    } else {                /* This is the child. */
        close(sockets[1]);
        if (write(sockets[0], DATA1, sizeof(DATA1)) < 0)
            perror("writing stream message");
        if (read(sockets[0], buf, sizeof(buf)) < 0)
            perror("reading stream message");
        printf("-->%s\n", buf);
        close(sockets[0]);
    }
}

Domains and protocols

Pipes and socketpairs are limited to communication between processes with a common ancestor. Whenever you have processes that have no common ancestor and that may be running on different computers, each process has to create its own sockets and then send messages through them.

Domain names

Sockets created by different programs use names to refer to one another. To be used, these names generally must be translated into addresses. The space that an address is drawn from is referred to as a domain.

There are several domains for sockets; you're most likely to encounter these two:


Note: With the exception of socketpair(), QNX doesn't support UNIX domain sockets. But QNX does support Internet domain sockets.

Internet domain

The Internet domain is the UNIX implementation of the DARPA Internet standard protocols IP, TCP, and UDP. Addresses in the Internet domain consist of a machine network address and an identifying number, called a port. Internet domain names allow communication between machines.

Communication types

Two ``types'' or ``styles'' of communication are supported:

Depending on the implementation, different protocols (i.e. IP, UDP, TCP) are used to achieve the required style of communication.

Stream communication

Stream communication, which takes place across a connection between two sockets, is reliable and error-free. And as with pipes, there are no message boundaries.

Reading from a stream may get data sent by one or more calls to the write() function. In some cases, the data returned from a read() call is only part of the data that was written (e.g. there isn't enough room for the entire message, or not all the data from a large message has been transferred).

The protocol implementing stream communication will retransmit messages that are received with errors. It will also return error conditions if a process tries to send a message after the connection has been broken.

Datagram communication

Datagram communication doesn't use connections; each datagram message is addressed individually. If the address is valid, the datagram is usually received, but this isn't guaranteed. Often datagrams are used for requests that need a response from the recipient. If no response arrives in a reasonable amount of time, the application usually repeats the request. The individual datagrams are kept separate when they're read (i.e. message boundaries are kept).

Stream vs datagram

The difference in performance between stream and datagram communication is generally less important than the difference in semantics. The performance gain you might find in using datagrams must be weighed against the increased complexity of the program, which must now concern itself with lost or out-of-order messages. If you can ignore lost messages, then you may wish to consider the quantity of traffic; if you'll use the connection frequently, the expense of setting up a connection for stream communication is justified.

Protocols

A protocol is a set of rules, data formats, and conventions that regulates the transfer of data between communicating processes. In general, there's one protocol for each socket type (stream, datagram, etc.) within each domain. The code that implements a protocol:

Several protocols can implement the same style of communication within a particular domain, each differing only in low-level details. Though you can select which protocol should be used, it's usually sufficient to request the default protocol. We've followed this practice in all of the sample programs.

When creating a socket, you must specify its domain, style, and protocol, as the sample code on the following pages demonstrates.

Datagrams in the Internet domain

When a message must be sent between machines, it's sent to the protocol handler on the destination machine, which interprets the address and determines which socket the message should be delivered to. The socket is bound to a port, which is a delivery slot managed by the functions that implement a particular protocol. Note that if several protocols are active on the same machine, they won't communicate with one another.

Specifying the IP address

The protocol for a socket is chosen when the socket is created. The local machine address for a socket can be any valid IP address of the machine-if the machine has more than one-or it can be the wildcard value INADDR_ANY (this wildcard value is used in the following sample program).

If a computer has several IP addresses, it's likely that messages sent to any of the addresses should be deliverable to a socket. This will be the case if the wildcard value has been chosen. Note that a message cannot be sent to INADDR_ANY.

In the sample program below, the destination hostname is given as a command-line argument. To determine a network address it can send the message to, the program looks up the host address by the call to gethostbyname(). The returned structure includes the host's network address, which is copied into the structure that specifies the message's destination.

Port numbers

You can think of the port number as the number of a mailbox into which the protocol places your messages. Certain daemons offering advertised services have reserved or ``well-known'' port numbers that fall in the range from 1 to 1023. Higher numbers are available to general users. See /etc/services and getservbyname().

Only servers need to ask for a particular number. The system will assign an unused port number when an address is bound to a socket. This may happen when an explicit bind() call is made with a port number of 0, or when a connect() or a send() is performed on an unbound socket. Note that port numbers aren't automatically reported back to the user. After calling bind() with a port value of 0, an application may call getsockname() to discover what port the bind() function actually assigned.

Reading Internet domain datagrams

Let's now look at two programs that create sockets separately. The structure of Internet domain addresses is defined in the file <netinet/in.h>.

tut3.c

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

/*
 * In the included file  <netinet/in.h> a sockaddr_in is  
 * defined as follows:
 * struct sockaddr_in {
 *      short          sin_family;
 *      u_short        sin_port;
 *      struct in_addr sin_addr;
 *      char           sin_zero[8];
 * };
 *
 * This program creates a datagram socket, binds a name to it,
 * then reads from the socket.
 */

main()
{
    int sock, length;
    struct sockaddr_in name;
    char buf[1024];

    /* Create socket from which to read. */
    sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0) {
        perror("opening datagram socket");
        exit(1);
    }
    /* Create name with wildcards. */
    name.sin_family = AF_INET;
    name.sin_addr.s_addr = INADDR_ANY;
    name.sin_port = 0;
    if (bind(sock, (struct sockaddr *)&name, sizeof(name))) {
        perror("binding datagram socket");
        exit(1);
    }
    /* Find assigned port value and print it out. */
    length = sizeof(name);
    if (getsockname(sock, (struct sockaddr *)&name, &length)) {
        perror("getting socket name");
        exit(1);
    }
    printf("Socket has port #%d\n", ntohs(name.sin_port));
    /* Read from the socket */
    if (read(sock, buf, sizeof(buf)) < 0)
        perror("receiving datagram packet");
    printf("-->%s\n", buf);
    close(sock);
}

Sending an Internet domain datagram

In the following example, an Internet domain datagram is sent to a receiver whose name is obtained from command-line arguments. The form of the command line is:

tut4 hostname portnumber

tut4.c

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#define DATA "The sea is calm tonight, the tide is full . . ."

main(int argc, char *argv[])
{
    int sock;
    struct sockaddr_in name;
    struct hostent *hp, *gethostbyname();

    /* Create socket on which to send. */
    sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0) {
        perror("opening datagram socket");
        exit(1);
    }
    /*
     * Construct name, with no wildcards, of the socket to 
     * send to. Gethostbyname() returns a structure including
     * the network address of the specified host. The port
     * number is taken from the command line.
     */
     hp = gethostbyname(argv[1]);
     if (hp == 0) {
         fprintf(stderr, "%s: unknown host\n", argv[1]);
         exit(2);
     }
     memcpy(&name.sin_addr, hp->h_addr, hp->h_length);
     name.sin_family = AF_INET;
     name.sin_port = htons(atoi(argv[2]));
     /* Send message. */
     if (sendto(sock, DATA, sizeof(DATA), 0, 
                  (struct sockaddr *)&name, sizeof(name)) < 0)
         perror("sending datagram message");
     close(sock);
}

The format of the socket address, including the order of the bytes in the address, is specified in part by standards within the Internet domain. Because machines differ in the internal representation they ordinarily use to represent integers, printing out the port number returned by getsockname() may result in a misinterpretation.

To print out the number, you must use the ntohs() function to convert the number from the network representation to the host's representation. On some machines (e.g. 68000-based) this is a null operation; on others (e.g. ix86-based), it results in a swapping of bytes.

Another function, htons(), converts a short integer from the host format to the network format: similar functions exist for long integers. For more information, see ntohl() and htonl() in the TCP/IP Libraries chapter.

Stream connections

For data to be sent between stream sockets (i.e. sockets using the SOCK_STREAM communication style), the sockets must be ``connected.'' The two sample programs below show you how to create such a connection.

The first program initiates a connection by creating a stream socket. It then calls connect(), specifying the address of the target socket it wishes to connect its socket to. If the target socket exists and is prepared to handle a connection, the connection will be completed, and the program can start sending messages. As with pipes, messages will be delivered in order, without message boundaries.

The connection is destroyed when either socket is closed. If a process persists in sending messages after the connection is closed, a SIGPIPE signal is sent to the process. Unless explicit action is taken to handle the signal (see signal() or sigaction()), the process will terminate.

Establishing a stream connection

Forming a stream connection is asymmetrical: one process (e.g. the first program below) requests a connection with a particular socket; the other process accepts connection requests. Before a connection can be accepted, a socket must be created and an address bound to it.

In the first example below, tut5.c is the client. It creates an unnamed socket and then connects to the server by using the specified command-line arguments (which are the values printed out by the server when it started up).

In the second example, tut6.c is the server; it creates a socket and binds a port number to it. It then prints out the port number that the client program needs to know. A connection may be destroyed by closing the corresponding socket.

Initiating an Internet domain stream connection

The sample program below creates a socket and initiates a connection with the socket given in the command line. One message is sent over the connection and then the socket is closed, ending the connection. The form of the command line is:

 
tut5 hostname portnumber

tut5.c

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#define DATA "Half a league, half a league . . ."

main(int argc, char *argv[])
{
    int sock;
    struct sockaddr_in server;
    struct hostent *hp, *gethostbyname();

    /* Create socket */
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        perror("opening stream socket");
        exit(1);
    }
    /* Connect socket using name specified by command line.  */
    server.sin_family = AF_INET;
    hp = gethostbyname(argv[1]);
    if (hp == 0) {
        fprintf(stderr, "%s: unknown host\n", argv[1]);
        exit(2);
    }
    memcpy(&server.sin_addr, hp->h_addr, hp->h_length);
    server.sin_port = htons(atoi(argv[2]));
    if (connect(sock, (struct sockaddr *)&server, 
                sizeof(server)) < 0) {
        perror("connecting stream socket");
        exit(1);
    }
    if (write(sock, DATA, sizeof(DATA)) < 0)
        perror("writing on stream socket");
        close(sock);
    }

Accepting an Internet domain stream connection

The sample program below creates a socket and then begins an infinite loop. Each time through the loop it accepts a connection and prints out messages from it. When the connection breaks, or a termination message comes through, the program accepts a new connection.

Since this program has an infinite loop, the socket sock is never explicitly closed. However, all sockets are closed automatically when a process is killed or terminates normally.

tut6.c

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

#define TRUE 1

main()
{
    int sock, length;
    struct sockaddr_in server;
    int msgsock;
    char buf[1024];
    int rval;
    int i;

    /* Create socket */
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        perror("opening stream socket");
        exit(1);
    }
    /* Name socket using wildcards */
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = INADDR_ANY;
    server.sin_port = 0;
    if (bind(sock, (struct sockaddr *)&server, 
             sizeof(server))) {
        perror("binding stream socket");
        exit(1);
    }
    /* Find out assigned port number and print it out */
    length = sizeof(server);
    if (getsockname(sock, (struct sockaddr *)&server, 
                    &length)) {
        perror("getting socket name");
        exit(1);
    }
    printf("Socket has port #%d\n", ntohs(server.sin_port));

    /* Start accepting connections */
    listen(sock, 5);
    do {
        msgsock = accept(sock, 0, 0);
        if (msgsock == -1) {
            perror("accept");
            return EXIT_FAILURE;
        } else do {
            memset(buf, 0, sizeof(buf));
            if ((rval  = read(msgsock, buf,  1024)) < 0)
                perror("reading stream message");
            else if (rval == 0)
                printf("Ending connection\n");
            else
                printf("-->%s\n", buf)
        } while (rval > 0);
        close(msgsock);
    } while (TRUE);
}

The server

The server program creates a socket, binds a name to the socket, and then prints out the socket number. The server then calls listen() for this socket. Since several clients may attempt to connect more or less simultaneously, a queue of pending connections is maintained in the system address space. The listen() call marks the socket as willing to accept connections and initializes the queue.

When a connection is requested, it's inserted in the queue. If the queue is full, an error status may be returned to the requester. The maximum length of this queue is specified by the second argument of listen() (and limited by the system). Once the listen call has been completed, the program enters an infinite loop. On each pass through the loop, a new connection is accepted and removed from the queue, and hence a new socket for the connection is created.

After the connection is created, the service (in this case printing out the messages) is performed and the connection socket is closed. The accept() call either takes a pending connection request from the queue (if one is available) or blocks, waiting for a request. If successful, this function returns a descriptor for the accepted socket.

Messages are read from the connection socket. Reads from an active connection will normally block until data is available; the number of bytes read is returned. When a connection is destroyed, the read() call returns immediately; the number of bytes returned is zero.

Creating an optimized server

The following program is a slight variation on the previous server design - in this case, accept() won't block. The program calls select() first to check for pending requests before calling accept(). The select() will indicate when a connect request has occurred. This strategy is useful when connections may be received on more than one socket or when data may arrive on other connected sockets before another connection request.

tut7.c

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

#define TRUE 1

/*
 * This program uses select() to check that someone
 * is trying to connect before calling accept().
 */

main()
{
    int sock, length;
    struct sockaddr_in server;
    int msgsock;
    char buf[1024];
    int rval;
    fd_set ready;
    struct timeval to;

    /* Create socket */
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        perror("opening stream socket");
        exit(1);
    }
    /* Name socket using wildcards */
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = INADDR_ANY;
    server.sin_port = 0;
    if (bind(sock, (struct sockaddr *)&server, sizeof(server))) {
        perror("binding stream socket");
        exit(1);
    }
    /* Find out assigned port number and print it out */
    length = sizeof(server);
    if (getsockname(sock, (struct sockaddr *)&server, &length)) {
        perror("getting socket name");
        exit(1);
    }
    printf("Socket has port #%d\n", ntohs(server.sin_port));

    /* Start accepting connections */
    listen(sock, 5);
    do {
        FD_ZERO(&ready);
        FD_SET(sock, &ready);
        to.tv_sec = 5;
        if (select(sock + 1, &ready, 0, 0, &to) < 0) {
            perror("select");
            return EXIT_FAILURE;
        }
        if (FD_ISSET(sock, &ready)) {
            msgsock = accept(sock, (struct  sockaddr *)0,
                             (int *)0);
            if (msgsock == -1) {
                perror("accept");
                return EXIT_FAILURE;
            } else do {
                memset(buf, 0, sizeof(buf));
                if  ((rval  =  read(msgsock, buf,
                                    sizeof(buf))) < 0)
                    perror("reading    stream message");
                else if (rval == 0)
                    printf("Ending connection\n");
                else
                    printf("-->%s\n", buf);
            } while (rval > 0);
            close(msgsock);
        } else
            printf("Do something else\n");
    } while (TRUE);
}

Transferring data through sockets

You can use several functions for reading and writing information. The simplest of these are read() and write().

The write() function takes as arguments a descriptor, a pointer to a buffer containing the data, and the size of the data. The read() function takes a descriptor, a pointer to a buffer in which data can be placed, and the requested amount of data.

The descriptor can indicate either a file or a connected socket. ``Connected'' can mean either a datagram socket or a connected stream socket for which a connect() call has provided a default destination (see connect() in the TCP/IP Libraries chapter in this guide.

Although the write() call requires a connected socket (no destination is specified in the call's parameters), read() can be used for either a connected or an unconnected socket. These calls are quite flexible; you can use them to write applications that require no assumptions about the source of their input or the destination of their output.


Note: Two calls similar to read() and write() -readv() and writev()- allow the source and destination of the input and output to use several separate buffers, while retaining the flexibility to handle both files and sockets. The ``v'' in the names of these calls stands for ``vector.''

Out-of-band data

Sometimes it's necessary to send high-priority data over a connection that may have unread low-priority data at the other end. For example, a user interface process may be interpreting commands and sending them on to another process through a stream connection. The user interface may have filled the stream with as yet unprocessed requests when the user types a command to cancel all outstanding requests. Rather than have the high-priority data wait to be processed after the low-priority data, you can have it sent as out-of-band (OOB) data.

The notification of pending OOB data results in the generation of a SIGURG signal, if this signal has been enabled. To enable this signal, you can use:

ioctl( fd, FIOASYNC, &value);

If value is nonzero, the signal is enabled.

Sending OOB information

To send and receive OOB information, you can use the send() and recv() calls, which are similar to write() and read(). You can use these calls only with sockets; specifying a descriptor for a file will result in the return of an error status. With these calls, you can also peek at data in a stream, i.e. they let a process read data without removing the data from the stream. One way you can use this facility is to read ahead in a stream to determine the size of the next item to be read.

Sending datagrams

To send datagrams using a call to sendto(), you must be able to specify the destination address as an argument.

The recvfrom() call is often used to read datagrams, since the call returns the address of the sender (if it's available) along with the data. If the sender's identity doesn't matter, you may use read() or recv().

Finally, to send and receive messages from multiple buffers, you use the sendmsg() and recvmsg() functions.

Varieties of read and write calls

The various calls for reading and writing are shown below, together with their parameters. The parameters for each function reflect the differences in the behavior of the various calls. In the examples in this chapter, the calls read() and write() have been used whenever possible.

/* The variable descriptor may be the descriptor of either */
/*  a file or a socket. */

int read(int descriptor, char *buf, int nbytes);
int write(int descriptor, char *buf, int nbytes);
/* An iovec can include several source buffers. */

int readv(int descriptor, struct iovec *iov, int iovcnt);
int writev(int descriptor, struct iovec *iovec, int ioveclen);

/* The variable "sock" must be the descriptor of a socket. */
/* Flags may include MSG_OOB, MSG_PEEK and MSG_WAITALL. */

int send(int sock, char *msg, int len, int flags);
int sendto(int sock, char *msg, int len, int flags, 
             struct sockaddr *to, int tolen);
int sendmsg(int sock, struct msghdr msg[], int flags);

int recv(int sock, char *buf, int len, int flags);
int recvfrom(int sock, char *buf, int len, int flags,
             struct sockaddr *from; int *fromlen);
int recvmsg(int sock, struct msghdr msg[], int flags );

Selecting a communications method

We've discussed several forms of IPC. Let's now review the factors that make each attractive.

Pipes and socketpairs

Pipes have the advantage of portability - they're supported in all UNIX systems. What's more, they're relatively simple to use.


Note: The Socket Manager isn't required for pipe IPC.

Socketpairs are also simple to use, and have the additional advantage of allowing bidirectional communication. However, socketpairs require communicating processes to be descendants of a common process.


Note: Traditionally, both pipes and socketpairs were restricted for use between processes running on the same host. QNX's transparent LAN environment supports both IPC methods between processes running on different QNX nodes.

Internet domain sockets

Internet domain sockets allow communication between machines where the underlying network is an Internet. This makes Internet sockets a necessary choice for communication between processes running on QNX and processes running on other machines connected to the Internet.

Datagram vs stream communication

Choosing between datagrams and stream communication requires that you carefully consider the semantic and performance requirements of the application.

Streams have both disadvantages and advantages. One disadvantage is that the number of available file descriptors may be limited by the system (see the -f option to Proc32); this can cause problems if a single server must talk with a large number of clients. Also, when delivering short messages, the stream setup and tear-down time can be unnecessarily long. However, streams have the advantage of built-in reliability. Typically, this is the deciding factor in choosing streams over datagrams.

For more info...

For more information on the topics we've discussed, refer to the TCP/IP Libraries chapter. The following pages are particularly relevant:

For more info on: See:
Creating and naming sockets socket(), bind()
Establishing connections listen(), accept(), connect()
Transferring data read(), write(), recv(), send()
Addresses inet_addr(), inet_aton(), inet_lnaof(), inet_makeaddr(), inet_netof(), inet_network(), inet_ntoa()
Protocols TCP and UDP protocols