Network Interface Guide

Socket Basics

This section describes the use of the basic socket interfaces.

Socket Creation

The socket(3SOCKET) call creates a socket in the specified family and of the specified type.

s = socket(family, type, protocol);
If the protocol is unspecified (a value of 0), the system selects a protocol that supports the requested socket type. The socket handle (a file descriptor) is returned.

The family is specified by one of the constants defined in sys/socket.h. Constants named AF_suite specify the address format to use in interpreting names, as shown in Table 2-1.

Table 2-1 Protocol Family

AF_APPLETALK

Apple Computer Inc. Appletalk network 

AF_INET6

Internet family for IPv6 and IPv4 

AF_INET

Internet family for IPv4 only 

AF_PUP

Xerox Corporation PUP internet 

AF_UNIX

Unix file system 

Socket types are defined in sys/socket.h. These types--SOCK_STREAM, SOCK_DGRAM, or SOCK_RAW--are supported by AF_INET6, AF_INET, and AF_UNIX. The following creates a stream socket in the Internet family:

s = socket(AF_INET6, SOCK_STREAM, 0);

This call results in a stream socket with the TCP protocol providing the underlying communication. Use the default protocol (the protocol argument is 0) in most situations. You can specify a protocol other than the default, as described in "Advanced Topics".

Binding Local Names

A socket is created with no name. A remote process has no way to refer to a socket until an address is bound to it. Communicating processes are connected through addresses. In the Internet family, a connection is composed of local and remote addresses, and local and remote ports. There can never be duplicate ordered sets, such as: protocol, local address, local port, foreign address, foreign port. In most families, connections must be unique.

The bind(3SOCKET) call allows a process to specify the local address of the socket. This forms the set local address, local port. connect(3SOCKET), and accept(3SOCKET) complete a socket's association by fixing the remote half of the address tuple. The bind(3SOCKET) call is used as follows:

bind (s, name, namelen);

The socket handle is s. The bound name is a byte string that is interpreted by the supporting protocol(s). Internet family names contain an Internet address and port number.

This example demonstrates binding an Internet address:

#include <sys/types.h>
#include <netinet/in.h>
...
struct sockaddr_in6 sin6;
...
		s = socket(AF_INET6, SOCK_STREAM, 0);
		bzero (&sin6, sizeof (sin6));
		sin6.sin6_family = AF_INET6;
		sin6.sin6_addr.s6_addr = in6addr_arg;
		sin6.sin6_port = htons(MYPORT);
		bind(s, (struct sockaddr *) &sin6, sizeof sin6);

The content of the address sin6 is described in "Address Binding", where Internet address bindings are discussed.

Connection Establishment

Connection establishment is usually asymmetric, with one process acting as the client and the other as the server. The server binds a socket to a well-known address associated with the service and blocks on its socket for a connect request. An unrelated process can then connect to the server. The client requests services from the server by initiating a connection to the server's socket. On the client side, the connect(3SOCKET) call initiates a connection. In the Internet family, this might appear as:

struct sockaddr_in6 server;
 ...
connect(s, (struct sockaddr *)&server, sizeof server);

If the client's socket is unbound at the time of the connect call, the system automatically selects and binds a name to the socket. See "Address Binding". This is the usual way that local addresses are bound to a socket on the client side.

To receive a client's connection, a server must perform two steps after binding its socket. The first is to indicate how many connection requests can be queued. The second step is to accept a connection:

struct sockaddr_in6 from;
 ...
listen(s, 5);                /* Allow queue of 5 connections */
fromlen = sizeof(from);
newsock = accept(s, (struct sockaddr *) &from, &fromlen);

The socket handle s is the socket bound to the address to which the connection request is sent. The second parameter of listen(3SOCKET) specifies the maximum number of outstanding connections that might be queued. from is a structure that is filled with the address of the client. A NULL pointer might be passed. fromlen is the length of the structure. (In the UNIX family, from is declared a struct sockaddr_un.)

accept(3SOCKET) normally blocks. accept(3SOCKET) returns a new socket descriptor that is connected to the requesting client. The value of fromlen is changed to the actual size of the address.

