0% found this document useful (0 votes)
8 views

CS7

network programming lecture at bits

Uploaded by

jarison_mander
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
8 views

CS7

network programming lecture at bits

Uploaded by

jarison_mander
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
You are on page 1/ 53

A Client-Server Application Using FIFOs

• Here we present a simple client-server


application that employs FIFOs for IPC.
• The server provides the service of assigning
unique sequential numbers to each client that
requests them.
• In the course of discussing this application, we
introduce a few concepts and techniques in
server design.

1
Application overview
• All clients send their requests to the server using a single server
FIFO.
• Server defines the well-known FIFO with the name seqnum_sv in
path (/tmp/seqnum_sv).
• This name is fixed, so that all clients know how to contact the server.
• It is not possible to use a single FIFO to send responses to all clients,
since multiple clients would race to read from the FIFO, and possibly
read each other’s response messages rather than their own.
• Therefore, each client creates a unique FIFO that the server uses for
delivering the response for that client.
• The server needs to know how to find each client’s FIFO.

2
• One possible way to do this is for the client to generate
its FIFO pathname, and then pass the pathname as part
of its request message.
• Alternatively, the client and server can agree on a
convention for constructing a client FIFO pathname, and,
as part of its request, the client can pass the server the
information required to construct the pathname specific
to this client.
• This latter solution is used in this example.
• Each client’s FIFO name is built from a template
(CLIENT_FIFO_TEMPLATE) consisting of a pathname
containing the client’s process ID.
• The inclusion of the process ID provides an easy way of
generating a name unique to this client. 3
The following figure shows how this application uses
FIFOs for communication between
the client and server processes of our application.

4
• Data in pipes and FIFOs is a byte stream;
boundaries between multiple messages are not
preserved.
• This means that when multiple messages are
being delivered to a single process, such as the
server in our example, then the sender and
receiver must agree on some convention for
separating the messages.
• Various approaches are possible:

5
6
• In our example application, we use the third of the
techniques described above, with each client sending
messages of a fixed size to the server.
• This message is defined by the request structure defined
in fifo_seqnum.h header file.
• Each request to the server includes the client’s process
ID, which enables the server to construct the name of
the FIFO used by the client to receive a response.
• The request also contains a field (seqLen) specifying how
many sequence numbers should be allocated to this
client.
• The response message sent from server to client consists
of a single field, seqNum, which is the starting value of
the range of sequence numbers allocated to this client.
7
Header file for fifo_seqnum_server.c and fifo_seqnum_client.c
–––––––––––––––––––––––––––– pipes/fifo_seqnum.h
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "tlpi_hdr.h“

#define SERVER_FIFO "/tmp/seqnum_sv"


/* Well-known name for server's FIFO */

#define CLIENT_FIFO_TEMPLATE "/tmp/seqnum_cl.%ld"


/* Template for building client FIFO name */

#define CLIENT_FIFO_NAME_LEN
(sizeof(CLIENT_FIFO_TEMPLATE) + 20)
/* Space required for client FIFO pathname (+20 as a generous
8
allowance for the PID) */
struct request
{
/* Request (client --> server) */
pid_t pid; /* PID of client */
int seqLen; /* Length of desired sequence
*/
};
struct response
{
/* Response (server --> client) */
int seqNum; /* Start of sequence */
};
9
Tlpi_hdr.h header file
#ifndef TLPI_HDR_H
#define TLPI_HDR_H /* Prevent accidental double inclusion */

#include <sys/types.h> /* Type definitions used by many programs */


#include <stdio.h> /* Standard I/O functions */
#include <stdlib.h> /* Prototypes of commonly used library functions,
plus EXIT_SUCCESS and EXIT_FAILURE constants */
#include <unistd.h> /* Prototypes for many system calls */
#include <errno.h> /* Declares errno and defines error constants */
#include <string.h> /* Commonly used string-handling functions */
#endif

