26.6 Web Client and
Simultaneous Connections (Continued)
We now revisit the Web client example from
Section 16.5 and
recode it using threads instead of nonblocking connects.
With threads, we can leave the sockets in their default blocking
mode and create one thread per connection. Each thread can block in
its call to connect, as the kernel will just run some
other thread that is ready.
Figure
26.13 shows the first part of the program, the globals, and the
start of the main function.
Globals
1鈥?6
We #include <thread.h>, in addition to the normal
<pthread.h>, because we need to use Solaris threads
in addition to Pthreads, as we will describe shortly.
10 We
have added one member to the file structure:
f_tid, the thread ID. The remainder of this code is
similar to Figure 16.15. With
this threads version, we do not use select and therefore
do not need any descriptor sets or the variable maxfd.
36 The
home_page function that is called is unchanged from
Figure 16.16.
Figure 26.13
Globals and start of main function.
threads/web01.c
1 #include "unpthread.h"
2 #include <thread.h> /* Solaris threads */
3 #define MAXFILES 20
4 #define SERV "80" /* port number or service name */
5 struct file {
6 char *f_name; /* filename */
7 char *f_host; /* hostname or IP address */
8 int f_fd; /* descriptor */
9 int f_flags; /* F_xxx below */
10 pthread_t f_tid; /* thread ID */
11 } file [MAXFILES];
12 #define F_CONNECTING 1 /* connect() in progress */
13 #define F_READING 2 /* connect() complete; now reading */
14 #define F_DONE 4 /* all done */
15 #define GET_CMD "GET %s HTTP/1.0\r\n\r\n"
16 int nconn, nfiles, nlefttoconn, nlefttoread;
17 void *do_get_read(void *);
18 void home_page(const char *, const char *);
19 void write_get_cmd(struct file *);
20 int
21 main(int argc, char **argv)
22 {
23 int i, n, maxnconn;
24 pthread_t tid;
25 struct file *fptr;
26 if (argc < 5)
27 err_quit("usage: web <#conns> <IPaddr> <homepage> file1 ...");
28 maxnconn = atoi(argv[1]);
29 nfiles = min(argc - 4, MAXFILES);
30 for (i = 0; i < nfiles; i++) {
31 file[i].f_name = argv[i + 4];
32 file[i].f_host = argv[2];
33 file[i].f_flags = 0;
34 }
35 printf("nflies = %d\n", nfiles);
36 home_page(argv[2], argv[3]);
37 nlefttoread = nlefttoconn = nfiles;
38 nconn = 0;
Figure
26.14 shows the main processing loop of the main
thread.
Figure 26.14
Main processing loop of main function.
threads/web01.c
39 while (nlefttoread > 0) {
40 while (nconn < maxnconn && nlefttoconn > 0) {
41 /* find a file to read */
42 for (i = 0; i < nfiles; i++)
43 if (file[i].f_flags == 0)
44 break;
45 if (i == nfiles)
46 err_quit("nlefttoconn = %d but nothing found", nlefttoconn);
47 file[i].f_flags = F_CONNECTING;
48 Pthread_create(&tid, NULL, &do_get_read, &file[i]);
49 file[i].f_tid = tid;
50 nconn++;
51 nlefttoconn--;
52 }
53 if ( (n = thr_join(0, &tid, (void **) &fptr)) != 0)
54 errno = n, err_sys("thr_join error");
55 nconn--;
56 nlefttoread--;
57 printf("thread id %d for %s done\n", tid, fptr->f_name);
58 }
59 exit(0);
60 }
If possible, create another
thread
40鈥?2
If we are allowed to create another thread (nconn is less
than maxnconn), we do so. The function that each new
thread executes is do_get_read and the argument is the
pointer to the file structure.
Wait for any thread to terminate
53鈥?4
We call the Solaris thread function thr_join with a first
argument of 0 to wait for any one of our threads to terminate.
Unfortunately, Pthreads does not provide a way to wait for
any one of our threads to
terminate; the pthread_join function makes us specify
exactly which thread we want to wait for. We will see in Section
26.9 that the Pthreads solution for this problem is more
complicated, requiring us to use a condition variable for the
terminating thread to notify the main thread when it is done.
The solution that we show, using the Solaris
thread thr_join function, is not portable to all
environments. Nevertheless, we want to show this version of our Web
client example using threads without having to complicate the
discussion with condition variables and mutexes. Fortunately, we
can mix Pthreads with Solaris threads under Solaris.
Figure
26.15 shows the do_get_read function, which is
executed by each thread. This function establishes the TCP
connection, sends an HTTP GET command to the server, and
reads the server's reply.
Figure 26.15
do_get_read function.
threads/web01.c
61 void *
62 do_get_read(void *vptr)
63 {
64 int fd, n;
65 char line[MAXLINE];
66 struct file *fptr;
67 fptr = (struct file *) vptr;
68 fd = Tcp_connect(fptr->f_host, SERV);
69 fptr->f_fd = fd;
70 printf("do_get_read for %s, fd %d, thread %d\n",
71 fptr->f_name, fd, fptr->f_tid);
72 write_get_cmd(fptr); /* write() the GET command */
73 /* Read server's reply */
74 for ( ; ;) {
75 if ( (n = Read(fd, line, MAXLINE)) == 0)
76 break; /* server closed connection */
77 printf("read %d bytes from %s\n", n, fptr->f_name);
78 }
79 printf("end-of-file on %s\n", fptr->f_name);
80 Close(fd);
81 fptr->f_flags = F_DONE; /* clears F_READING */
82 return (fptr); /* terminate thread */
83 }
Create TCP socket, establish
connection
68鈥?1
A TCP socket is created and a connection is established by our
tcp_connect function. The socket is a normal blocking
socket, so the thread will block in the call to connect
until the connection is established.
Write request to server
72
write_get_cmd builds the HTTP GET command and
sends it to the server. We do not show this function again as the
only difference from Figure 16.18 is that
the threads version does not call FD_SET and does not use
maxfd.
Read server's reply
73鈥?2
The server's reply is then read. When the connection is closed by
the server, the F_DONE flag is set and the function
returns, terminating the thread.
We also do not show the home_page
function, as it is identical to the version shown in Figure
16.16.
We will return to this example, replacing the
Solaris thr_join function with the more portable Pthreads
solution, but we must first discuss mutexes and condition
variables.
|