Go to main content

Oracle® Solaris 11.3 Programming Interfaces Guide

Exit Print View

Updated: April 2019
 
 

Client-Server Programs

The most common form of distributed application is the client-server model. In this scheme, client processes request services from a server process.

An alternate scheme is a service server that can eliminate dormant server processes. An example is inetd, the Internet service daemon. inetd listens at a variety of ports, determined at startup by reading a configuration file. When a connection is requested on an inetd serviced port, inetd spawns the appropriate server to serve the client. Clients are unaware that an intermediary has played any part in the connection. For more information, see inetd Daemon.

Sockets and Servers

Most servers are accessed at well-known Internet port numbers or UNIX family names. The service rlogin is an example of a well-known UNIX family name. The main loop of a remote login server is shown in Example 27, Showing a Server Main Loop.

The server dissociates from the controlling terminal of its invoker unless the server is operating in DEBUG mode.

(void) close(0);
(void) close(1);
(void) close(2);
(void) open("/", O_RDONLY);
(void) dup2(0, 1);
(void) dup2(0, 2);
setsid();

Dissociating prevents the server from receiving signals from the process group of the controlling terminal. After a server has dissociated from the controlling terminal, the server cannot send reports of errors to the terminal. The dissociated server must log errors with syslog. For more information, see the syslog(3C) man page.

The server gets its service definition by calling getaddrinfo. For more information, see the getaddrinfo(3SOCKET) man page.

bzero(&hints, sizeof (hints));
hints.ai_flags = AI_ALL|AI_ADDRCONFIG;
hints.ai_socktype = SOCK_STREAM;
error = getaddrinfo(NULL, "rlogin", &hints, &api);

The result, which is returned in api, contains the Internet port at which the program listens for service requests. Some standard port numbers are defined in /usr/include/netinet/in.h.

The server then creates a socket, and listens for service requests. The bind() routine ensures that the server listens at the expected location. Because the remote login server listens at a restricted port number, the server runs as superuser. The main body of the server is the following loop.

Example 27  Showing a Server Main Loop
/* Wait for a connection request. */
for (;;) {
    faddrlen = sizeof (faddr);
    new_sock = accept(sock, (struct sockaddr *)api->ai_addr,
            api->ai_addrlen)
    if (new_sock == -1) {
        if (errno != EINTR && errno != ECONNABORTED) {
            perror("rlogind: accept");
        }
        continue;
    }
    if (fork() == 0) {
        close (sock);
        doit (new_sock, &faddr);
    }
    close (new_sock);
}
/*NOTREACHED*/

accept() blocks messages until a client requests service. accept() also returns a failure indication if accept() is interrupted by a signal, such as SIGCHLD. The return value from accept() is checked, and an error is logged with syslog, if an error occurs. For more information, see the accept(3SOCKET) and syslog(3C) man pages.

The server then forks a child process, and invokes the main body of the remote login protocol processing. The socket used by the parent to queue connection requests is closed in the child. The socket created by accept is closed in the parent. The address of the client is passed to the server application's doit() routine, which authenticates the client.

Sockets and Clients

This section describes the steps taken by a client process. As in the server, the first step is to locate the service definition for a remote login.

bzero(&hints, sizeof (hints));
hints.ai_flags = AI_ALL|AI_ADDRCONFIG;
hints.ai_socktype = SOCK_STREAM;

error = getaddrinfo(hostname, servicename, &hints, &res);
if (error != 0) {
    (void) fprintf(stderr, "getaddrinfo: %s for host %s service %s\n",
        gai_strerror(error), hostname, servicename);
    return (-1);
}

getaddrinfo() returns the head of a list of addresses in res. The desired address is found by creating a socket and trying to connect to each address returned in the list until one works.

for (aip = res; aip != NULL; aip = aip->ai_next) {
    /*
     * Open socket.  The address type depends on what
     * getaddrinfo() gave us.
     */
    sock = socket(aip->ai_family, aip->ai_socktype,
          aip->ai_protocol);
    if (sock == -1) {
        perror("socket");
        freeaddrinfo(res);
        return (-1);
    }

    /* Connect to the host. */
    if (connect(sock, aip->ai_addr, aip->ai_addrlen) == -1) {
        perror("connect");
        (void) close(sock);
        sock = -1;
        continue;
    }
    break;
}