10
Server program
The server performs the following steps:
• Create the server’s well-known FIFO and open the FIFO for
reading. The server must be run before any clients, so that the
server FIFO exists by the time a client attempts to open it. The
server’s open() blocks until the first client opens the other end of
the server FIFO for writing.
• Ignore the SIGPIPE signal, so that if the server attempts to write to
a client FIFO that doesn’t have a reader, then, rather than being
sent a SIGPIPE signal (which kills a process by default), it receives
an EPIPE error from the write() system call.
• Enter a loop that reads and responds to each incoming client
request. To send the response, the server constructs the name of
the client FIFO and then opens that FIFO.
• If the server encounters an error in opening the client FIFO, it
abandons that client’s request. 11
include <signal.h>
#include "fifo_seqnum.h"
#include "tlpi_hdr.h"
int main(int argc, char *argv[])
{
int serverFd, clientFd;
char clientFifo[CLIENT_FIFO_NAME_LEN];
struct request req;
struct response resp;
int seqNum = 0; /* This is our "service" */
12
/* Create well-known FIFO, and open it for reading */

umask(0); /* So we get the permissions we want */

if (mkfifo(SERVER_FIFO, S_IRUSR | S_IWUSR | S_IWGRP) == -1 &&


errno != EEXIST)
printf("mkfifo error %s", SERVER_FIFO);

serverFd = open(SERVER_FIFO, O_RDONLY);

if (serverFd == -1)

printf("open error %s", SERVER_FIFO);

13
/* Let's find out about broken client pipe via failed write()
*/
if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
printf("signal error");

for (;;) { /* Read requests and send responses */


if (read(serverFd, &req, sizeof(struct request))
!= sizeof(struct request)) {
fprintf(stderr, "Error reading request; discarding\
n");
continue; /* Either partial read or error */
}
14
/* Open client FIFO (previously created by client)
*/

snprintf(clientFifo, CLIENT_FIFO_NAME_LEN,
CLIENT_FIFO_TEMPLATE, (long) req.pid);
clientFd = open(clientFifo, O_WRONLY);
if (clientFd == -1) { /* Open failed, give up on client */
printf("open client fifo error %s", clientFifo);
continue;
}

15
/* Send response and close FIFO */

resp.seqNum = seqNum;
if (write(clientFd, &resp, sizeof(struct response))
!= sizeof(struct response))
fprintf(stderr, "Error writing to FIFO %s\n", clientFifo);
if (close(clientFd) == -1)
printf("close clienfd error");

seqNum += req.seqLen; /* Update our sequence

number */
}
} 16
Client program
The client performs the following steps:
• Create a FIFO to be used for receiving a response from the server. This is
done before sending the request, in order to ensure that the FIFO exists
by the time the server attempts to open it and send a response message.

• Construct a message for the server containing the client’s process ID and a
number (taken from an optional command-line argument) specifying the
length of the sequence that the client wishes the server to assign to it (If
no command-line argument is supplied, the default sequence length is 1.)

• Open the server FIFO and send the message to the server.

• Open the client FIFO, and read and print the server’s response

17
#include "fifo_seqnum.h"
#include "tlpi_hdr.h"
#include<stdlib.h>

static char clientFifo[CLIENT_FIFO_NAME_LEN];

static void /* Invoked on exit to delete client FIFO */


removeFifo(void)
{
unlink(clientFifo);
}
18
int main(int argc, char *argv[])
{
int serverFd, clientFd;
struct request req;
struct response resp;

if (argc > 1 && strcmp(argv[1], "--help") == 0)


printf("%s [seq-len...]\n", argv[0]);

19
/* Create our FIFO (before sending request, to avoid a race) */

umask(0); /* So we get the permissions we want */


snprintf(clientFifo, CLIENT_FIFO_NAME_LEN,
CLIENT_FIFO_TEMPLATE,
(long) getpid());
if (mkfifo(clientFifo, S_IRUSR | S_IWUSR | S_IWGRP) == -1
&& errno != EEXIST)
printf("mkfifo %s", clientFifo);

if (atexit(removeFifo) != 0)
perror("atexit");

20
/* Construct request message, open server FIFO, and
send message */

req.pid = getpid();
req.seqLen = (argc > 1) ? atoi(argv[1]): 1;

serverFd = open(SERVER_FIFO, O_WRONLY);


if (serverFd == -1)
printf("open %s", SERVER_FIFO);

if (write(serverFd, &req, sizeof(struct request)) !=


sizeof(struct request))
printf("Can't write to server"); 21
/* Open our FIFO, read and display response */

clientFd = open(clientFifo, O_RDONLY);


if (clientFd == -1)
printf("open %s", clientFifo);

if (read(clientFd, &resp, sizeof(struct response))


!= sizeof(struct response))
printf("Can't read response from server");

printf("%d\n", resp.seqNum);
exit(EXIT_SUCCESS);
} 22
Output

23
Nonblocking I/O
when a process opens one end of a FIFO, it blocks
if the other end of the FIFO has not yet been
opened. Sometimes, it is desirable not to block,
and for this purpose, the O_NONBLOCK flag can
be specified when calling open():

fd = open("fifopath", O_RDONLY | O_NONBLOCK);


