A simple client/server sample

The first versions of this example were created for the course High level programming 1. These were based on merging the source files of [PP]. This sample program itself is a simple client/server program, its client side is based on the kliens.c of [PP] (see pp. 133). Now, we have complemented the client to wait for characters plus (+) or minus (-) to be typed in the command line.

The code of the server is placed into a source file called szerver.c that is more complex than the client. It is based on a parallel and multiplexed example of [PP] (see pp. 123) that uses POSIX signal handling. The subject matter of the server and all other servers in [PP] are substantially identical. They implement a simple echo-like server. In the present example, this functionality is complemented by a counter that counts the actions of clients.

The usage of the sources szerver.c and kliens.c is shown in the following YouTube video: http://youtu.be/J--bRSmNjZg,

.

The client side

The whole source code of the server is the following.

#include <stdio.h> #include
          <stdlib.h> #include <unistd.h> #include <string.h>
          //#include <sys/types.h> #include <sys/socket.h>
          #include <netinet/in.h> #define SZERVER_PORT 2012 #define
          BUFFER_MERET 256 int kliens (char b) { int kapu, olvasva; struct
          sockaddr_in szerver; char buffer[BUFFER_MERET]; memset ((void *)
          &szerver, 0, sizeof (szerver)); szerver.sin_family = AF_INET;
          inet_aton ("127.0.0.1", &(szerver.sin_addr)); szerver.sin_port =
          htons (SZERVER_PORT); if ((kapu = socket (PF_INET, SOCK_STREAM,
          IPPROTO_TCP)) == -1) {4 perror ("socket"); return -1; } if (connect
          (kapu, (struct sockaddr *) &szerver, sizeof (szerver)) == -1) {
          perror ("connect"); return -1; } write(kapu, &b, 1); while
          ((olvasva = read (kapu, buffer, BUFFER_MERET)) > 0) write (1,
          buffer, olvasva); close(kapu); return 0; } int main (int argc, char
          *argv[]) { int gyermekem_pid; int statusz; int i; char b = '+'; if
          (argc == 2) b = argv[1][0]; for (i=0; i<4; ++i) if
          ((gyermekem_pid = fork()) == 0) { kliens(b); } else if
          (gyermekem_pid > 0) { kliens(b); // wait(&statusz); } else {
          exit(-1); } return 0; }

In the main function first we check whether the second command-line argument is given or not. If yes, then its first character will be stored in the variable b that will be sent to the server. The default value of b is a plus (+) character.

We are going to try to overload the server. For this reason, TCP connections will be opened simultaneously not sequentially. To be more precise, the following client program opens 30 TCP connections to the server. If you do not understand why exactly the following code snippet works, then suspend the elaboration of this example and first start by solving the next exercise.

for (i=0; i<4; ++i) if
          ((gyermekem_pid = fork()) == 0) { kliens(b); } else if
          (gyermekem_pid > 0) { kliens(b); wait(&statusz); } else {
          exit(-1); }

Example 4.1. 30 processes, with using a paper and pen

Here you have to show, how the next code snippet operates. Just use a paper and pen in this exercise. Your solution is probably good if the result 30=2+4+8+16 can be read easily from your own sketch.