The socket has been created and has been connected to the desired service. The connect routine implicitly binds sock, because sock is unbound.

Connectionless Servers

Some services use datagram sockets. The rwho service provides status information about hosts that are connected to a local area network. Avoid running in.rwhod because in.rwho causes heavy network traffic. The rwho service broadcasts information to all hosts connected to a particular network. The rwho service is an example of datagram socket use.

A user on a host that is running the rwho server can get the current status of another host with ruptime. For more information, see the rwho(1), in.rwhod(1M), and ruptime(1) man pages.

Typical output is illustrated in the following example.

Example 28  Showing Typical ruptime Output
example1 up 9:45, 5 users, load 1.15, 1.39, 1.31
example2 up 2+12:04, 8 users, load 4.67, 5.13, 4.59
example3 up 10:10, 0 users, load 0.27, 0.15, 0.14
example4 up 2+06:28, 9 users, load 1.04, 1.20, 1.65
example5 up 25+09:48, 0 users, load 1.49, 1.43, 1.41
example6 5+00:05, 0 users, load 1.51, 1.54, 1.56
example7 down 0:24
example8 down 17:04
example9 down 16:09
example10 up 2+15:57, 3 users, load 1.52, 1.81, 1.86

Status information is periodically broadcast by the rwho server processes on each host. The server process also receives the status information. The server also updates a database. This database is interpreted for the status of each host. Servers operate autonomously, coupled only by the local network and its broadcast capabilities.

Use of broadcast is fairly inefficient because broadcast generates a lot of net traffic. Unless the service is used widely and frequently, the expense of periodic broadcasts outweighs the simplicity.

The following example shows a simplified version of the rwho server. The sample code receives status information broadcast by other hosts on the network and supplies the status of the host on which the sample code is running. The first task is done in the main loop of the program: Packets received at the rwho port are checked to be sure they were sent by another rwho server process and are stamped with the arrival time. The packets then update a file with the status of the host. When a host has not been heard from for an extended time, the database routines assume the host is down and logs this information. Because a server might be down while a host is up, this application is prone to error.

Example 29  Printing rwho Server Information
main()
{
    ...
    sp = getservbyname("who", "udp");
    net = getnetbyname("localnet");
    sin.sin6_addr = inet_makeaddr(net->n_net, in6addr_any);
    sin.sin6_port = sp->s_port;
    ...
    s = socket(AF_INET6, SOCK_DGRAM, 0);
    ...
    on = 1;
    if (setsockopt(s, SOL_SOCKET, SO_BROADCAST, &on, sizeof on)
            == -1) {
        syslog(LOG_ERR, "setsockopt SO_BROADCAST: %m");
        exit(1);
    }
    bind(s, (struct sockaddr *) &sin, sizeof sin);
    ...
    signal(SIGALRM, onalrm);
    onalrm();
    while(1) {
        struct whod wd;
        int cc, whod, len = sizeof from;
        cc = recvfrom(s, (char *) &wd, sizeof(struct whod), 0,
            (struct sockaddr *) &from, &len);
        if (cc <= 0) {
            if (cc == -1 && errno != EINTR)
                syslog(LOG_ERR, "rwhod: recv: %m");
            continue;
        }
        if (from.sin6_port != sp->s_port) {
            syslog(LOG_ERR, "rwhod: %d: bad from port",
                ntohs(from.sin6_port));
            continue;
        }
        ...
        if (!verify( wd.wd_hostname)) {
            syslog(LOG_ERR, "rwhod: bad host name from %x",
                ntohl(from.sin6_addr.s6_addr));
            continue;
        }
        (void) sn

printf(path, sizeof(PATH), 

 "%s/whod.%s", RWHODIR, wd.wd_hostname);
        whod = open(path, O_WRONLY|O_CREAT|O_TRUNC

|O_NOFOLLOW, 0666);
        ...
        (void) time(&wd.wd_recvtime);
        (void) write(whod, (char *) &wd, cc);
        (void) close(whod);
    }
    exit(0);
}

The second server task is to supply the status of its host. This requires periodically acquiring system status information, packaging it in a message, and broadcasting it on the local network for other rwho servers to hear. This task is run by a timer. This task is triggered by a signal.

Status information is broadcast on the local network. For networks that do not support broadcast, use multicast.