if (fd == -1)
errExit("open");
24
The O_NONBLOCK flag changes things only if the
other end of the FIFO is not yet open, and the
effect depends on whether we are opening the
FIFO for reading or writing:
If the FIFO is being opened for reading, and no
process currently has the write end of the FIFO
open, then the open() call succeeds immediately
( just as though the other end of the FIFO was
already open).
If the FIFO is being opened FIFO for writing, and
the other end of the FIFO is not already open for
reading, then open() fails, setting errno to ENXIO.
25
Semantics of open() for a FIFO

26
Using the O_NONBLOCK flag when opening a
FIFO serves two main purposes:

• It allows a single process to open both ends of a


FIFO. The process first opens the FIFO for
reading specifying O_NONBLOCK, and then
opens the FIFO for writing.
• It prevents deadlocks between processes
opening two FIFOs.

27
Deadlock between processes opening two
FIFOs

28
• A deadlock is a situation where two or more process are
blocked because each is waiting on the other process(es)
to complete some action.
• The two processes X and Y shown in above figure are
deadlocked.
• Each process is blocked waiting to open aFIFO for reading.
• This blocking would not happen if a process could perform
its second step (opening the other FIFO for writing).
• This particular deadlock problem could be solved by
reversing the order of steps 1 and 2 in process Y, while
leaving the order in process X unchanged, or vice versa.
• However, such an arrangement of steps may not be easy
to achieve in some applications. Instead, we can resolve
the problem by having either process, or both, specify the
O_NONBLOCK flag when opening the FIFOs for reading. 29
Nonblocking read() and write()
• We opened a FIFO using O_NONBLOCK, but the subsequent read() and write()
operate in blocking mode.
• We can use fcntl() to enable or disable the O_NONBLOCK open file status flag.
To enable the flag, we write the following (omitting error checking):

int flags;
flags = fcntl(fd, F_GETFL); /* Fetch open files status flags */
flags |= O_NONBLOCK; /* Enable O_NONBLOCK bit */
fcntl(fd, F_SETFL, flags); /* Update open files status flags */

And to disable it, we write the following:

flags = fcntl(fd, F_GETFL);


flags &= ~O_NONBLOCK; /* Disable O_NONBLOCK bit */
fcntl(fd, F_SETFL, flags);
30
Semantics of read() and write() on Pipes
and FIFOs
The only difference between blocking and non-blocking reads occurs
when no data is present and the write end is open. In this case, a
normal read() blocks, while a non-blocking read() fails with the error
EAGAIN.

31
The impact of the O_NONBLOCK flag when writing to a pipe or FIFO is made complex by
interactions with the PIPE_BUF limit. The write() behavior is summarized in Table 44-3.

32
INTRODUCTIONTOSYSTEMVIP
C
Three different mechanisms for inter-process communication in UNIX system V
IPC.
Message queues
 Semaphores
 Shared memory
• Although these three IPC mechanisms are quite diverse in function, there are
good reasons for discussing them together.
• One reason is that they were developed together, first appearing in the late
1970s in Columbus UNIX. This was a Bell-internal UNIX implementation used
for database and transaction-processing systems for telephone company
record keeping and administration. Around 1983, these IPC mechanisms made
their way into mainstream UNIX by appearing in System V
• A more significant reason for discussing the System V IPC mechanisms together
is that their programming interfaces share a number of common
characteristics, so that many of the same concepts apply to all of these
mechanisms.
33
Here, an overview of the System V IPC mechanisms and details those features that are
common to all three mechanisms are provided. The three mechanisms are then
discussed individually in the following chapters.
API Overview:
Table 45-1 summarizes the header files and system calls used for working with System V
IPC objects. Some implementations require the inclusion of <sys/types.h> before
including the header files shown in Table 45-1.

34
Creating and opening a System V IPC
object
• Each System V IPC mechanism has an associated get system call.
-> msgget() for message queues
->semget() for semaphores
->shmget() for shared memory.

• These system calls are analogous to the open() system call used for files.
• Given an integer key (analogous to a filename), the get call either:

 creates a new IPC object with the given key and returns a unique
identifier for that object; or

 returns the identifier of an existing IPC object with the given key.

35
IPC identifier
• All that the get call is doing is converting one number (the key) into another
number (the identifier called as IPC identifier).
• An IPC identifier is analogous to a file descriptor in that it is used in all
subsequent system calls to refer to the IPC object.

• There is an important semantic difference.


 file descriptor is a process attribute, an IPC identifier is a property of the
kernel and is visible system-wide.

 All processes accessing the same object use the same identifier. This means
that if we know an IPC object already exists, we can skip the get call, provided
we have some other means of knowing the identifier of the object.
• For example, the process that created the object might write the identifier to a
file that can then be read by other processes.

