在非阻塞式connect—web客户程序一节中,我们使用非阻塞式connect(对于每个待读取的文件,向服务器发起非阻塞连接)和select(监听所有的套接字描述符)实现了一个web客户程序,本节我们使用线程来实现同样的功能。
我们为每个待读取的文件创建一个线程,这个线程完成从服务器上读取文件的工作。因而我们修改了之前定义的file结构体:
struct file {
char *f_name; /*文件名*/
char *f_host; /*服务器主机名*/
int f_flags; /*状态*/
pthread_t f_tid; /*线程ID*/
} file[MAXFILES];
我们添加了表示线程ID的f_tid成员,移除了套接字描述符成员f_fd(我们不需要监听套接字描述符)。
读取文件的状态也只需要两种:
#define F_DONE 1 /*读取文件完成*/
#define F_JOINED 2 /*读取文件的线程已经终止*/
我们增加了三个全局变量,一个表示“条件”的变量、互斥锁和条件变量。主线程使用该条件变量等待有子线程退出,然后调用pthread_join获取其终止状态:
int ndone; /*不为0表示有线程退出*/
pthread_mutex_t ndone_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t ndone_cond = PTHREAD_COND_INITIALIZER;
线程函数代码如下,它完成从服务器上读取文件的任务。
void *do_get_read(void *vptr)
{
int fd, n;
char line[MAXLINE];
struct file *fptr;
fptr = (struct file *)vptr;
fd = Tcp_connect(fptr->f_host, SERV); /*连接到服务器*/
printf("do_get_read for %s, fd %d, thread %d\n",
fptr->f_name, fd, fptr->f_tid);
write_get_cmd(fptr); /*发起读取文件GET命令*/
for ( ; ; ) { /*读取文件*/
if ((n = Read(fd, line, MAXLINE)) == 0)
break;
printf("read %d bytes from %s\n", n, fptr->f_name);
}
printf("end-of-file on %s\n", fptr->f_name);
Close(fd);
Pthread_mutex_lock(&ndone_mutex);
fptr->f_flags = F_DONE;
ndone++;
Pthread_cond_signal(&ndone_cond);
Pthread_mutex_unlock(&ndone_mutex);
/*返回fptr*/
return fptr;
}
主函数的代码如下:
int nconn, nfiles, nlefttoconn, nlefttoread;
int main(int argc, char **argv)
{
int i, n, maxnconn;
pthread_t tid;
struct file *fptr;
if (argc < 5)
err_quit("usage: web <#conns> <hostname> <homepage> <file1> ...");
maxnconn = atoi(argv[1]); /*最大连接数*/
nfiles = min(argc - 4, MAXFILES);
for (i = 0; i < nfiles; i++) {
file[i].f_name = argv[i + 4]; /*文件名*/
file[i].f_host = argv[2]; /*服务器主机名*/
file[i].f_flags = 0;
}
printf("nfiles = %d\n", nfiles);
/*访问服务器主页*/
home_page(argv[2], argv[3]);
nlefttoread = nlefttoconn = nfiles;
nconn = 0;
while (nlefttoread > 0) {
while (nconn < maxnconn && nlefttoconn > 0) {
/*找到一个文件去读*/
for (i = 0; i < nfiles; i++)
if (file[i].f_flags == 0)
break;
if (i == nfiles)
err_quit("nlefttoconn = %d but nothing found", nlefttoconn);
/*为获取每个文件创建一个线程*/
Pthread_create(&tid, NULL, &do_get_read, &file[i]);
file[i].f_tid = tid;
nconn++;
nlefttoconn--;
}
Pthread_mutex_lock(&ndone_mutex);
while (ndone == 0)
Pthread_cond_wait(&ndone_cond, &ndone_mutex); /*等待一个线程完成任务并退出*/
for (i = 0; i < nfiles; i++) {
if (file[i].f_flags & F_DONE) {
/*等待完成任务的线程终止,获取它的退出状态*/
Pthread_join(file[i].f_tid, (void **)&fptr);
if (&file[i] != fptr)
err_quit("file[i] != fptr");
fptr->f_flags = F_JOINED;
ndone--;
nconn--;
nlefttoread--;
printf("thread %d for %s done\n", fptr->f_tid, fptr->f_name);
}
}
Pthread_mutex_unlock(&ndone_mutex);
}
exit(0);
}
home_page函数和write_get_cmd函数仍使用之前的版本。主函数不再使用select监听任何描述符,因为读取文件的任务已经由子线程完成了,它(主线程)只需等待有子线程退出(程序参数conns限制了同时建立的最大连接数,也就限制了能同时创建的线程的数量),然后等待其终止,再创建新的线程继续读取文件。