Êíèãà: Programming with POSIX® Threads

4.3 Client/Server

4.3 Client/Server

But the Judge said he never had summed up before;

So the Snark undertook it instead,

And summed it so well that it came to far more

Than the Witnesses ever had said!

Lewis Carroll, The Hunting of the Snark

In a client/server system, a "client" requests that a "server" perform some operation on a set of data (Figure 4.3). The server performs the operation independently — the client can either wait for the server or proceed in parallel and look for the result at a later time when the result is required. Although it is simplest to have the client wait for the server, that's rarely very useful — it certainly doesn't


FIGURE 4.3 Client/Server

provide a speed advantage to the client. On the other hand, it can be an easy way to manage synchronization for some common resource.

If a set of threads all need to read input from stdin, it might be confusing for them to each issue independent prompt-and-read operations. Imagine that two threads each writes its prompt using printf, and then each reads the response using gets — you would have no way of knowing to which thread you were responding. If one thread asks "OK to send mail?" and the other asks "OK to delete root directory?" you'd probably like to know which thread will receive your response. Of course there are ways to keep the prompt and response "connected" without introducing a server thread; for example, by using the flockfile and funlockfile functions to lock both stdin and stdout around the prompt-and-read sequences, but a server thread is more interesting — and certainly more relevant to this section.

In the following program, server.c, each of four threads will repeatedly read, and then echo, input lines. When the program is run you should see the threads prompt in varying orders, and another thread may prompt before the echo. But you'll never see a prompt or an echo between the prompt and read performed by the "prompt server."

7-9 These symbols define the commands that can be sent to the "prompt server." It can be asked to read input, write output, or quit.

14-22 The request_t structure defines each request to the server. The outstanding requests are linked in a list using the next member. The operation member contains one of the request codes (read, write, or quit). The synchronous member is nonzero if the client wishes to wait for the operation to be completed (synchronous), or 0 if it does not wish to wait (asynchronous).

27-33 The tty_server_t structure provides the context for the server thread. It has the synchronization objects (mutex and request), a flag denoting whether the server is running, and a list of requests that have been made and not yet processed (first and last).

35-37 This program has a single server, and the control structure (tty_server) is statically allocated and initialized here. The list of requests is empty, and the server is not running. The mutex and condition variable are statically initialized.

43-45 The main program and client threads coordinate their shutdown using these synchronization objects (client_mutex and clients_done) rather than using pthread_join.

? server.c part 1 definitions

1 #include <pthread.h>
2 #include <math.h>
3 #include "errors.h" 4
5 #define CLIENT THREADS 4 /* Number of clients */
6
7 #define REQ_READ 1 /* Read with prompt */
8 #define REQ WRITE 2 /* Write */
9 #define REQ_QUIT 3 /* Quit server */
10
11 /*
12 * Internal to server "package" — one for each request.
13 */
14 typedef struct request_tag {
15  struct request_tag *next; /* Link to next */
16  int operation; /* Function code */
17  int synchronous; /* Nonzero if synchronous */
18  int done_flag; /* Predicate for wait */
19  pthread_cond_t done; /* Wait for completion */
20  char prompt[32]; /* Prompt string for reads */
21  char text[128]; /* Read/write text */
22 } request_t;

23
24 /*
25 * Static context for the server
26 */
27 typedef struct tty_server_tag {
28  request_t *first;
29  request_t *last;
30  int running;
31  pthread_mutex_t mutex;
32  pthread_cond_t request;
33 } tty_server_t;

34
35 tty_server_t tty_server = {
36  NULL, NULL, 0,
37  PTHREAD MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER};
38
39 /*
40 * Main program data
41 */ 42
43 int client_threads;
44 pthread_mutex_t client_mutex = PTHREAD_MUTEX_INITIALIZER;
45 pthread_cond_t clients_done = PTHREAD_COND_INITIALIZER;

Part 2 shows the server thread function, tty_server_routine. It loops, processing requests continuously until asked to quit.