A server cannot indicate that it accepts connections only from specific addresses. The server can check the from address returned by accept(3SOCKET) and close a connection with an unacceptable client. A server can accept connections on more than one socket, or avoid blocking on the accept call. These techniques are presented in "Advanced Topics".

Connection Errors

An error is returned if the connection is unsuccessful (however, an address bound by the system remains). Otherwise, the socket is associated with the server and data transfer can begin.

Table 2-2 lists some of the more common errors returned when a connection attempt fails.

Table 2-2 Socket Connection Errors

Socket Errors 

Error Description 

ENOBUFS

Lack of memory available to support the call. 

EPROTONOSUPPORT

Request for an unknown protocol. 

EPROTOTYPE

Request for an unsupported type of socket. 

ETIMEDOUT

No connection established in specified time. This happens when the destination host is down or when problems in the network result in lost transmissions. 

ECONNREFUSED

The host refused service. This happens when a server process is not present at the requested address. 

ENETDOWN or EHOSTDOWN

These errors are caused by status information delivered by the underlying communication interface. 

ENETUNREACH or EHOSTUNREACH

These operational errors can occur either because there is no route to the network or host, or because of status information returned by intermediate gateways or switching nodes. The status returned is not always sufficient to distinguish between a network that is down and a host that is down. 

Data Transfer

This section describes the functions to send and receive data. You can send or receive a message with the normal read(2) and write(2) interfaces:

write(s, buf, sizeof buf);
read(s,  buf, sizeof buf);

Or the calls send(3SOCKET) and recv(3SOCKET) can be used:

send(s, buf, sizeof buf, flags);
recv(s, buf, sizeof buf, flags);

send(3SOCKET) and recv(3SOCKET) are very similar to read(2) and write(2), but the flags argument is important. The flags, defined in sys/socket.h, can be specified as a nonzero value if one or more of the following is required:

MSG_OOB

Send and receive out-of-band data

MSG_PEEK

Look at data without reading

MSG_DONTROUTE

Send data without routing packets

Out-of-band data is specific to stream sockets. When MSG_PEEK is specified with a recv(3SOCKET) call, any data present is returned to the user but treated as still unread. The next read(2) or recv(3SOCKET) call on the socket returns the same data. The option to send data without routing packets applied to the outgoing packets is currently used only by the routing table management process and is unlikely to be interesting to most users.

Closing Sockets

A SOCK_STREAM socket can be discarded by a close(2) function call. If data is queued to a socket that promises reliable delivery after a close(2), the protocol continues to try to transfer the data. If the data is still undelivered after an arbitrary period, it is discarded.

A shutdown(3SOCKET) closes SOCK_STREAM sockets gracefully. Both processes can acknowledge that they are no longer sending. This call has the form:

shutdown(s, how);

Where how is defined as:

Disallows further receives 

Disallows further sends 

Disallows both further sends and receives 

Connecting Stream Sockets

Figure 2-1 and the next two examples illustrate initiating and accepting an Internet family stream connection.

Figure 2-1 Connection-Oriented Communication Using Stream Sockets

Graphic

The program in Example 2-1 is a server. It creates a socket and binds a name to it, then displays the port number. The program calls listen(3SOCKET) to mark the socket ready to accept connection requests and initialize a queue for the requests. The rest of the program is an infinite loop. Each pass of the loop accepts a new connection and removes it from the queue, creating a new socket. The server reads and displays the messages from the socket and closes it. The use of in6addr_any is explained in "Address Binding".


Example 2-1 Accepting an Internet Stream Connection (Server)

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
 
#define TRUE 1
 
/*
* This program creates a socket and then begins an infinite loop.
* Each time through the loop it accepts a connection and prints
* data from it. When the connection breaks, or the client closes
* the connection, the program accepts a new connection.
*/
 
