《UNIX网络编程 卷1》 笔记: 多线程—web客户程序

来源:互联网 发布:单片机晶振电容选择 编辑:程序博客网 时间:2024/06/05 15:33

在非阻塞式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限制了同时建立的最大连接数,也就限制了能同时创建的线程的数量),然后等待其终止,再创建新的线程继续读取文件。

阅读全文
0 0
原创粉丝点击