1、实现代码
/*
* echoservert_pre.c - A prethreaded concurrent echo server
*/
/* $begin echoservertpremain */
#include "csapp.h"
#include "sbuf.h"
#define NTHREADS 4
#define SBUFSIZE 16
void echo_cnt(int connfd);
void *thread(void *vargp);
sbuf_t sbuf; /* shared buffer of connected descriptors */
int main(int argc, char **argv)
{
int i, listenfd, connfd, port;
socklen_t clientlen=sizeof(struct sockaddr_in);
struct sockaddr_in clientaddr;
pthread_t tid;
if (argc != 2) {
fprintf(stderr, "usage: %s <port>\n", argv[0]);
exit(0);
}
port = atoi(argv[1]);
sbuf_init(&sbuf, SBUFSIZE); //line:conc:pre:initsbuf
listenfd = Open_listenfd(port);
for (i = 0; i < NTHREADS; i++) /* Create worker threads */ //line:conc:pre:begincreate
Pthread_create(&tid, NULL, thread, NULL); //line:conc:pre:endcreate
while (1) {
connfd = Accept(listenfd, (SA *) &clientaddr, &clientlen);
sbuf_insert(&sbuf, connfd); /* Insert connfd in buffer */
}
}
void *thread(void *vargp)
{
Pthread_detach(pthread_self());
while (1) {
int connfd = sbuf_remove(&sbuf); /* Remove connfd from buffer */ //line:conc:pre:removeconnfd
echo_cnt(connfd); /* Service client */
Close(connfd);
}
}
/* $end echoservertpremain */
echo_cnt.c代码如下
* A thread-safe version of echo that counts the total number
* of bytes received from clients.
*/
/* $begin echo_cnt */
#include "csapp.h"
static int byte_cnt; /* byte counter */
static sem_t mutex; /* and the mutex that protects it */
static void init_echo_cnt(void)
{
Sem_init(&mutex, 0, 1);
byte_cnt = 0;
}
void echo_cnt(int connfd)
{
int n;
char buf[MAXLINE];
rio_t rio;
static pthread_once_t once = PTHREAD_ONCE_INIT;
Pthread_once(&once, init_echo_cnt); //line:conc:pre:pthreadonce
Rio_readinitb(&rio, connfd); //line:conc:pre:rioinitb
while((n = Rio_readlineb(&rio, buf, MAXLINE)) != 0) {
P(&mutex);
byte_cnt += n; //line:conc:pre:cntaccess1
printf("thread %d received %d (%d total) bytes on fd %d\n",
(int) pthread_self(), n, byte_cnt, connfd); //line:conc:pre:cntaccess2
V(&mutex);
Rio_writen(connfd, buf, n);
}
}
/* $end echo_cnt */
2、代码分析
在之前,已经知道了如何使用信号量来访问共享变量和调度对共享资源的访问。上面是一个基于称为预线程化技术的并发服务器。在之前基于线程的服务器中,我们为每个新客户创建了一个新线程。这种方法的缺点是,我们为每一个客户端创建一个新线程,导致不小的代价。一个基于预线程化的服务器试图使用如下图所示的生产者消费者模型来降低这种开销。服务器是由一个主线程和一组工作者线程构成的。主线程不断接受来自客户端的连接请求,并将得到的连接描述符放在一个有限缓冲区中,每一个工作者线程反复从共享缓冲区中取出描述符,为客户端服务,然后等待下一个描述符。
主程序中,利用sbuf包实现一个预线程化的并发echo服务器,在初始化缓冲区sbuf后,主线程创建了一组工作者线程,然后进入了无线的服务器循环,接受连接请求,并将得到的已连接描述符插入到缓冲区sbuf中,每个工作者线程的行为都很简单,等待直到它能从缓冲区中取出一个已连接描述符,然后调用echo_cnt函数回送客户端的输入。
echo_cnt函数是echo函数的一个版本,在全局变量byte_cnt中记录所有客户端接收到的累计字节数。这个程序展示了一个从线程例程调用初始化程序包的一般技术。这种情况下,需要初始化byte_cnt计数器和mutex信号量。一个方法是之前使用过的,主线程显式的调用一个初始化函数。另一个方法就是这里的,当第一次有某个线程调用echo_cnt函数时,调用pthread_once函数去调用初始化函数,这个方法的优点是使程序包的使用更加容易。缺点是,每一次调用echo_cnt都会导致调用pthread_once函数,而在大多数时候并没有做什么有用的事情。
一旦程序包被初始化,echo_cnt函数会初始化RIO带缓冲区的IO包,然后回送从客户端接受到的每一个文本行,在其中对共享变量的访问是被P和V操作保护的。