main()
{
   int sock, length;
   struct sockaddr_in6 server;
   int msgsock;
   char buf[1024];
   int rval;
 
   /* Create socket. */
   sock = socket(AF_INET6, SOCK_STREAM, 0);
   if (sock == -1) {
      perror("opening stream socket");
      exit(1);
   }
   /* Bind socket using wildcards.*/
   bzero (&server, sizeof(server));
   bzero (&sin6, sizeof (sin6));
   server.sin6_family = AF_INET6;
   server.sin6_addr.s6_addr = in6addr_any;
   server.sin6_port = 0;
   if (bind(sock, (struct sockaddr *) &server, sizeof server)
         == -1) {
      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)
         == -1) {
      perror("getting socket name");
      exit(1);
   }
   printf("Socket port #%d\n", ntohs(server.sin6_port));
   /* Start accepting connections. */
   listen(sock, 5);
   do {
      msgsock = accept(sock,(struct sockaddr *) 0,(int *) 0);
      if (msgsock == -1
         perror("accept");
      else do {
         memset(buf, 0, sizeof buf);
         if ((rval = read(msgsock,buf, 1024)) == -1)
            perror("reading stream message");
         if (rval == 0)
            printf("Ending connection\n");
         else
            /* assumes the data is printable */
            printf("-->%s\n", buf);
      } while (rval > 0);
      close(msgsock);
   } while(TRUE);
   /*
    * Since this program has an infinite loop, the socket "sock" is
    * never explicitly closed. However, all sockets will be closed
    * automatically when a process is killed or terminates normally.
    */
   exit(0);
}

To initiate a connection, the client program in Example 2-2 creates a stream socket and calls connect(3SOCKET), specifying the address of the socket for connection. If the target socket exists and the request is accepted, the connection is complete and the program can send data. Data are delivered in sequence with no message boundaries. The connection is destroyed when either socket is closed. For more information about data representation routines, such as ntohl(3SOCKET), ntohs(3SOCKET), htons(3SOCKET), and htonl(3XNET), in this program, see the byteorder(3SOCKET) man page.


Example 2-2 Internet family Stream Connection (Client)

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
 
#define DATA "Half a league, half a league . . ."
 
/*
 * This program creates a socket and initiates a connection with
 * the socket given in the command line. Some data are sent over the
 * connection and then the socket is closed, ending the connection.
 * The form of the command line is: streamwrite hostname portnumber
 * Usage: pgm host port
 */
main(argc, argv)
    int argc;
    char *argv[];
{
    int sock, errnum h_addr_index;
    struct sockaddr_in6 server;
    struct hostent *hp;
    char buf[1024];
 
    /* Create socket. */
    sock = socket( AF_INET6, SOCK_STREAM, 0);
    if (sock == -1) {
        perror("opening stream socket");
        exit(1);
    }
    /* Connect socket using name specified by command line. */
    bzero (&sin6, sizeof (sin6));
    server.sin6_family = AF_INET6;
    hp = getipnodebyname(AF_INET6, argv[1], AI_DEFAULT, &errnum);
/*
 * getinodebyname returns a structure including the network address
 * of the specified host.
 */
    if (hp == (struct hostent *) 0) {
        fprintf(stderr, "%s: unknown host\n", argv[1]);
        exit(2);
    }

    h_addr_index = 0;
    while (hp->h_addr_list[h_addr_index] != NULL) {
        bcopy(hp->h_addr_list[h_addr_index], &server.sin6_addr,
                    hp->h_length);
        server.sin6_port = htons(atoi(argv[2]));
        if (connect(sock, (struct sockaddr *) &server,
                    sizeof (server)) == -1) {
            if (hp->h_addr_list[++h_addr_index] != NULL) {
                /* Try next address */
                continue;
            }
            perror("connecting stream socket");
            freehostent(hp);
            exit(1);
        }
        break;
    }
    freehostent(hp);
    if (write( sock, DATA, sizeof DATA) == -1)
        perror("writing on stream socket");
    close(sock);
    freehostent (hp);
    exit(0);
}

Datagram Sockets

A datagram socket provides a symmetric data exchange interface. There is no requirement for connection establishment. Each message carries the destination address. Figure 2-2 shows the flow of communication between server and client.


Note -

The bind(3SOCKET) step shown below for the server is optional.


Figure 2-2 Connectionless Communication Using Datagram Sockets

