UNIX网络编程——客户/服务器程序设计示范(八)
TCP预先创建线程服务器程序,主线程统一accept
最后一个使用线程的服务器程序设计示范是在程序启动阶段创建一个线程池之后只让主线程调用accept并把每个客户连接传递给池中某个可用线程。
本设计示范的问题在于主线程如何把一个已连接套接字传递给线程池中某个可用线程。这里有多个实现手段。我们原本可以如前使用描述符传递,不过既然所有线程和所有描述符都在同一个进程之内,我们没有必要把一个描述符从一个线程传递到另一个线程。接收线程只需知道这个已连接套接字描述符的值,而描述符传递实际传递的并非这个值,而是对这个套接字的一个引用,因而将返回一个不同于原值的描述符(该套接字的引用计数也被递增)。
typedef struct { pthread_t thread_tid; /* thread ID */ long thread_count; /* # connections handled */ } Thread; Thread *tptr; /* array of Thread structures; calloc'ed */ #define MAXNCLI 32 int clifd[MAXNCLI], iget, iput; pthread_mutex_t clifd_mutex; pthread_cond_t clifd_cond;定义存放已连接套接字描述符的共享数组
07-10 我们还定义一个clifd数组,由主线程往中存入已接受的已连接套接字描述符,并由线程池中的可用线程从中取出一个以服务相应的客户。iput是主线程将往该数组中存入的下一个元素的下标,iget是线程池中某个线程将从该数组中取出的下一个元素的下标。这个有所有线程共享的数据结构自然必须得到保护,我们使用互斥锁和条件变量做到这一点。
#include "unpthread.h" #include "pthread08.h" static int nthreads; pthread_mutex_t clifd_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t clifd_cond = PTHREAD_COND_INITIALIZER; int main(int argc, char **argv) { int i, listenfd, connfd; void sig_int(int), thread_make(int); socklen_t addrlen, clilen; struct sockaddr *cliaddr; if (argc == 3) listenfd = Tcp_listen(NULL, argv[1], &addrlen); else if (argc == 4) listenfd = Tcp_listen(argv[1], argv[2], &addrlen); else err_quit("usage: serv08 [ <host> ] <port#> <#threads>"); cliaddr = Malloc(addrlen); nthreads = atoi(argv[argc-1]); tptr = Calloc(nthreads, sizeof(Thread)); iget = iput = 0; /* 4create all the threads */ for (i = 0; i < nthreads; i++) thread_make(i); /* only main thread returns */ Signal(SIGINT, sig_int); for ( ; ; ) { clilen = addrlen; connfd = Accept(listenfd, cliaddr, &clilen); Pthread_mutex_lock(&clifd_mutex); clifd[iput] = connfd; if (++iput == MAXNCLI) iput = 0; if (iput == iget) err_quit("iput = iget = %d", iput); Pthread_cond_signal(&clifd_cond); Pthread_mutex_unlock(&clifd_mutex); } } void sig_int(int signo) { int i; void pr_cpu_time(void); pr_cpu_time(); for (i = 0; i < nthreads; i++) printf("thread %d, %ld connections\n", i, tptr[i].thread_count); exit(0); }
创建线程池
29-30 使用thread_make创建池中的每个线程。
等待客户连接
34-46 主线程大部分时间阻塞在accept调用中,等待各个客户连接的到达。一旦某个客户连接到达,主线程就把它的已连接套接字描述符存入clifd数组的下一个元素,不过需事先获取保护该数组的互斥锁。主线程还检查iput下标没有赶上iget下标(若赶上则说明该数组不够大),并发送信号到条件变量信号,然后释放互斥锁,以允许线程池中某个线程为这个客户服务。
#include "unpthread.h" #include "pthread08.h" void thread_make(int i) { void *thread_main(void *); Pthread_create(&tptr[i].thread_tid, NULL, &thread_main, (void *) i); return; /* main thread returns */ } void * thread_main(void *arg) { int connfd; void web_child(int); printf("thread %d starting\n", (int) arg); for ( ; ; ) { Pthread_mutex_lock(&clifd_mutex); while (iget == iput) Pthread_cond_wait(&clifd_cond, &clifd_mutex); connfd = clifd[iget]; /* connected socket to service */ if (++iget == MAXNCLI) iget = 0; Pthread_mutex_unlock(&clifd_mutex); tptr[(int) arg].thread_count++; web_child(connfd); /* process request */ Close(connfd); } }等待未之服务的客户描述符
21-31 线程池中每个线程都试图获取保护clifd数组的互斥锁。获得之后就测试iput与iget,若两者相等则无事可做,于是通过调用pthread_cond_wait睡眠在条件变量上。主线程接受一个连接后将待用pthread_cond_signal向条件变量发送信号,以唤醒睡眠在其上的线程。若测得iput与iget不等则从clifd数组中取出下一个元素以获得一个连接,然后调用web_child。