Unix™ Systems Programming [Electronic resources] : Communication, Concurrency, and Threads نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Unix™ Systems Programming [Electronic resources] : Communication, Concurrency, and Threads - نسخه متنی

Prentice Hall

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید










6.4 Pipes and the Client-Server Model


The client-server model is a standard pattern for process interaction. One process, designated the client, requests a service from another process, called the server. The chapters in Part 4 of the book develop and analyze applications that are based on the client-server model with network communication. This section introduces client-server applications that use named pipes as the communication vehicle. We look at two types of client-server communicationsimple-request and request-reply. In simple-request, the client sends information to the server in a one-way transmission; in request-reply the client sends a request and the server sends a reply.

Programs 6.7 and 6.8 illustrate how the simple-request protocol can be useful in logging. The client writes logging information to a named pipe rather than to standard error. A server reads from the named pipe and writes to a file. At first glance, the use of the named pipe appears to have added an extra step with no benefit. However, pipes and FIFOs have a very important propertywrites of no more than PIPE_BUF bytes are guaranteed to be atomic. That is, the information is written as a unit with no intervening bytes from other writes. In contrast, an fprintf is not atomic, so pieces of the messages from multiple clients might be interspersed.

The server of Program 6.7 creates the pipe if it does not already exist. The server opens the pipe for both reading and writing, even though it will not write to the pipe. When an attempt is made to open a pipe for reading, open blocks until another process opens the pipe for writing. Because the server opens the pipe for reading and writing, open does not block. The server uses copyfile to read from the pipe and to write to standard output. To write to a file, just redirect standard output when the server is started. Since the server has the pipe open for writing as well as reading, copyfile will never detect an end-of-file. This technique allows the server to keep running even when no clients are currently writing to the pipe. Barring errors, the server runs forever.

Program 6.7 pipeserver.c

The program reads what is written to a named pipe and writes it to standard output.


#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include "restart.h"
#define FIFOARG 1
#define FIFO_PERMS (S_IRWXU | S_IWGRP| S_IWOTH)
int main (int argc, char *argv[]) {
int requestfd;
if (argc != 2) { /* name of server fifo is passed on the command line */
fprintf(stderr, "Usage: %s fifoname > logfile\n", argv[0]);
return 1;
}
/* create a named pipe to handle incoming requests */
if ((mkfifo(argv[FIFOARG], FIFO_PERMS) == -1) && (errno != EEXIST)) {
perror("Server failed to create a FIFO");
return 1;
}
/* open a read/write communication endpoint to the pipe */
if ((requestfd = open(argv[FIFOARG], O_RDWR)) == -1) {
perror("Server failed to open its FIFO");
return 1;
}
copyfile(requestfd, STDOUT_FILENO);
return 1;
}

The client in Program 6.8 writes a single line to the pipe. The line contains the process ID of the client and the current time. Multiple copies of Program 6.8 can run concurrently. Because of the atomic nature of writes to the pipe, pieces of the messages from different clients are not interleaved.

Program 6.8 pipeclient.c

The client writes an informative message to a named pipe.


#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <time.h>
#include <unistd.h>
#include <sys/stat.h>
#include "restart.h"
#define FIFOARG 1
int main (int argc, char *argv[]) {
time_t curtime;
int len;
char requestbuf[PIPE_BUF];
int requestfd;
if (argc != 2) { /* name of server fifo is passed on the command line */
fprintf(stderr, "Usage: %s fifoname", argv[0]);
return 1;
}
if ((requestfd = open(argv[FIFOARG], O_WRONLY)) == -1) {
perror("Client failed to open log fifo for writing");
return 1;
}
curtime = time(NULL);
snprintf(requestbuf, PIPE_BUF, "%d: %s", (int)getpid(), ctime(&curtime));
len = strlen(requestbuf);
if (r_write(requestfd, requestbuf, len) != len) {
perror("Client failed to write");
return 1;
}
r_close(requestfd);
return 0;
}

Exercise 6.11

How would you start Program 6.7 so that it uses the pipe mypipe and the log file it creates is called mylog? When will the program terminate?

Answer:


pipeserver mypipe > mylog

The program does not terminate unless it is killed. You can kill it by typing Ctrl-C at the keyboard. No client error can cause the server to terminate.

Exercise 6.12

Start the pipeserver of Section 8.4 explains how to respond to these types of signals.

Section 6.10 explores an alternative strategy in which the server creates a separate named pipe for each distinct client. Now each pipe only has a single reader, eliminating the two problems described above.

Program 6.9 seqserverbad.c

A sequence server reads a character from the request pipe and transmits a sequence number to the sequence pipe. (See text for a discussion.)


