Book "Linux API. Comprehensive guide»


Book "Linux API. Comprehensive guide»

Good afternoon I bring to your attention the book “Linux API. Comprehensive guide "(translation of the book The Linux Programming Interface). It can be ordered on the publisher's website, and if you apply the promo code LinuxAPI you will get a 30% discount.

An excerpt from the book for review:

Sockets: server architecture

In this chapter, we will discuss the basics of designing iterative and parallel servers, as well as a special inetd daemon that facilitates the creation of server-side Internet applications.

Iteration and Parallel Servers

There are two common socket-based network server architectures:

  • iterative: the server serves clients one at a time, first processing the request (or several requests) of one client and then moving on to the next;

  • parallel: the server is designed to serve multiple clients at the same time.

Section 44.8 has already provided an example of an iteration server based on FIFO queues.

Iteration servers are usually only suitable in situations where client requests can be processed fairly quickly, as each client has to wait until any other clients in front of it have been served. A common use case for this approach is to exchange single requests and responses between a client and a server.

Parallel servers are suitable in cases where each request takes a significant amount of time to process, or the client and server have a long exchange of messages. In this chapter, we will mainly focus on the traditional (and easiest) way to design parallel servers, which is to create a separate child process for each new client. Such a process does all the work of serving the client, after which it ends. Since each of these processes operates independently, it is possible to serve multiple clients at the same time. The main task of the main server process (parent) is to create a separate child for each new client (alternatively, instead of processes, you can create threads of execution).

In the following sections, we will look at examples of iterative and parallel servers based on internet domain sockets. These two servers implement a simplified version of the echo service (RFC 862) that returns a copy of any message sent to it by the client.

echo iteration udp server

In this and the next section, we will introduce servers for the echo service. It is available on port number 7 and works both over UDP and TCP (this port is reserved, and therefore the echo server must be run with administrator privileges).

The echo UDP server constantly reads datagrams and returns a copy of them to the sender. Since the server only needs to process one message at a time, an iterative architecture will suffice here. The header file for servers is shown in Listing 56.1-XNUMX.

Listing 56.1. Header file for programs id_echo_sv.c and id_echo_cl.c

#include "inet_sockets.h" /* Declares our socket functions */
#include "tlpi_hdr.h"

#define SERVICE "echo" /* UDP service name */

#define BUF_SIZE 500 /* Maximum size of datagrams that
can be read by client and server */
________________________________________________________________________ sockets/id_echo.h

Listing 56.2-XNUMX shows the server implementation. It is worth noting the following points:

  • to put the server into daemon mode, we use the becomeDaemon() function from Section 37.2;

  • to make the program more compact, we use the internet domain socket library developed in section 55.12;

  • if the server cannot return a response to the client, it writes a message to the log using the syslog() call.

In a real application, we would most likely impose a certain limit on the frequency of logging messages using syslog(). This would eliminate the possibility of an attacker overflowing the system log. Also, keep in mind that each syslog() call is quite expensive, since it uses fsync() by default.

Listing 56.2. An iteration server that implements the echo UDP service

_________________________________________________________________sockets/id_echo_sv.c
#include
#include "id_echo.h"
#include "become_daemon.h"

int
main(int argc, char *argv[])
{
int sfd;
ssize_t numRead;
socklen_tlen;
struct sockaddr_storage claddr;
charbuf[BUF_SIZE];
char addrStr[IS_ADDR_STR_LEN];

if (becomeDaemon(0) == -1)
errExit("becomeDaemon");

sfd = inetBind(SERVICE, SOCK_DGRAM, NULL);
if (sfd == -1) {
syslog(LOG_ERR, "Could not create server socket (%s)",
strerror(errno));
exit (EXIT_FAILURE);

/* Receive datagrams and return copies to senders */
}
for(;;) {
len = sizeof(struct sockaddr_storage);
numRead = recvfrom(sfd, buf, BUF_SIZE, 0, (struct sockaddr *) &claddr, &len);

if (numRead == -1)
errExit("recvfrom");
if (sendto(sfd, buf, numRead, 0, (struct sockaddr *) &claddr, len)
!=numRead)
syslog(LOG_WARNING, "Error echoing response to %s (%s)",
inetAddressStr((struct sockaddr *) &claddr, len,
addrStr, IS_ADDR_STR_LEN),
strerror(errno));
}
}
_________________________________________________________________sockets/id_echo_sv.c

We use the program in listing 56.3 to test the server. It also uses the Internet domain socket library developed in Section 55.12. The client program takes the name of the network host where the server is located as the first argument on the command line. The client enters a loop where it sends each of the remaining arguments to the server as separate datagrams, and then reads and outputs the datagrams received from the server in response.

Listing 56.3. Client for echo UDP service

#include "id_echo.h"

int
main(int argc, char *argv[])
{
int sfd,j;
size_tlen;
ssize_t numRead;
charbuf[BUF_SIZE];

if (argc < 2 || strcmp(argv[1], "--help") == 0)
usageErr("%s host msg…n", argv[0]);

/* Form the server address based on the first command line argument */
sfd = inetConnect(argv[1], SERVICE, SOCK_DGRAM);
if (sfd == -1)
fatal("Could not connect to server socket");

/* Send the rest of the arguments to the server as separate datagrams */
for (j = 2; j < argc; j++) {
len = strlen(argv[j]);
if (write(sfd, argv[j], len) != len)
fatal("partial/failed write");

numRead = read(sfd, buf, BUF_SIZE);
if (numRead == -1)
errExit("read");
printf("[%ld bytes] %.*sn", (long) numRead, (int) numRead, buf);
}
exit (EXIT_SUCCESS);
}
_________________________________________________________________sockets/id_echo_cl.c

The following is an example of what we will see when we start the server and two client instances:

$ su // Requires privileges to bind to a reserved port
Password:
# ./id_echo_sv // The server goes into the background
# exit // Relinquish admin rights
$ ./id_echo_cl localhost hello world // This client sends two datagrams
[5 bytes] hello // The client outputs the response received from the server
[5 bytes] world
$ ./id_echo_cl localhost goodbye // This client sends one datagram
[7 bytes] goodbye

I wish you a pleasant reading)

Source: linux.org.ru