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 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.
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);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 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); } } } }
}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); }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 messagesThis 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);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.
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));
}[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[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 2012The 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.
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.
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));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.