Go to main content

Oracle® Solaris 11.4 Programming Interfaces Guide

Exit Print View

Updated: November 2020
 
 

Socket Basics

This section describes the use of the basic socket interfaces.

Socket Creation

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

s = socket(family, type, protocol);

If the protocol is unspecified, the system selects a protocol that supports the requested socket type. The socket handle is returned. The socket handle is a file descriptor.

The family is specified by one of the constants that are defined in sys/socket.h. Constants that are named AF_suite specify the address format to use in interpreting names:

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. For more information, see the socket(3C) man page.

The following example creates a stream socket in the Internet family:

s = socket(AF_INET6, SOCK_STREAM, 0);

This call results in a stream socket. The TCP protocol provides the underlying communication. Set the protocol argument to 0, the default, in most situations. You can specify a protocol other than the default, as described in Advanced Socket Topics.

Binding Local Names

A socket is created without a name. A remote process cannot refer to a socket until an address is bound to the socket. Processes that communicate are connected through addresses. In the Internet family, a connection is composed of local and remote addresses and local and remote ports. Duplicate ordered sets, such as: protocol, local address, local port, foreign address, foreign port cannot exist. In most families, connections must be unique.

The bind() interface enables a process to specify the local address of the socket. This interface forms the local address, local port set. connect() and accept() complete a socket's association by fixing the remote half of the address tuple. For more information, see the bind(3C), connect(3C), and accept(3C) man pages.

The bind() 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 protocols. 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 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 request services from the server by initiating a connection to the server's socket. On the client side, the connect() call initiates a connection. In the Internet family, this connection 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. For more information, see Address Binding. This automatic selection is the usual way to bind local addresses 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 step 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() specifies the maximum number of outstanding connections that might be queued. The from structure is filled with the address of the client. A NULL pointer might be passed. fromlen is the length of the structure. For more information, see the listen(3C) man page.

The accept() routine normally blocks processes. accept() 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. For more information, see the accept(3C) man page.

A server cannot indicate that the server accepts connections from only specific addresses. The server can check the from address returned by accept() 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 Socket Topics.

Connection Errors

An error is returned if the connection is unsuccessful, but an address bound by the system remains. If the connection is successful, the socket is associated with the server and data transfer can begin.

The following table lists some of the more common errors returned when a connection attempt fails.

Table 8  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 error happens when the destination host is down or when problems in the network cause in lost transmissions.
ECONNREFUSED
The host refused service. This error 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 because no route to the network or host exists. These errors can also occur because of status information returned by intermediate gateways or switching nodes. The status information that is 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 interfaces to send and receive data. You can send or receive a message with the read() and write() interfaces as follows:

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

You can also use send() and recv().

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

send() and recv() are similar to read() and write(), but the flags argument is required. The flags argument, which is 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() call, any data present is returned to the user, but treated as still unread. The next read() or recv() 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.

For more information, see the read(2), write(2), send(3C), and recv(3C) man pages.

Closing Sockets

A SOCK_STREAM socket can be discarded by a close() interface call. If data is queued to a socket that delivers after a close(), the protocol continues to transfer the data. The data is discarded if it remains undelivered after an arbitrary period. For more information, see the close(2) man page.

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

shutdown(s, how);

where how is defined as:

0

Disallows further data reception

1

Disallows further data transmission

2

Disallows further transmission and further reception

For more information about closing SOCK_STREAM sockets, see the shutdown(3C) man page.

Connecting Stream Sockets

The following two examples illustrate initiating and accepting an Internet family stream connection.

Figure 5  Connection-Oriented Communication Using Stream Sockets

image:This graphic depicts data flow between a client and a server, using the accept/connect and read/write function pairs.

The following example program is a server. The server creates a socket and binds a name to the socket, then displays the port number. The program calls listen() to mark the socket as ready to accept connection requests and to 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 the socket. The use of in6addr_any is explained in Address Binding.

Example 21  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));
    server.sin6_family = AF_INET6;
    server.sin6_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, sizeof(buf))) == -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 are closed
     * automatically when a process is killed or terminates normally.
     */
    exit(0);
}

To initiate a connection, the client program in Example 22, Initiating an Internet Family Stream Connection (Client) creates a stream socket, then calls connect(), specifying the address of the socket for connection. If the target socket exists, and the request is accepted, the connection is complete. The program can now send data. Data is 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(), ntohs(), htons(), and htonl(), see the byteorder(3C) man page.

Example 22  Initiating an 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(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 (&server, sizeof (server));
    server.sin6_family = AF_INET6;
    hp = getipnodebyname(argv[1], AF_INET6, AI_DEFAULT, &errnum);
/*
 * getipnodebyname 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);
}

You can add support for one-to-one SCTP connections to stream sockets. The following example code adds the –p to an existing program, enabling the program to specify the protocol to use.

Example 23  Adding SCTP Support to a Stream Socket
#include <stdio.h>
#include <netdb.h>
#include <string.h>
#include <errno.h>

int
main(int argc, char *argv[])
{
    struct protoent *proto = NULL;
    int c;
    int s;
    int protocol;

    while ((c = getopt(argc, argv, "p:")) != -1) {
        switch (c) {
        case 'p':
            proto = getprotobyname(optarg);
            if (proto == NULL) {
                fprintf(stderr, "Unknown protocol: %s\n",
                        optarg);
                return (-1);
            }
            break;
        default:
            fprintf(stderr, "Unknown option: %c\n", c);
            return (-1);
        }
    }

    /* Use the default protocol, which is TCP, if not specified. */
    if (proto == NULL)
        protocol = 0;
    else
        protocol = proto->p_proto;

    /* Create a IPv6 SOCK_STREAM socket of the protocol. */
    if ((s = socket(AF_INET6, SOCK_STREAM, protocol)) == -1) {
        fprintf(stderr, "Cannot create SOCK_STREAM socket of type %s: "
                "%s\n", proto != NULL ? proto->p_name : "tcp",
                strerror(errno));
        return (-1);
    }
    printf("Success\n");
    return (0);
}

Input/Output Multiplexing

Requests can be multiplexed among multiple sockets or multiple files. Use select() to multiplex:

#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() is the number of file descriptors in the lists pointed to by the next three arguments.

The second, third, and fourth arguments of select() 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. You can designate any of these pointers as a properly cast null. Each set is a structure that contains an array of long integer bit masks. Set the size of the array with FD_SETSIZE, which is 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() enables the specification of a timeout value. If the timeout pointer is NULL, select() blocks until a descriptor is selectable, or until a signal is received. If the fields in timeout are set to 0, select() polls and returns immediately.

The select() routine returns the number of file descriptors that are selected, or a zero if the timeout has expired. The select() routine 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.

Test the status of a file descriptor in a select mask with the FD_ISSET (fd, &mask) macro. The macro returns a nonzero value if fd is in the set mask. Otherwise, the macro returns zero. Use select() followed by a FD_ISSET (fd, &mask) macro on the read set to check for queued connect requests on a socket. For more information, see the select(3C) man page.

The following example 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(). The program accepts connection requests, reads data, and disconnects on a single socket.

Example 24  Using select() 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, sizeof(buf))) == -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() 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.

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