25-30 The server waits for a request to appear using the request condition variable.

31-34 Remove the first request from the queue — if the queue is now empty, also clear the pointer to the last entry (tty_server.last).

43-66 The switch statement performs the requested work, depending on the operation given in the request packet. REQ_QUIT tells the server to shut down. REQ_ READ tells the server to read, with an optional prompt string. REQ_WRITE tells the server to write a string.

67-79 If a request is marked "synchronous" (synchronous flag is nonzero), the server sets done_flag and signals the done condition variable. When the request is synchronous, the client is responsible for freeing the request packet. If the request was asynchronous, the server frees request on completion.

80-81 If the request was REQ_QUIT, terminate the server thread by breaking out of the while loop, to the return statement.

? server.c part 2 tty_server_routine

1 /*
2 * The server start routine. It waits for a request to appear
3 * in tty_server.requests using the request condition variable.
4 * It processes requests in FIFO order. If a request is marked
5 * "synchronous" (synchronous != 0), the server will set done_flag
6 * and signal the request's condition variable. The client is
7 * responsible for freeing the request. If the request was not
8 * synchronous, the server will free the request on completion.
9 */
10 void *tty_server_routine (void *arg)
11 {
12 static pthread_mutex_t prompt_mutex = PTHREAD_MUTEX_INITIALIZER;
13 request_t *request;
14 int operation, len;
15 int status;

16
17 while (1) {
18 status = pthread_mutex_lock (&tty_server.mutex);
19 if (status != 0)
20 err_abort (status, "Lock server mutex");

21
22 /*
23 * Wait for data
24 */ 
25 while (tty_server.first == NULL) {
26 status = pthread_cond_wait (
27 &tty_server.request, &tty_server.mutex);
28 if (status != 0)
29 err_abort (status, "Wait for request");
30 }
31 request = tty_server.first;
32 tty_server.first = request->next;
33 if (tty_server.first == NULL)
34 tty_server.last = NULL;
35 status = pthread_mutex_unlock (&tty_server.mutex);
36 if (status != 0)
37 err_abort (status, "Unlock server mutex");
38
39 /*
40 * Process the data
41 */
42 operation = request->operation;
43 switch (operation) {
44 case REQ_QUIT:
45 break;
46 case REQ_READ:
47 if (strlen (request->prompt) > 0)
48 printf (request->prompt);
49 if (fgets (request->text, 128, stdin) == NULL)
50 request->text[0] = '';
51 /*
52 * Because fgets returns the newline, and we don't
53 * want it, we look for it, and turn it into a null
54 * (truncating the input) if found. It should be the
55 * last character, if it is there.
56 */
57 len = strlen (request->text);
58 if (len > 0 && request->text[len-l] == 'n')
59 request->text[len-l] = '';
60 break;
61 case REQ_WRITE:
62 puts (request->text);
63 break;
64 default:
65 break;
66 }
67 if (request->synchronous) {
68 status = pthread_mutex_lock (&tty_server.mutex);
69 if (status != 0)
70 err_abort (status, "Lock server mutex");
71 request->done_flag = 1;
72 status = pthread_cond_signal (&request->done);
73 if (status != 0)
74 err_abort (status, "Signal server condition");
75 status = pthread_mutex_unlock (&tty_server.mutex);
76 if (status != 0)
77 err_abort (status, "Unlock server mutex");
78 } else
79 free (request);
80 if (operation == REQ_QUIT)
81 break;
82 }
83 return NULL;
84 }

Part 3 shows the function that is called to initiate a request to the tty server thread. The caller specifies the desired operation (REQ_QUIT, REQ_READ, or REQ_ WRITE), whether the operation is synchronous or not (sync), an optional prompt string (prompt) for REQ_READ operations, and the pointer to a string (input for REQ_WRITE, or a buffer to return the result of an REQ_READ operation).

16-40 If a tty server thread is not already running, start one. A temporary thread attributes object (detached_attr) is created, and the detachstate attribute is set to PTHREAD_CREATE_DETACHED. Thread attributes will be explained later in Section 5.2.3. In this case, we are just saying that we will not need to use the thread identifier after creation.