36
The following example shows how to create
a System V message queue:
id = msgget(key, IPC_CREAT | S_IRUSR | S_IWUSR);
if (id == -1)
errExit("msgget");
• key is the first argument, and the identifier is returned as
the function result.
• We specify the permissions to be placed on the new
object as part of the final (flags) argument to the get call,
using the same bit-mask constants as are used for files.
• In the above example, permission is granted to just the
owner of the object to read and write messages on the
queue.
37
• Several UNIX implementations define the following bit-mask
constants for IPC permissions.
• MSG_R, MSG_W, SEM_R, SEM_A, SHM_R, and SHM_W. These
correspond to owner (user) read and write permissions for each
IPC mechanism.
• To get the corresponding group and other permission bit masks,
these constants can be right-shifted 3 and 6 bits.
• If no IPC object corresponding to the given key currently exists, and
IPC_CREAT (analogous to the open() O_CREAT flag) was specified as
part of the flags argument, then the get call creates a new IPC
object.
• If no corresponding IPC object currently exists, and IPC_CREAT was
not specified, then the get call fails with the error ENOENT.
• A process can guarantee that it is the one creating an IPC object by
specifying the IPC_EXCL flag (analogous to the open() O_EXCL flag).
If IPC_EXCL is specified and the IPC object corresponding to the
38
given key already exists, then the get call fails with the error EEXIST.
IPC object deletion and object persistence
• The ctl system call (msgctl(), semctl(), shmctl()) for each
System V IPC mechanism performs a range of control
operations for the object.
• Many of these operations are specific to the IPC
mechanism, but a few are generic to all IPC mechanisms.
• An example of a generic control operation is IPC_RMID,
which is used to delete an object.
• For example, we can use the following call to delete a
shared memory object:
if (shmctl(id, IPC_RMID, NULL) == -1)
errExit("shmctl");
39
• For message queues and semaphores, deletion
of the IPC object is immediate, and any
information contained within the object is
destroyed, regardless of whether any other
process is still using the object.
• Deletion of shared memory objects occurs
differently. Following the shmctl(id, IPC_RMID,
NULL) call, the shared memory segment is
removed only after all processes using the
segment detach it (using shmdt()). (This is much
closer to the situation with file deletion.)

40
• System V IPC objects have kernel persistence. Once created, an IPC object
continues to exist until it is explicitly deleted or the system is shut down.
Advantage:
It is possible for a process to create an object, modify its state, and then
exit, leaving the object to be accessed by some process that is started at a
later time.
Disadvantage:
It can also be disadvantageous for the following reasons:
 There are system-imposed limits on the number of IPC objects of each
type. If we fail to remove unused objects, we may eventually encounter
application errors as a result of reaching these limits.
When deleting a message queue or semaphore object, a multi-process
application may not be able to easily determine which will be the last
process requiring access to the object, and thus when the object can be
safely deleted. The problem is that these objects are connectionless—the
kernel doesn’t keep a record of which processes have the object open.
(This disadvantage doesn’t apply for shared memory segments, because of
their different deletion semantics, described above.)
41
IPC Keys
• System V IPC keys are integer values represented using
the data type key_t.
• The IPC get calls translate a key into the corresponding
integer IPC identifier.
• These calls guarantee that if we create a new IPC
object, then that object will have a unique identifier,
and that if we specify the key of an existing object, then
we’ll always obtain the (same) identifier for that object.
• So, how do we provide a unique key—one that
guarantees that we won’t accidentally obtain the
identifier of an existing IPC object used by some other
application?
42
There are three possibilities:
• Randomly choose some integer key value, which is
typically placed in a header file included by all programs
using the IPC object. The difficulty with this approach is
that we may accidentally choose a value used by
another application.
• Specify the IPC_PRIVATE constant as the key value to the
get call when creating the IPC object, which always
results in the creation of a new IPC object that is
guaranteed to have a unique key.
• Employ the ftok() function to generate a (likely unique)
key.
Note: Using either IPC_PRIVATE or ftok() is the usual
technique.
43
Generating a unique key with IPC_PRIVATE
• When creating a new IPC object, the key may be specified
as IPC_PRIVATE, as follows:
id = msgget(IPC_PRIVATE, S_IRUSR | S_IWUSR);
• In this case, it is not necessary to specify the IPC_CREAT or
IPC_EXCL flags.
• This technique is useful in multi-process applications. We
can also use this technique in client-server applications (i.e.,
those involving unrelated processes), but the clients must
have a means of obtaining the identifiers of the IPC objects
created by the server (and vice versa).
• For example, after creating an IPC object, the server could
then write its identifier to a file that can be read by the
clients. 44
Generating a unique key with ftok()
• The ftok() (file to key) function returns a key
value suitable for use in a subsequent call to
one of the System V IPC get system calls.
#include <sys/ipc.h>
key_t ftok(char *pathname, int proj);
Returns integer key on success, or –1 on error
• This key value is generated from the supplied
pathname and proj value using an system
implementation-defined algorithm.
45
Implementation defined algorithm uses the following.
Only the least significant 8 bits of proj are employed by
the algorithm.
The application must ensure that the pathname refers
to an existing file to which stat() can be applied
(otherwise, ftok() returns –1).
If different pathnames (links) referring to the same file
(i.e., i-node) are supplied to ftok() with the same proj
value, the same key value must be returned.
• Note:
• 1. To put things another way, ftok() uses the i-node number rather
than the name of the file to generate the key value.
• 2. The purpose of the proj value is simply to allow us to generate
multiple keys from the same file, which is useful when an
application needs to create multiple IPC objects of the same type.
46
• A typical usage of ftok() is the following:
key_t key;
int id;
key = ftok("/mydir/myfile", 4);
if (key == -1)
errExit("ftok");
id = msgget(key, IPC_CREAT | S_IRUSR | S_IWUSR);
if (id == -1)
errExit("msgget");

