Книга: Distributed operating systems

2.3.2. An Example Client and Server

2.3.2. An Example Client and Server

To provide more insight into how clients and servers work, in this section we will present an outline of a client and a file server in C. Both the client and the server need to share some definitions, so we will collect these into a file called header.h, which is shown in Fig. 2-8. Both the client and server include these using the

#include <header.h>

statement. This statement has the effect of causing a preprocessor to literally insert the entire contents of header.h into the source program just before the compiler starts compiling the program.

/* Definitions needed by clients and servers. */
#define MAX_PATH    255 /* maximum length of a file name */
#define BUF_SIZE   1024 /* how much data to transfer at once */
#define FILE_SERVER 243 /* file server's network address */
/* Definitions of the allowed operations. */
#define CREATE 1 /* create a new file */
#define READ   2 /* read a piece of a file and return it */
#define WRITE  3 /* write a piece of a file */
#define DELETE 4 /* delete an existing file */
/* Error codes. */
#define OK            0 /* operation performed correctly */
#define E_BAD_OPCODE –1 /* unknown operation requested */
#define E_BAD_PARAM  –2 /* error in a parameter */
#define E_IO         –3 /* disk error or other I/O error */
/* Definition of the message format. */
struct message {
 long source; /* sender's identity */
 long dest; /* receiver's identity */
 long opcode; /* which operation: CREATE, READ, etc. */
 long count; /* how many bytes to transfer */
 long offset; /* where in file to start reading or writing */
 long extra1; /* extra field */
 long extra2; /* extra field */
 long result; /* result of the operation reported here */
 char name[MAX_PATH]; /* name of the file being operated on */
 char data[BUF_SIZE]; /* data to be read or written */
};

Fig. 2-8. The header.h file used by the client and server.

Let us first take a look at header.h. It starts out by defining two constants, MAX_PATH and BUF_SIZE, that determine the size of two arrays needed in the message. The former tells how many characters a file name (i.e., a path name like /usr/ast/books/opsys/chapter1.t) may contain. The latter fixes the amount of data that may be read or written in one operation by setting the buffer size. The next constant, FILE_SERVER, provides the network address of the file server so that clients can send messages to it.

The second group of constants defines the operation numbers. These are needed to ensure that the client and server agree on which code will represent a READ, which code will represent a WRITE, and so on. We have only shown four here, but in a real system there would normally be more.

Every reply contains a result code. If the operation succeeds, the result code often contains useful information (such as the number of bytes actually read). If there is no value to be returned (such as when a file is created), the value OK is used. If the operation is unsuccessful for some reason, the result code tells why, using codes such as E_BAD_OPCODE, E_BAD_PARAM, and so on.

Finally, we come to the most important part of header.h, the definition of the message itself. In our example it is a structure with 10 fields. All requests from the client to the server use this format, as do all replies. In a real system, one would probably not have a fixed format message (because not all the fields are needed in all cases), but it makes the explanation simpler here. The source and dest fields identify the sender and receiver, respectively. The opcode field is one of the operations defined above, that is, CREATE, READ, WRITE, or DELETE. The count and offset fields are used for parameters, and two other fields, extra1 and extra2, are defined to provide space for additional parameters in case the server is expanded in the future. The result field is not used for client-to-server requests but holds the result value for server-to-client replies. Finally, we have two arrays. The first, name, holds the name of the file being accessed. The second, data, holds the data sent back on a reply to read or the data sent to the server on a WRITE.

Let us now look at the code, as outlined in Fig. 2-9. In (a) we have the server; in (b) we have the client. The server is straightforward. The main loop starts out by calling receive to get a request message. The first parameter identifies the caller by giving its address, and the second parameter points to a message buffer where the incoming message can be stored. The library procedure receive traps to the kernel to suspend the server until a message arrives. When one comes in, the server continues and dispatches on the opcode type. For each opcode, a different procedure is called. The incoming message and a buffer for the outgoing message are given as parameters. The procedure examines the incoming message, ml, and builds the reply in ml. It also returns a function value that is sent back in the result field. After the send has completed, the server goes back to the top of the loop to execute receive and wait for the next incoming message.

In Fig. 2-9(b) we have a procedure that copies a file using the server. Its body consists of a loop that reads one block from the source file and writes it to the destination file. The loop is repeated until the source file has been copied completely, as indicated by a zero or negative return code from the read.

The first part of the loop is concerned with building a message for the READ operation and sending it to the server. After the reply has been received, the second part of the loop is entered, which takes the data just received and sends it back to the server in the form of a WRITE to the destination file. The programs of Fig. 2-9 are just sketches of the code. Many details have been omitted. For example, the do_xxx procedures (the ones that actually do the work) are not shown, and no error checking is done. Still, the general idea of how a client and a server interact should be clear. In the following sections we will look at some of the issues that relate to clients and servers in more detail.

#include <header.h>
void main(void) {
 struct message m1, m2; /* incoming and outgoing messages */
 int r; /* result code */
 while (1) { /* server runs forever */
  receive(FILE_SERVER,&m1); /* block waiting for a message */
  switch(m1.opcode) { /* dispatch on type of request */
  case CREATE: r = do_create(&m1, &m2); break;
  case READ: r = do_read(&m1, &m2); break;
  case WRITE: r = do_write(&m1, &m2); break;
  case DELETE: r = do_delete(&m1, &m2); break;
  default: r = E_BAD_OPCODE;
 }
 m2.result = r; /* return result to client */
 send(m1.source, &m2); /* send reply */
 }
}

(a)

#include <header.h>
int copy(char *src, char *dst) /* procedure to copy file using the server */
{
 struct message m1; /* message buffer */
 long position; /* current file position */
 long client = 110; /* client's address */
 initialize(); /* prepare for execution */
 position = 0;
 do {
  /* Get a block of data from the source file. */
  m1.opcode = READ; /* operation is a read */
  m1.offset = position; /* current position in the file */
  m1.count = BUF_SIZE; /* how many bytes to read */
  strcpy(&m1.name, src); /* copy name of file to be read to message */
  send(FILE_SERVER, &m1); /* send the message to the file server */
  receive(client, &m1); /* block waiting for the reply */
  /* Write the data just received to the destination file. */
  m1.opcode = WRITE; /* operation is a write */
  m1.offset = position; /* current position in the file */
  m1.count = m1.result; /* how many bytes to write */
  strcpy(&m1.name, dst); /* copy name of file to be written to buf */
  send(FILE_SERVER, &m1); /* send the message to the file server */
  receive(client, &m1); /* block waiting for the reply */
  position += m1.result; /* m1.result is number of bytes written •/
 } while (m1.result > 0); /* iterate until done */
 return(m1.result >= 0 ? OK : m1.result); /* return OK or error code */
}

Fig. 2-9. (a) A sample server. (b) A client procedure using that server to copy a file.

Оглавление книги


Генерация: 0.591. Запросов К БД/Cache: 3 / 0
поделиться
Вверх Вниз