for (i=0; i<4; ++i) if
            ((gyermekem_pid = fork()) == 0) { kliens(b); } else if
            (gyermekem_pid > 0) { kliens(b); // wait(&statusz); } else
            { exit(-1); }


Connecting and communicating with the server are hidden in the kliens function. Here, if the dear reader is not already familiar with the structure struct sockaddr_in, it is described in the manual page man 7 ip. This structure represents a communication endpoint, as it can be seen on the next manual snippet from man 7 ip.

struct sockaddr_in { sa_family_t sin_family; /* address
          family: AF_INET */ in_port_t sin_port; /* port in network byte order
          */ struct in_addr sin_addr; /* internet address */ }; /* Internet
          address. */ struct in_addr { uint32_t s_addr; /* address in network
          byte order */ }; sin_family is always set to AF_INET. This is
          required; in Linux 2.2 most networking functions return EINVAL when
          this setting is missing. sin_port contains the port in network byte
          order. The port numbers below 1024 are called privileged ports (or
          some‐

We need to create this structure and set its members (such as protocol family, IP address and port number) accordingly:

struct sockaddr_in szerver; memset
          ((void *) &szerver, 0, sizeof (szerver)); szerver.sin_family =
          AF_INET; inet_aton ("127.0.0.1", &(szerver.sin_addr));
          szerver.sin_port = htons (SZERVER_PORT);

In the following, if we see unknown function calls or data structures we we will find a more detailed description in the appropriate manual page. For example, in the actual case above, see the page man 3 htons.

Organization of the manual pages

The readers have already noticed that manual pages are organized into numbered sections, as shown in the following breakdown:

  • Section 1, User commands (Linux User's Manual/User Commands/User utilities), for example man w, man who or man passwd

  • Section 2, System calls (Linux Programmer's Manual), man 2 read

  • Section 3, Library functions (Linux Programmer's Manual), man 3 printf

  • Section 4, Special files, man 4 stdin

  • Section 5, Formats and protocols man 5 passwd

  • Section 6, Games, man 6 fortune

  • Section 7, Miscellaneous, man 7 hier

  • Section 8, System administration, man 8 shutdown

(For further details, see man man.)

The man {section number (1-8)} intro command gives a general description of the above sections.

Then, using the socket system call, we create the abstraction of a communication endpoint that will be connected to the previously specified address by the connect system call. It should be noted that the number returned by the socket is a file descriptor. (See also the Párhuzamos programozás GNU/Linux környezetben: SysV IPC, P-szálak, OpenMP http://www.inf.unideb.hu/~nbatfai/konyvek/PARP/parp.book.xml.pdf, [PARP] book about the appearance of file descriptors in the kernel.)

The program sends a byte to this file descriptor using the write system call, then reads a byte from the same file descriptor using the read and so on in the while loop, as long as it can read while it is echoing the read data to the standard output.

write(kapu, &b, 1); while ((olvasva
          = read (kapu, buffer, BUFFER_MERET)) > 0) write (1, buffer,
          olvasva);

The portability of the example

If we compile the sources on different GNU/Linux environments we will get warnings from the compiler. In most of such cases it is typically enough to include the appropriate header files to solve these problems.

NOTES POSIX.1-2001 does not require the inclusion of
              <sys/types.h>, and this header file is not required on
              Linux. However, some historical (BSD) implementations required
              this header file, and portable applications are probably wise to
              include it.

The server side

The whole source code of the server can be found in the source file called szerver.c.

// szerver.c // // "forkos, socketes,
          multiplexelt, szemaforos, osztott memóriás" állatorvosi // szerver
          ló a laborra Programozó Páternoszter // // Copyright (C) 2011, 2012
          Bátfai Norbert, nbatfai@inf.unideb.hu // // This program is free
          software: you can redistribute it and/or modify // it under the
          terms of the GNU General Public License as published by // the Free
          Software Foundation, either version 3 of the License, or // (at your
          option) any later version. // // This program is distributed in the
          hope that it will be useful, // but WITHOUT ANY WARRANTY; without
          even the implied warranty of // MERCHANTABILITY or FITNESS FOR A
          PARTICULAR PURPOSE. See the // GNU General Public License for more
          details. // // You should have received a copy of the GNU General
          Public License // along with this program. If not, see
          <http://www.gnu.org/licenses/>. // // Ez a program szabad
          szoftver; terjeszthető illetve módosítható a // Free Software
          Foundation által kiadott GNU General Public License //
          dokumentumában leírtak; akár a licenc 3-as, akár (tetszőleges)
          későbbi // változata szerint. // // Ez a program abban a reményben
          kerül közreadásra, hogy hasznos lesz, // de minden egyéb GARANCIA
          NÉLKÜL, az ELADHATÓSÁGRA vagy VALAMELY CÉLRA // VALÓ
          ALKALMAZHATÓSÁGRA való származtatott garanciát is beleértve. //
          További részleteket a GNU General Public License tartalmaz. // // A
          felhasználónak a programmal együtt meg kell kapnia a GNU General //
          Public License egy példányát; ha mégsem kapta meg, akkor // tekintse
          meg a <http://www.gnu.org/licenses/> oldalon. // // // Version
          history: // // 0.0.1
          http://progpater.blog.hu/2011/03/06/halozati_vegyertek // 0.0.2
          előkészítés a PARP könyvhöz #include <stdio.h> #include
          <stdlib.h> #include <string.h> #include
          <netinet/in.h> #include <sys/socket.h> #include
          <time.h> #include <arpa/inet.h> #include
          <unistd.h> #include <fcntl.h> #include
          <sys/select.h> #include <signal.h> #include
          <errno.h> #include <sys/sem.h> #include
          <sys/shm.h> #include <sys/wait.h> #define SZERVER_PORT
          2012 #define SZERVER_SOR_MERET 10 #define CTIME_BUFFER_MERET 128 int
          kiszolgal (int kliens, int szemafor, int *osztott_memoria_terulet) {
          char buffer[CTIME_BUFFER_MERET] = ""; time_t t = time (NULL); char
          *ts = ctime_r (&t, buffer); char buffer2[256] = ""; int ertek,
          nkliens; struct sembuf zar, nyit; zar.sem_num = 0; zar.sem_op = -1;
          zar.sem_flg= SEM_UNDO; nyit.sem_num = 0; nyit.sem_op = 1;
          nyit.sem_flg= SEM_UNDO; int olvasott = read (kliens, buffer2, 10);
          write (kliens, buffer2, olvasott); if (semop (szemafor, &zar, 1)
          == -1) { perror ("semop zar"); exit (EXIT_FAILURE); }
          ++*(osztott_memoria_terulet+1); if (buffer2[0] == '+')
          ++*osztott_memoria_terulet; else --*osztott_memoria_terulet; ertek =
          *osztott_memoria_terulet; nkliens = *(osztott_memoria_terulet+1); if
          (semop (szemafor, &nyit, 1) == -1) { perror ("semop nyit"); exit
          (EXIT_FAILURE); } olvasott = snprintf(buffer2, 50, "Ertek=%d
          Kliensek=%d\n", ertek, nkliens); write (kliens, buffer2, olvasott);
          return write (kliens, ts, strlen (ts)); } void zombi_elharito (int
          sig) { while (wait (NULL) > 0); } int main (void) { int
          kapu_figyelo, kapcsolat, kliensm, sockoptval = 1, s, gyermekem_pid,
          szemafor; fd_set kapu_figyelok; int osztott_memoria; int
          *osztott_memoria_terulet; struct timeval timeout; struct sockaddr_in
          szerver, kliens; struct sigaction sa; sa.sa_handler =
          zombi_elharito; sigemptyset (&sa.sa_mask); sa.sa_flags =
          SA_RESTART; memset ((void *) &szerver, 0, sizeof (szerver));
          szerver.sin_family = AF_INET; inet_aton ("127.0.0.1",
          &(szerver.sin_addr)); szerver.sin_port = htons (SZERVER_PORT);
          if ((kapu_figyelo = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP)) ==
          -1) { perror ("socket"); exit (EXIT_FAILURE); } setsockopt
          (kapu_figyelo, SOL_SOCKET, SO_REUSEADDR, (void *) &sockoptval,
          sizeof (sockoptval)); fcntl (kapu_figyelo, F_SETFL, fcntl
          (kapu_figyelo, F_GETFL) | O_NONBLOCK); if (bind (kapu_figyelo,
          (struct sockaddr *) &szerver, sizeof (szerver)) == -1) { perror
          ("bind"); exit (EXIT_FAILURE); } if (listen (kapu_figyelo,
          SZERVER_SOR_MERET) == -1) { perror ("listen"); exit (EXIT_FAILURE);
          } printf ("%s:%d\n", inet_ntoa (szerver.sin_addr), ntohs
          (szerver.sin_port)); if ((szemafor = semget (ftok (".", 42), 1,
          IPC_CREAT | S_IRUSR | S_IWUSR)) == -1) { perror ("semget"); exit
          (EXIT_FAILURE); } printf ("szemafor: %d\n", szemafor); fflush
          (stdout); if (semctl (szemafor, 0, SETVAL, 1)) { perror ("semctl");
          exit (EXIT_FAILURE); } if ((osztott_memoria = shmget (ftok (".",
          44), 2*sizeof(int), IPC_CREAT | S_IRUSR | S_IWUSR)) == -1) { perror
          ("shmget"); exit (EXIT_FAILURE); } if ((osztott_memoria_terulet =
          (int *)shmat (osztott_memoria, NULL, 0)) < 0) { perror ("shmat");
          exit (EXIT_FAILURE); } *osztott_memoria_terulet = 42;
          *(osztott_memoria_terulet+1) = 0; sigaction (SIGCHLD, &sa,
          NULL); FD_ZERO (&kapu_figyelok); for (;;) { FD_SET
          (kapu_figyelo, &kapu_figyelok); timeout.tv_sec = 3;
          timeout.tv_usec = 0; if ((s = select (kapu_figyelo + 1,
          &kapu_figyelok, NULL, NULL, &timeout)) == -1) { // perror
          ("select"); // Interrupted system call/ EINTR - SIGCHLD // exit
          (EXIT_FAILURE); } else if (!s) { printf ("vartam...\n"); fflush
          (stdout); } else { if (FD_ISSET (kapu_figyelo, &kapu_figyelok))
          { memset ((void *) &kliens, 0, (kliensm = sizeof (kliens))); if
          ((kapcsolat = accept (kapu_figyelo, (struct sockaddr *) &kliens,
          (socklen_t *) & kliensm)) == -1) { perror ("accept"); exit
          (EXIT_FAILURE); } printf (" <-> %s:%d\n", inet_ntoa
          (kliens.sin_addr), ntohs (kliens.sin_port)); if ((gyermekem_pid =
          fork ()) == 0) { close (kapu_figyelo); if (kiszolgal (kapcsolat,
          szemafor, osztott_memoria_terulet) == -1) { perror ("kiszolgal"); }
          close (kapcsolat); exit (EXIT_SUCCESS); } else if (gyermekem_pid
          > 0) { // wait(&statusz); e miatt kezeljuk a SIGCHLD jelet,
          // l. a Zombik fejezetet (PP)! //
          http://www.inf.unideb.hu/~nbatfai/#pp close (kapcsolat); } else {
          close (kapcsolat); perror ("fork"); exit (EXIT_FAILURE); } } } }
          }

Discussing the source code of the server side

The beginning of the main function can be familiar from the client. The communication endpoint is created on the server side the same way as in the client side.

struct sockaddr_in szerver, kliens;
            memset ((void *) &szerver, 0, sizeof (szerver));
            szerver.sin_family = AF_INET; inet_aton ("127.0.0.1",
            &(szerver.sin_addr)); szerver.sin_port = htons
            (SZERVER_PORT);

The next part of the beginning of the main function is the setting of the POSIX signal handling. Some lines below, we can see that the SIGCHLD signal will be processed by the signal handler function called zombi_elharito.

struct sigaction sa; sa.sa_handler =
            zombi_elharito; sigemptyset (&sa.sa_mask); sa.sa_flags =
            SA_RESTART;

The variable osztott_memoria is used for identifying a shared memory segment that is pointed by the pointer osztott_memoria_terulet. It is interested to note that this pointer will point outside the process's address space and all the processes that attach the shared memory segment will access it.

int osztott_memoria; int
            *osztott_memoria_terulet;

In the remaining part of the beginning of the main function,

int kapu_figyelo, kapcsolat, kliensm,
            sockoptval = 1, s, gyermekem_pid, szemafor; fd_set kapu_figyelok;
            struct timeval timeout;

the kapu_figyelo is the server socket and the kapcsolat is a client socket. The kliensm is a technical variable that contains the size of client's sockaddr_in structure. The sockoptval is also a technical variable that helps to test the server. The calling the function

            setsockopt (kapu_figyelo, SOL_SOCKET, SO_REUSEADDR, NULL, 0);
            

allows us to restart the server immediately without waiting for the freeing of the server socket by the operating system.

 vartam... ^C [norbert@matrica halozati]$ ./szerver
            bind: Address already in use 

The gyermekem_pid variable contains the returned value of the fork system call that returns 0 in the child and returns the child's PID in the parent. The szemafor variable, like in the case of the shered memory, is used for identifying the IPC resource. The kapu_figyelok and timeout variables will be discussed later together with the fork system call.

The socket is created the same way as in the client.

if ((kapu_figyelo = socket (PF_INET,
            SOCK_STREAM, IPPROTO_TCP)) == -1) { perror ("socket"); exit
            (EXIT_FAILURE); }

We connect the socket to the previously specified (127.0.0.1:2012) address using the bind system call.

if (bind (kapu_figyelo, (struct
            sockaddr *) &szerver, sizeof (szerver)) == -1) { perror
            ("bind"); exit (EXIT_FAILURE); }

Then we start to listen for incoming client connect requests

if (listen (kapu_figyelo,
            SZERVER_SOR_MERET) == -1) { perror ("listen"); exit
            (EXIT_FAILURE); }

and in general we can start processing the client's request

if ((kapcsolat = accept
            (kapu_figyelo, (struct sockaddr *) &kliens, (socklen_t *)
            & kliensm)) == -1)

but now we are going to use a more sophisticated approach to handle clients. Because calling the accept will be embedded in the calling select.

We set server socket to non-blocking. First, we read the socket descriptor flags then set it.

fcntl (kapu_figyelo, F_SETFL, fcntl
            (kapu_figyelo, F_GETFL) | O_NONBLOCK);

The semaphore array is created by the semget system call, where the first argument is a key to identify the allocated shared memory segment. It is generated by the ftok library function using the name of actual directory and a magic number 42 (see the man page man 3 ftok).

if ((szemafor = semget (ftok (".",
            42), 1, IPC_CREAT | S_IRUSR | S_IWUSR)) == -1) { perror
            ("semget"); exit (EXIT_FAILURE); }

I don't understand why I don't understand...

Suppose we are testing the server. If the server runs the first time but it doesn't run other times again then we will change the actual directory or the magic number.

The second argument of semget is the number of the semaphores in the array to be created and that number is equal to 1 now. It is enough because the mutual exclusion can be achieved using only one semaphore. The third argument sets permissions on the semaphore to be created, these are set in the same way as the ones used in file handling. The properties of the created semaphore can be seen using the command ipcs:

nbatfai@morse:~$ ipcs ------ Shared Memory Segments
            -------- key shmid owner perms bytes nattch status 0x2c0003f5
            32769 nbatfai 600 8 16 ------ Semaphore Arrays -------- key semid
            owner perms nsems 0x2a0003f5 294921 nbatfai 600 1 ------ Message
            Queues -------- key msqid owner perms used-bytes messages

This screen snippet shows that there are 16 serving processes that attach the shared memory segment with size of 8 (2*sizeof(int)) bytes.

We set the first and now the only element of the semaphore array identified by szemafor to 1. This means that the semaphore is open.

if (semctl (szemafor, 0, SETVAL, 1))
            { perror ("semctl"); exit (EXIT_FAILURE); }

Similarly, we create the shared memory. The only one difference is in the second argument of the system call, because in this case it denotes the size of memory segment to be allocated.

if ((osztott_memoria = shmget (ftok
            (".", 44), 2*sizeof(int), IPC_CREAT | S_IRUSR | S_IWUSR)) == -1) {
            perror ("shmget"); exit (EXIT_FAILURE); }

We attach the shared memory segment

if ((osztott_memoria_terulet = (int
            *)shmat (osztott_memoria, NULL, 0)) < 0) { perror ("shmat");
            exit (EXIT_FAILURE); } *osztott_memoria_terulet = 42;
            *(osztott_memoria_terulet+1) = 0;

it is necessary because the forked child processes will inherit the attached shared memory segment as we can see it in the manual page man 2 shmat:

After a fork(2) the child inherits the attached shared
            memory segments. After an execve(2) all attached shared memory
            segments are detached from the process. Upon _exit(2) all attached
            shared memory segments are detached from the process.

Finally, the next line assigns the previously created sigaction sa structure to the handling of the SIGCHLD signal.

sigaction (SIGCHLD, &sa,
            NULL);

A server of zombies

What will happen if we comment out the above line?

[norbert@matrica halozati]$ ps -p `echo $(pgrep -u
              norbert szerver)`|tee zombik| wc 30 178 1388 [norbert@matrica
              halozati]$ more zombik PID TTY STAT TIME COMMAND 12885 pts/2 S+
              0:00 ./szerver 12892 pts/2 Z+ 0:00 [szerver] <defunct>
              12893 pts/2 Z+ 0:00 [szerver] <defunct> 12894 pts/2 Z+
              0:00 [szerver] <defunct> 12895 pts/2 Z+ 0:00 [szerver]
              <defunct> 12897 pts/2 Z+ 0:00 [szerver] <defunct>
              12899 pts/2 Z+ 0:00 [szerver] <defunct>

Or if we comment out the line sa.sa_flags = SA_RESTART; statement?

Let's see now the multiplexing in the code. During multiplexing we usually use sets of file descriptors. Now first, we clear the set kapu_figyelok then add the server socket to it using the FD_SET macro.

FD_ZERO (&kapu_figyelok); for
            (;;) { FD_SET (kapu_figyelo, &kapu_figyelok);

The members of the structure called timeval may already be known from the manual page man 2 select:

The timeout The time structures involved are defined in
            <sys/time.h> and look like struct timeval { long tv_sec; /*
            seconds */ long tv_usec; /* microseconds */ };

According to these settings, the timeout is equal to 3 seconds and 0 microseconds, The select system call will return periodically after this timeout has expired.

timeout.tv_sec = 3; timeout.tv_usec =
            0; if ((s = select (kapu_figyelo + 1, &kapu_figyelok, NULL,
            NULL, &timeout)) == -1)

We can also see the arguments of the select in the manual page man 2 select:

int select(int nfds, fd_set *readfds, fd_set *writefds,
            fd_set *exceptfds, struct timeval *timeout);

from where only the first file descriptor set will be used effectively because we want to multiplex the incoming client connections only and all the clients' requests will be handled by separate processes. In accordance with this, we assign the kapu_figyelok actual parameter for the readfds formal parameter. As described in the manual page for select the value of nfds must be set to maximum file descriptor plus one.

The select may return errors for several reasons. Errors are indicated by returning -1, to get the exact error we have to check the value of errno.

If the select returns 0 it means that nothing important had happened before the timeout expired.

if ((s = select (kapu_figyelo + 1,
            &kapu_figyelok, NULL, NULL, &timeout)) == -1) { // perror
            ("select"); // Interrupted system call/ EINTR - SIGCHLD // exit
            (EXIT_FAILURE); } else if (!s) { printf ("vartam...\n"); fflush
            (stdout); }

A non-negative and non-zero value are more interesting values, because this value is the number of file descriptors that are ready to be read from or written to. In our case, to be more precise, to read from the server socket. But as a defensive programming technique we check that whether the server socket is listed in the readfds set using the FD_ISSET macro:

else { if (FD_ISSET (kapu_figyelo,
            &kapu_figyelok)) {

Here, we are already sure that accept will not be blocked.

if ((kapcsolat = accept
            (kapu_figyelo, (struct sockaddr *) &kliens, (socklen_t *)
            & kliensm)) == -1) { perror ("accept"); exit (EXIT_FAILURE);
            }

then after printing some debug messages we fork a child process

printf (" <-> %s:%d\n",
            inet_ntoa (kliens.sin_addr), ntohs (kliens.sin_port)); if
            ((gyermekem_pid = fork ()) == 0) {

The child process

if ((gyermekem_pid = fork ()) == 0) {
            close (kapu_figyelo); if (kiszolgal (kapcsolat, szemafor,
            osztott_memoria_terulet) == -1) { perror ("kiszolgal"); } close
            (kapcsolat); exit (EXIT_SUCCESS); }

closes the server socket and starts to serve the client through the client socket. Finally, after the client's request has been served by the kiszolgal handler function the child process closes the client socket as well and exits.

The parent process

else if (gyermekem_pid > 0) { //
            wait(&statusz); e miatt kezeljuk a SIGCHLD jelet, // l. a
            Zombik fejezetet (PP)! // http://www.inf.unideb.hu/~nbatfai/#pp
            close (kapcsolat); }

closes the client socket and goes back immediately to wait for the next client connection requests.

Protecting the memory of the processes

The client handler function kiszolgal gets three arguments. These are the file descriptor of client socket, the identifier of the semaphore array and a pointer to the attached memory segment.

int kiszolgal (int kliens, int
            szemafor, int *osztott_memoria_terulet) {

The other variables are technical, for example the buffer is an internal puffer used by the reentrant function like ctime_r.

char buffer[CTIME_BUFFER_MERET] = "";
            time_t t = time (NULL); char *ts = ctime_r (&t, buffer); char
            buffer2[256] = ""; int ertek, nkliens;

It is much more interesting to create the two sembuf data structures that will control the semaphore operations.

struct sembuf zar, nyit; zar.sem_num
            = 0; zar.sem_op = -1; zar.sem_flg= SEM_UNDO; nyit.sem_num = 0;
            nyit.sem_op = 1; nyit.sem_flg= SEM_UNDO;

The detailed description of semaphore handling of this example can be found in the book Párhuzamos programozás GNU/Linux környezetben: SysV IPC, P-szálak, OpenMP http://www.inf.unideb.hu/~nbatfai/konyvek/PARP/parp.book.xml.pdf, [PARP].

As mentioned in the previous section about the parent process, the semaphore was set to signaled because its value was set to 1 by the parent process.

We read the first 10 bytes of what the client sent to us and we write back it to the client.

int olvasott = read (kliens, buffer2,
            10); write (kliens, buffer2, olvasott);

We count how many clients worked on the int variable stored in the shared memory segment. The counter is the second variable on the shared memory area and it is pointed by osztott_memoria_terulet+1.

++*(osztott_memoria_terulet+1);

If client sends a plus sign, the following code snippet will increment the first variable that is stored in the shared memory segment. Otherwise, the shared variable will be decreased:

if (buffer2[0] == '+')
            ++*osztott_memoria_terulet; else
            --*osztott_memoria_terulet;

The above last two code snippets are part of the critical section. We would like to achieve that only one process may enter the critical section at the same time in order that variables should not go wrong. Therefore, here we apply mutual exclusion to achieve this goal: only one process can enter the critical section at a time. If a process can acquire the semaphore, all the other processes will be blocked until the semaphore is nonsignaled. The next snippet sets the semaphore to nonsignaled

if (semop (szemafor, &zar, 1) ==
            -1) { perror ("semop zar"); exit (EXIT_FAILURE);
            }

or this one sets the semaphore to signaled after the process exits from the critical section.

if (semop (szemafor, &nyit, 1) ==
            -1) { perror ("semop nyit"); exit (EXIT_FAILURE);
            }

In the critical section we create copies of the two shared variables.

ertek = *osztott_memoria_terulet;
            nkliens = *(osztott_memoria_terulet+1);

These copied values and the system time will be sent back to the client.

olvasott = snprintf(buffer2, 50,
            "Ertek=%d Kliensek=%d\n", ertek, nkliens); write (kliens, buffer2,
            olvasott); return write (kliens, ts, strlen (ts));
            }

Testing of the example

Testing on localhost

Compiling and running the server
[norbert@matrica halozati]$ gcc szerver.c -o szerver
              [norbert@matrica halozati]$ ./szerver 127.0.0.1:2012 szemafor:
              98305 vartam... vartam... <-> 127.0.0.1:55055 <->
              127.0.0.1:55056 . . . <-> 127.0.0.1:55079 <->
              127.0.0.1:55080 <-> 127.0.0.1:55081 <->
              127.0.0.1:55082 <-> 127.0.0.1:55083 <->
              127.0.0.1:55084 vartam... ^C
Compiling and running the client
[norbert@matrica halozati]$ gcc kliens.c -o kliens
              [norbert@matrica halozati]$ ./kliens +Ertek=43 Kliensek=1 Sat
              Aug 4 16:18:49 2012 +Ertek=44 Kliensek=2 Sat Aug 4 16:18:49 2012
              +Ertek=45 Kliensek=3 Sat Aug 4 16:18:49 2012 . . . +Ertek=69
              Kliensek=27 Sat Aug 4 16:18:49 2012 +Ertek=70 Kliensek=28 Sat
              Aug 4 16:18:49 2012 +Ertek=71 Kliensek=29 Sat Aug 4 16:18:49
              2012 +Ertek=72 Kliensek=30 Sat Aug 4 16:18:49 2012

The second last row +Ertek=72 Kliensek=30 shows that the value of 42 does not go wrong because it is equal 72 after 30 incrementations.

There is some disturbance in the force

What will happen if we comment out the usage of the semaphore?

/* if (semop (szemafor, &zar,
              1) == -1) { perror ("semop zar"); exit (EXIT_FAILURE); } */
              ++*(osztott_memoria_terulet+1); if (buffer2[0] == '+')
              ++*osztott_memoria_terulet; else --*osztott_memoria_terulet;
              ertek = *osztott_memoria_terulet; nkliens =
              *(osztott_memoria_terulet+1); /* if (semop (szemafor, &nyit,
              1) == -1) { perror ("semop nyit"); exit (EXIT_FAILURE); }
              */

Using the following command we run clients in a little bit more aggressive way. It is important that the magic value 42 should be unchanged after running the clients.

[norbert@matrica halozati]$ ./kliens +& ./kliens
              -& ./kliens +& ./kliens -& ./kliens +& ./kliens
              -& ./kliens +& ./kliens -& ./kliens +& ./kliens
              -& ./kliens +& ./kliens -& ./kliens +& ./kliens
              -& ./kliens +& ./kliens -& ./kliens +& ./kliens
              -&

We query the value using the telnet program as a client:

[norbert@matrica halozati]$ telnet localhost 2012 Trying
              ::1... telnet: connect to address ::1: Connection refused Trying
              127.0.0.1... Connected to localhost. Escape character is '^]'. +
              + Ertek=47 Kliensek=514 Sat Aug 4 16:38:20 2012 Connection
              closed by foreign host.

It can be seen very well from the server's response that the value of 42 goes wrong. If we put back the protection, we will get the right result:

[norbert@matrica halozati]$ telnet localhost 2012 Trying
              ::1... telnet: connect to address ::1: Connection refused Trying
              127.0.0.1... Connected to localhost. Escape character is '^]'. +
              + Ertek=43 Kliensek=541 Sat Aug 4 16:47:31 2012 Connection
              closed by foreign host.

the value of the shared variable is equal to 43 because the query as a client also increment the value.

Testing on two machines

The following changes have to be applied in the source codes of the client and the server. In the code of the server,

//inet_aton ("127.0.0.1",
            &(szerver.sin_addr)); szerver.sin_addr.s_addr =
            htonl(INADDR_ANY);

and we use the wildcard address instead of localhost and accordingly with this we apply the server's IP address in the code of the client:

//inet_aton ("127.0.0.1",
            &(szerver.sin_addr)); inet_aton ("193.6.135.21",
            &(szerver.sin_addr));
Portability

Now let's see what may happen if we will try the example in another machine:

nbatfai@morse:~$ gcc szerver2.c -o szerver szerver2.c:
              In function ‘main’: szerver2.c:158: error: ‘S_IRUSR’ undeclared
              (first use in this function) szerver2.c:158: error: (Each
              undeclared identifier is reported only once szerver2.c:158:
              error: for each function it appears in.) szerver2.c:158: error:
              ‘S_IWUSR’ undeclared (first use in this function)

Recall what we wrote in the previous section about portability. The S_IRUSR and S_IWUSR macros can be found in the manual page man 2 stat. So including the sys/stat.h header hopefully everything will work well for the reader, too:

[norbert@matrica halozati]$ telnet morse 2012Trying
              193.6.135.21... Connected to morse. Escape character is '^]'. +
              + Ertek=43 Kliensek=541 Sat Aug 4 17:30:00 2012 Connection
              closed by foreign host.
Testing on a virtualized machine

Figure 4.1. Compiling the client and the server.

Compiling the client and the server.

Figure 4.2. Running the client and the server.

Running the client and the server.

Figure 4.3. Checking the results.

Checking the results.

We have used and recommend the following books: [UNP] and [LINUXPROG]. If the reader wants to get a deeper and overall understanding on network programming, we recommend the former one.