47
Associated Data Structure and Object
Permissions
• The kernel maintains an associated data structure for each instance of
a System V IPC object.
• The form of this data structure varies according to the IPC mechanism
(message queue, semaphore, or shared memory) and is defined in the
corresponding header file for the IPC mechanism (see Table 45-1).
• We discuss mechanism specific details of each of these data structures
in the following chapters.
• The associated data structure for an IPC object is initialized when the
object is created via the appropriate get system call.
• Once the object has been created, a program can obtain a copy of this
data structure using the appropriate ctl system call, by specifying an
operation type of IPC_STAT.
• Conversely, some parts of the data structure can be modified using
the IPC_SET operation. 48
Common data structure for all 3 IPC
mechanism
• All three IPC mechanisms includes a substructure, ipc_perm, that
holds information used to determine permissions granted on the
object:
struct ipc_perm {
key_t __key; /* Key, as supplied to 'get' call */
uid_t uid; /* Owner's user ID */
gid_t gid; /* Owner's group ID */
uid_t cuid; /* Creator's user ID */
gid_t cgid; /* Creator's group ID */
unsigned short mode; /* Permissions */
unsigned short __seq; /* Sequence number */
};
49
• Initially, the corresponding user and creator ID fields have
the same values.
• The creator IDs cannot be changed, but the owner IDs can
be changed via the IPC_SET operation.
• The following code demonstrates how to change the uid
field for a shared memory segment (the associated data
structure is of type shmid_ds):
struct shmid_ds shmds;
if (shmctl(id, IPC_STAT, &shmds) == -1) /* Fetch from kernel */
errExit("shmctl");
shmds.shm_perm.uid = newuid; /* Change owner UID */
if (shmctl(id, IPC_SET, &shmds) == -1) /* Update kernel copy
*/
errExit("shmctl");
50
Cleanup of IPC objects within a server
–––––––––––––––svipc/svmsg_demo_server.c
include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>
#include "tlpi_hdr.h"
#define KEY_FILE "f1"
/* Should be an existing file or one
that this program creates */
int main(int argc, char *argv[])
{
int msqid;
key_t key;
const int MQ_PERMS = S_IRUSR | S_IWUSR | S_IWGRP; /* rw--w---- */
51
/* Generate the key for the message queue */
key = ftok(KEY_FILE, 1);
if (key == -1)
printf("ftok");
/* While msgget() fails, try creating the queue exclusively
*/
while ((msqid = msgget(key, IPC_CREAT | IPC_EXCL |
MQ_PERMS)) == -1) {
if (errno == EEXIST) { /* MQ with the same key already
exists - remove it and try again */
msqid = msgget(key, 0);
if (msqid == -1)
printf("msgget() failed to retrieve old queue ID");
52
if (msgctl(msqid, IPC_RMID, NULL) == -1)
printf("msgget() failed to delete old queue");
printf("Removed old message queue (id=%d)\n", msqid);
} else { /* Some other error --> give up */
printf("msgget() failed");
}
}
printf("\nNewly Created Message queue ID=%ld",msqid);
/* Upon loop exit, we've successfully created the
message queue,
and we can then carry on to do other work... */
exit(EXIT_SUCCESS);
} 53

You might also like