Graphic

Datagram sockets are created as described in "Socket Creation". If a particular local address is needed, the bind(3SOCKET) operation must precede the first data transmission. Otherwise, the system sets the local address and/or port when data is first sent. To send data, sendto(3SOCKET) is used:

sendto(s, buf, buflen, flags, (struct sockaddr *) &to, tolen);

The s, buf, buflen, and flags parameters are the same as in connection-oriented sockets. The to and tolen values indicate the address of the intended recipient of the message. A locally detected error condition (such as an unreachable network) causes a return of -1 and errno to be set to the error number.

To receive messages on a datagram socket, recvfrom(3SOCKET) is used:

recvfrom(s, buf, buflen, flags, (struct sockaddr *) &from, &fromlen);

Before the call, fromlen is set to the size of the from buffer. On return, it is set to the size of the address from which the datagram was received.

Datagram sockets can also use the connect(3SOCKET) call to associate a socket with a specific destination address. It can then use the send(3SOCKET) call. Any data sent on the socket without explicitly specifying a destination address is addressed to the connected peer, and only data received from that peer is delivered. Only one connected address is permitted for one socket at a time. A second connect(3SOCKET) call changes the destination address. Connect requests on datagram sockets return immediately. The system records the peer's address. accept(3SOCKET), and listen(3SOCKET) are not used with datagram sockets.

While a datagram socket is connected, errors from previous send(3SOCKET) calls can be returned asynchronously. These errors can be reported on subsequent operations on the socket, or an option of getsockopt(3SOCKET), SO_ERROR, can be used to interrogate the error status.

Example 2-3 shows how to send an Internet call by creating a socket, binding a name to the socket, and sending the message to the socket.


Example 2-3 Sending an Internet Family Datagram

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
 
#define DATA "The sea is calm, the tide is full . . ."
 
/*
 * Here I send a datagram to a receiver whose name I get from
 * the command line arguments. The form of the command line is:
 * dgramsend hostname portnumber
 */
main(argc, argv)
   int argc, errnum;
   char *argv[];
{
   int sock;
   struct sockaddr_in6 name;
   struct hostent *hp;
 
   /* Create socket on which to send. */
   sock = socket(AF_INET6,SOCK_DGRAM, 0);
   if (sock == -1) {
      perror("opening datagram socket");
      exit(1);
   }
   /*
    * Construct name, with no wildcards, of the socket to ``send''
    * to. getinodebyname returns a structure including the network
    * address of the specified host. The port number is taken from
    * the command line.
    */
   hp = getipnodebyname(AF_INET6, argv[1], AI_DEFAULT, &errnum);
   if (hp == (struct hostent *) 0) {
      fprintf(stderr, "%s: unknown host\n", argv[1]);
      exit(2);
   }
   bzero (&sin6, sizeof (sin6));
   bzero (&name, sizeof (name));
   memcpy((char *) &name.sin6_addr, (char *) hp->h_addr,
      hp->h_length);
   name.sin6_family = AF_INET6;
   name.sin6_port = htons(atoi(argv[2]));
   /* Send message. */
   if (sendto(sock,DATA, sizeof DATA ,0,
      (struct sockaddr *) &name,sizeof name) == -1)
      perror("sending datagram message");
   close(sock);
   exit(0);
}

Example 2-4 shows how to read an Internet call by creating a socket, binding a name to the socket, and then reading from the socket.


Example 2-4 Reading Internet Family Datagrams

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
 
/*
 * This program creates a datagram socket, binds a name to it, then
 * reads from the socket.
 */
 