45-76 Allocate and initialize a server request (request_t) packet. If the request is synchronous, initialize the condition variable (done) in the request packet — otherwise the condition variable isn't used. The new request is linked onto the request queue.

81-83 Wake the server thread to handle the queued request.

88-105 If the request is synchronous, wait for the server to set done_flag and signal the done condition variable. If the operation is REQ_READ, copy the result string into the output buffer. Finally, destroy the condition variable, and free the request packet.

? server.c part 3 tty_server_request

1 /*
2 * Request an operation
3 */
4 void tty_server_request (
5 int operation,
6 int sync,
7 const char *prompt,
8 char *string)
9 {
10 request_t *request;
11 int status;

12
13 status = pthread_mutex_lock (&tty_server.mutex);
14 if (status != 0)
15 err_abort (status, "Lock server mutex");
16 if (!tty_server.running) {
17 pthread_t thread;
18 pthread_attr_t detached_attr;

19
20 status = pthread_attr_init (&detached_attr);
21 if (status != 0)
22 err_abort (status, "Init attributes object");
23 status = pthread_attr_setdetachstate (
24 &detached_attr, PTHREAD_CREATE_DETACHED);
25 if (status != 0)
26 err_abort (status, "Set detach state");
27 tty_server.running = 1;
28 status = pthread_create (&thread, &detached_attr,
29 tty_server_routine, NULL);
30 if (status != 0)
31 err_abort (status, "Create server");

32
33 /*
34 * Ignore an error in destroying the attributes object.
35 * It's unlikely to fail, there's nothing useful we can
36 * do about it, and it's not worth aborting the program
37 * over it.
38 */
39 pthread_attr_destroy (&detached_attr);
40 }

41
42 /*
43 * Create and initialize a request structure.
44 */
45 request = (request_t*)malloc (sizeof (request_t));
46 if (request == NULL)
47 errno_abort ("Allocate request");
48 request->next = NULL;
49 request->operation = operation;
50 request->synchronous = sync;
51 if (sync) {
52 request->done_flag = 0;
53 status = pthread_cond_init (&request->done, NULL);
54 if (status != 0)
55 err_abort (status, "Init request condition");
56 }
57 if (prompt != NULL)
58 strncpy (request->prompt, prompt, 32);
59 else
60 request->prompt[0] = '';
61 if (operation == REQ_WRITE && string != NULL)
62 strncpy (request->text, string, 128);
63 else
64 request->text[0] = '';

65
66 /*
67 * Add the request to the queue, maintaining the first and
68 * last pointers.
69 */
70 if (tty_server.first == NULL) {
71 tty_server.first = request;
72 tty_server.last = request;
73 } else {
74 (tty_server.last)->next = request;
75 tty_server.last = request;
76 } 77
78 /*
79 * Tell the server that a request is available.
80 */
81 status = pthread_cond_signal (&tty_server.request);
82 if (status != 0)
83 err_abort (status, "Wake server");
84
85 /*
86 * If the request was "synchronous", then wait for a reply.
87 */
88 if (sync) {
89 while (!request->done_flag) {
90 status = pthread_cond_wait (
91 &request->done, &tty_server.mutex);
92 if (status != 0)
93 err_abort (status, "Wait for sync request");
94 }
95 if (operation == REQ_READ) {
96 if (strlen (request->text) > 0)
97 strcpy (string, request->text);
98 else
99 string[0] = '*;
100 }
101 status = pthread_cond_destroy (&request->done);
102 if (status != 0)
103 err_abort (status, "Destroy request condition");
104 free (request);
105 }
106 status = pthread_mutex_unlock (&tty_server.mutex);
107 if (status != 0)
108 err_abort (status, "Unlock mutex");
109 }

Part 4 shows the thread start function for the client threads, which repeatedly queue tty operation requests to the server.

