[ Team LiB ] |
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. Globals1–16 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 thread40–52 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 terminate53–54 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.
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 connection68–71 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 server72 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 reply73–82 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. |
[ Team LiB ] |