main()
{
   int sock, length;
   struct sockaddr_in6 name;
   char buf[1024];
 
   /* Create socket from which to read. */
   sock = socket(AF_INET6, SOCK_DGRAM, 0);
   if (sock == -1) {
      perror("opening datagram socket");
      exit(1);
   }
   /* Create name with wildcards. */
   bzero (&sin6, sizeof (sin6));
   name.sin6_family = AF_INET6;
   name.sin6_addr.s6_addr = in6addr_any;
   name.sin6_port = 0;
   if (bind(sock,(struct sockaddr *)&name, sizeof name) == -1) {
      perror("binding datagram socket");
      exit(1);
   }
   /* Find assigned port value and print it out. */
   length = sizeof(name);
   if (getsockname(sock,(struct sockaddr *) &name, &length)
         == -1) 	{
      perror("getting socket name");
      exit(1);
   }
   printf("Socket port #%d\n", ntohs(name.sin6_port));
   /* Read from the socket. */
   if (read(sock, buf, 1024) == -1 )
      perror("receiving datagram packet");
   /* Assumes the data is printable */
   printf("-->%s\n", buf);
   close(sock);
   exit(0);
}

Input/Output Multiplexing

Requests can be multiplexed among multiple sockets or files. Use select(3C) to do this:

#include <sys/time.h>
#include <sys/types.h>
#include <sys/select.h>
 ...
fd_set readmask, writemask, exceptmask;
struct timeval timeout;
 ...
select(nfds, &readmask, &writemask, &exceptmask, &timeout);

The first argument of select(3C) is the number of file descriptors in the lists pointed to by the next three arguments.

The second, third, and fourth arguments of select(3C) point to three sets of file descriptors: a set of descriptors to read on, a set to write on, and a set on which exception conditions are accepted. Out-of-band data is the only exceptional condition. Any of these pointers can be a properly cast null. Each set is a structure containing an array of long integer bit masks. The size of the array is set by FD_SETSIZE (defined in select.h). The array is long enough to hold one bit for each FD_SETSIZE file descriptor.

The macros FD_SET(fd, &mask) and FD_CLR(fd, &mask) add and delete, respectively, the file descriptor fd in the set mask. The set should be zeroed before use, and the macro FD_ZERO(&mask) clears the set mask.

The fifth argument of select(3C) allows a time-out value to be specified. If the timeout pointer is NULL, select(3C) blocks until a descriptor is selectable, or until a signal is received. If the fields in timeout are set to 0, select(3C) polls and returns immediately.

select(3C) normally returns the number of file descriptors selected. select(3C) returns a 0 if the time-out has expired. select(3C) returns -1 for an error or interrupt with the error number in errno and the file descriptor masks unchanged. For a successful return, the three sets indicate which file descriptors are ready to be read from, written to, or have exceptional conditions pending.

You should test the status of a file descriptor in a select mask with the FD_ISSET(fd, &mask) macro. It returns a nonzero value if fd is in the set mask, and 0 if it is not. Use select(3C) followed by a FD_ISSET(fd, &mask) macro on the read set to check for queued connect requests on a socket.

Example 2-5 shows how to select on a "listening" socket for readability to determine when a new connection can be picked up with a call to accept(3SOCKET). The program accepts connection requests, reads data, and disconnects on a single socket.


Example 2-5 Using select(3C) to Check for Pending Connections

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.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_in6 server;
   int msgsock;
   char buf[1024];
   int rval;
   fd_set ready;
   struct timeval to;
 
   /* Open a socket and bind it as in previous examples. */
 
   /* Start accepting connections. */
   listen(sock, 5);
   do {
      FD_ZERO(&ready);
      FD_SET(sock, &ready);
      to.tv_sec = 5;
      to.tv_usec = 0;
      if (select(sock + 1, &ready, (fd_set *)0, (fd_set *)0, &to) == -1) {
         perror("select");
         continue;
      }
      if (FD_ISSET(sock, &ready)) {
         msgsock = accept(sock, (struct sockaddr *)0,
            (int *)0);
         if (msgsock == -1)
            perror("accept");
         else do {
            memset(buf, 0, sizeof buf);
            if ((rval = read(msgsock, buf, 1024)) == -1)
               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);
	   exit(0);
}

In previous versions of the select(3C) routine, its arguments were pointers to integers instead of pointers to fd_sets. This style of call still works if the number of file descriptors is smaller than the number of bits in an integer.

select(3C) provides a synchronous multiplexing scheme. The SIGIO and SIGURG signals (described in "Advanced Topics") provide asynchronous notification of output completion, input availability, and exceptional conditions.