18.3.3 SHTTPD的多客户端支持的实现
服务器SHTTPD的多客户端支持框架的函数主要为Worker_ScheduleRun()和函数Worker_ScheduleStop(),这两个函数通过对结构struct worker_opts进行管理来控制线程的状态。结构struct worker_opts的原型如下:
struct worker_opts{
pthread_t th; /*线程的ID号*/
int flags; /*线程状态*/
pthread_mutex_t mutex; /*线程任务互斥*/
struct worker_ctl *work; /*本线程的总控结构*/
};
结构中的成员变量flags用于表示线程所处的状态,成员mutex用于控制对本线程互斥区成员的访问,th为线程建立时的线程ID号。
Worker_ScheduleRun()函数初始化多个处理客户端请求的业务线程,并侦听客户端的连接请求,当有连接到来的时候,查询业务处理线程,将此客户端分配给业务处理线程。
(1)首先初始化业务处理线程。
/*主调度过程,
* 当有客户端连接到来的时候
* 将客户端连接分配给空闲客户端
* 由客户端处理到来的请求
*/
int Worker_ScheduleRun(int ss)
{
DBGPRINT("==>Worker_ScheduleRun/n");
struct sockaddr_in client;
socklen_t len = sizeof(client);
/*初始化业务线程*/
Worker_Init();
int i = 0;
(2)当调度状态SCHEDULESTATUS为STATUS_RUNNING的时候,select()等待客户端的连接。当有客户端连接到来的时候,查找空闲的业务线程,如果没有则增加一个业务线程,然后将此任务分配给找到的空闲业务线程。
for(;SCHEDULESTATUS== STATUS_RUNNING;)
{
struct timeval tv; /*超时时间*/
fd_set rfds; /*读文件集*/
int retval = -1;
/*清空读文件集,将客户端连接描述符放入读文件集*/
FD_ZERO(&rfds);
FD_SET(ss, &rfds);
tv.tv_sec = 0; /*设置超时*/
tv.tv_usec = 500000;
retval = select(ss + 1, &rfds, NULL, NULL, &tv); /*超时读数据*/
switch(retval)
{
case -1: /*错误*/
case 0: /*超时*/
continue;
break;
default:
if(FD_ISSET(ss, &rfds)) /*检测文件*/
{
int sc = accept(ss, (struct sockaddr*)&client, &len);
i = WORKER_ISSTATUS(WORKER_IDEL); /*查找空闲业务线程*/
if(i == -1) /*没有找到*/
{
/*是否到达最大客户端数*/
i = WORKER_ISSTATUS(WORKER_DETACHED);
if(i != -1) /*没有,增加一个业务处理线程*/
Worker_Add( i);
}
if(i != -1) /*业务处理线程空闲,分配任务*/
{
wctls[i].conn.cs = sc; /*套接字描述符*/
pthread_mutex_unlock(&wctls[i].opts.mutex);
/*告诉业务线程有任务*/
}
}
}
}
DBGPRINT("<==Worker_ScheduleRun/n");
return 0;
}
(3)Worker_ScheduleStop()函数用于终止业务处理线程和分配任务线程。它先将控制分配任务流程的变量SCHEDULESTATUS设置为STATSU_STOP,保证分配业务线程不再分配任务;然后给所有的业务处理线程发送终止命令,等待所有的业务线程退出,当所有的业务线程销毁之后,释放资源并退出。
/*停止调度过程*/
int Worker_ScheduleStop()
{
DBGPRINT("==>Worker_ScheduleStop/n");
SCHEDULESTATUS = STATSU_STOP; /*给任务分配线程设置终止条件*/
int i =0;
Worker_Destory(); /*销毁业务线程*/
int allfired = 0;
for(;!allfired;) /*查询并等待业务线程终止*/
{
allfired = 1;
for(i = 0; i<conf_para.MaxClient;i++)
{
int flags = wctls[i].opts.flags;
if(flags == WORKER_DETACHING || flags == WORKER_IDEL)
/*线程正活动*/
allfired = 0;
}
}
pthread_mutex_destroy(&thread_init); /*销毁互斥变量*/
for(i = 0; i<conf_para.MaxClient;i++) /*销毁业务处理线程的互斥*/
pthread_mutex_destroy(&wctls[i].opts.mutex);
free(wctls); /*销毁业务数据*/
DBGPRINT("<==Worker_ScheduleStop/n");
return 0;
}
业务处理线程的维护有业务处理线程初始化、业务处理线程销毁、业务处理线程增加和业务处理线程删除等。负责初始化、增加、删除和销毁功能,并且有查询业务处理线程状态的函数,方便控制业务线程。
(4)初始化业务线程的函数为Worker_Init(),负责申请维护全部业务和线程状态的结构struct worker_ctl,个数为配置文件中设置的最大客户端数,每一个均表示一个线程:
/*初始化线程*/
static void Worker_Init()
{
DBGPRINT("==>Worker_Init/n");
int i = 0;
wctls = (struct worker_ctl*)malloc(sizeof(struct worker_ctl)*conf_para.
MaxClient); /*初始化总控参数*/
memset(wctls, 0, sizeof(*wctls)*conf_para.MaxClient);/*清零*/
结构定义如下,其中的成员opts用于表示线程的状态,成员conn表示客户端请求的状态和值:
struct worker_ctl{
struct worker_opts opts;
struct worker_conn conn;
};
其中的结构struct worker_conn用于维护客户端请求和响应数据,结构如下所示。其中的dreq存放接收到的客户端请求数据,dres存放发送给客户端的数据,cs为与客户端连接的套接字描述符,to表示超时响应时间,con_res维护响应结构,con_req维护客户端的 请求。
struct worker_conn
{
#define K 1024
char dreq[16*K]; /*请求缓冲区*/
char dres[16*K]; /*响应缓冲区*/
int cs; /*客户端套接字文件描述符*/
int to; /*客户端无响应时间超时退出时间*/
struct conn_response con_res; /*响应结构*/
struct conn_request con_req; /*请求结构*/
struct worker_ctl *work; /*本线程的总控结构*/
};
(5)然后开始初始化结构struct worker_ctl的值,例如设置线程的默认状态为WORKER_DETACHED,表示可以建立线程挂接到此结构上,初始化互斥量并调用函数pthread_mutex_lock锁定互斥区。还完成各部分的回指针,方便之后的调用,主要有控制结构opts中指向总控结构的指针,请求结构中指向连接的指针,响应结构中指向连接的指针。
/*初始化一些参数*/
for(i = 0; i<conf_para.MaxClient;i++)
{
/*opts&conn结构与worker_ctl结构形成回指针*/
wctls[i].opts.work = &wctls[i];
wctls[i].conn.work = &wctls[i];
/*opts结构部分的初始化*/
wctls[i].opts.flags = WORKER_DETACHED;
//wctls[i].opts.mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_init(&wctls[i].opts.mutex,NULL);
pthread_mutex_lock(&wctls[i].opts.mutex);
/*conn部分的初始化*/
/*con_req&con_res与conn结构形成回指*/
wctls[i].conn.con_req.conn = &wctls[i].conn;
wctls[i].conn.con_res.conn = &wctls[i].conn;
wctls[i].conn.cs = -1;/*客户端socket连接为空*/
/*con_req部分初始化*/
wctls[i].conn.con_req.req.ptr = wctls[i].conn.dreq;
wctls[i].conn.con_req.head = wctls[i].conn.dreq;
wctls[i].conn.con_req.uri = wctls[i].conn.dreq;
/*con_res部分初始化*/
wctls[i].conn.con_res.fd = -1;
wctls[i].conn.con_res.res.ptr = wctls[i].conn.dres;
}
在初始化参数完毕后,建立多个业务线程,其个数由配置时指定,即初始化客户端 数量。
for(i = 0; i<conf_para.InitClient;i++)
{
/*增加规定个数工作线程*/
Worker_Add(i);
}
DBGPRINT("<==Worker_Init/n");
}