12-22 Read a line through the tty server. If the resulting string is empty, break out of the loop and terminate. Otherwise, loop four times printing the result string, at one-second intervals. Why four? It just "mixes things up" a little.

26-31 Decrease the count of client threads, and wake the main thread if this is the last client thread to terminate.

server.c part 4 client_routine

1 /*
2 * Client routine — multiple copies will request server.
3 */
4 void *client_routine (void *arg)
5 {
6 int my_number = (int)arg, loops;
7 char prompt[32];
8 char string[128], formatted[128];
9 int status;

10
11 sprintf (prompt, "Client %d> ", my_number);
12 while (1) {
13 tty_server_request (REQ_READ, 1, prompt, string);
14 if (strlen (string) == 0)
15 break;
16 for (loops = 0; loops < 4; loops++) {
17 sprintf (
18 formatted, "(%d#%d) %s", my_number, loops, string);
19 tty_server_request (REQ_WRITE, 0, NULL, formatted);
20 sleep (1);
21 }
22 }
23 status = pthread_mutex_lock (&client_mutex);
24 if (status != 0)
25 err_abort (status, "Lock client mutex");
26 client_threads--;
27 if (client_threads <= 0) {
28 status = pthread_cond_signal (&clients_done);
29 if (status != 0)
30 err_abort (status, "Signal clients done");
31 }
32 status = pthread_mutex_unlock (&client_mutex);
33 if (status != 0)
34 err_abort (status, "Unlock client mutex");
35 return NULL;
36 }

? server.c part 4 client_routine

Part 5 shows the main program for server.c. It creates a set ofclient threads to utilize the tty server, and waits for them.

7-15 On a Solaris system, set the concurrency level to the number of client threads by calling thr_setconcurrency. Because all the client threads will spend some of their time blocked on condition variables, we don't really need to increase the concurrency level for this program — however, it will provide less predictable execution behavior.

20-26 Create the client threads.

27-35 This construct is much like pthread_join, except that it completes only when all of the client threads have terminated. As I have said elsewhere, pthread_join is nothing magical, and there is no reason to use it to detect thread termination unless it does exactly what you want. Joining multiple threads in a loop with pthread_join is rarely exactly what you want, and a "multiple join" like that shown here is easy to construct.

? server.c part 5 main

1 int main (int argc, char *argv[])
2 {
3 pthread_t thread;
4 int count;
5 int status; 6
7 #ifdef sun
8 /*
9 * On Solaris 2.5, threads are not timesliced. To ensure
10 * that our threads can run concurrently, we need to
11 * increase the concurrency level to CLIENT_THREADS.
12 */
13 DPRINTF (("Setting concurrency level to %dn", CLIENT_THREADS));
14 thr_setconcurrency (CLIENT_THREADS);
15 #endif

16
17 /*
18 * Create CLIENT_THREADS clients.
19 */
20 client_threads = CLIENT_THREADS;
21 for (count = 0; count < client_threads; count++) {
22 status = pthread_create (&thread, NULL,
23 client_routine, (void*)count);
24 if (status != 0)
25 err_abort (status, "Create client thread");
26 }
27 status = pthread_mutex_lock (&client_mutex);
28 if (status != 0)
29 err_abort (status, "Lock client mutex");
30 while (client_threads > 0) {
31 status = pthread_cond_wait (&clients_done, &client_mutex);
32 if (status != 0)
33 err_abort (status, "Wait for clients to finish");
34 }
35 status = pthread_mutex_unlock (&client_mutex);
36 if (status != 0)
37 err_abort (status, "Unlock client mutex");
38 printf ("All clients donen");
39 tty_server_request (REQ_QUIT, 1, NULL, NULL);
40 return 0;
41 }

Îãëàâëåíèå êíèãè

Îãëàâëåíèå ñòàòüè/êíèãè

Ãåíåðàöèÿ: 0.074. Çàïðîñîâ Ê ÁÄ/Cache: 2 / 0
ïîäåëèòüñÿ
Ââåðõ Âíèç