#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include "restart.h"
#define ERROR_CHAR'e'
#define OK_CHAR 'g'
#define REQUEST_FIFO 1
#define REQ_PERMS (S_IRUSR | S_IWUSR | S_IWGRP | S_IWOTH)
#define SEQUENCE_FIFO 2
#define SEQ_PERMS (S_IRUSR | S_IWUSR | S_IRGRP| S_IROTH)
int main (int argc, char *argv[]) {
char buf[1];
int reqfd, seqfd; long seqnum = 1;
if (argc != 3) { /* names of fifos passed on the command line */
fprintf(stderr, "Usage: %s requestfifo sequencefifo\n", argv[0]);
return 1;
}
/* create a named pipe to handle incoming requests */
if ((mkfifo(argv[REQUEST_FIFO], REQ_PERMS) == -1) && (errno != EEXIST)) {
perror("Server failed to create request FIFO");
return 1;
}
if ((mkfifo(argv[SEQUENCE_FIFO], SEQ_PERMS) == -1) && (errno != EEXIST)){
perror("Server failed to create sequence FIFO");
if (unlink(argv[REQUEST_FIFO]) == -1)
perror("Server failed to unlink request FIFO");
return 1;
}
if (((reqfd = open(argv[REQUEST_FIFO], O_RDWR)) == -1) ||
((seqfd = open(argv[SEQUENCE_FIFO], O_RDWR)) == -1)) {
perror("Server failed to open one of the FIFOs");
return 1;
}
for ( ; ; ) {
if (r_read(reqfd, buf, 1) == 1) {
if ((buf[0] == OK_CHAR) &&
(r_write(seqfd, &seqnum, sizeof(seqnum)) == sizeof(seqnum)))
seqnum++;
else if (buf[0] == ERROR_CHAR)
break;
}
}
if (unlink(argv[REQUEST_FIFO]) == -1)
perror("Server failed to unlink request FIFO");
if (unlink(argv[SEQUENCE_FIFO]) == -1)
perror("Server failed to unlink sequence FIFO");
return 0;
}

Program 6.10 seqclientbad.c

The client writes a request to a request pipe and reads the sequence number from the sequence pipe. This client can cause the server to exit.


#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>
#include <sys/stat.h>
#include "restart.h"
#define ERROR_CHAR 'e'
#define OK_CHAR 'g'
#define REPEAT_MAX 100
#define REQUEST_FIFO 1
#define SEQUENCE_FIFO 2
#define SLEEP_MAX 5
int main (int argc, char *argv[]) {
int i;
char reqbuf[1];
int reqfd, seqfd;
long seqnum;
if (argc != 3) { /* names of pipes are command-line arguments */
fprintf(stderr, "Usage: %s requestfifo sequencefifo\n", argv[0]);
return 1;
}
if (((reqfd = open(argv[REQUEST_FIFO], O_WRONLY)) == -1) ||
((seqfd = open(argv[SEQUENCE_FIFO], O_RDONLY)) == -1)) {
perror("Client failed to open a FIFO");
return 1;
}
for (i = 0; i < REPEAT_MAX; i++) {
reqbuf[0] = OK_CHAR;
sleep((int)(SLEEP_MAX*drand48()));
if (r_write(reqfd, reqbuf, 1) == -1) {
perror("Client failed to write request");
break;
}
if (r_read(seqfd, &seqnum, sizeof(seqnum)) != sizeof(seqnum) ) {
fprintf(stderr, "Client failed to read full sequence number\n");
reqbuf[0] = ERROR_CHAR;
r_write(reqfd, reqbuf, 1);
break;
}
fprintf(stderr, "[%ld]:received sequence number %ld\n",
(long)getpid(), seqnum);
}
return 0;
}

The situation with nonatomic reads from pipes can actually be worse than described here. We have assumed that a read becomes nonatomic as follows.


  1. The server gets two requests and writes two sequence numbers (4-byte integers) to the pipe.

  2. One client calls read for the sequence pipe requesting four bytes, but read returns only two bytes.

  3. The second client calls read for the sequence pipe to read the next four bytes. These four bytes consist of the last two bytes from the first sequence number and the first two bytes of the second sequence number.


Under these circumstances the first client detects an error, and the server shuts down. The second client may or may not know an error occurred.

However, another scenario is technically possible, although it is very unlikely. Suppose the server writes two 4-byte integer sequence numbers and the bytes in the pipe are abcdefgh. The POSIX standard does not exclude the possibility that the first client will read the bytes abgh and the second one will read the bytes cdef. In this case, the sequence numbers are incorrect and the error is not detected at all.

Exercise 6.13

Try running one copy of Program 6.9 (seqserverbad) and two copies of Program 6.10 (seqclientbad). What happens?

Answer:

This should work correctly. The two copies of seqclientbad should get disjoint sets of sequence numbers.

Exercise 6.14

Try running two copies of Program 6.9 (seqserverbad) and one copy of Program 6.10 (seqclientbad). What happens?

Answer:

Either server can respond to a request for a sequence number. It is possible that the client will get the same sequence number twice.

Exercise 6.15

Change the seqclientbad to have a SLEEP_MAX of 0 and a REPEAT_MAX of 1,000,000. Comment out the last fprintf line. Run two copies of the client with one copy of the server. What happens?

Answer:

It is possible, but unlikely, that the server will terminate because one of the clients received an incorrect number of bytes when requesting the sequence number.


    / 276