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

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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值