Web-server的线程池实现

Web-server

一、编译方式

在files目录下使用如下命令进行编译:
gcc server.c -o server -pthread
由于使用了多线程,需要添加 -pthread 选项
使用./server开启服务器

二、程序设计

1.程序分模块实现功能:
  • main函数:初始化工作,创建服务器套接字并设置监听端口、开辟线程池;调用accept()、handle_clnt()等函数与客户端交互;收尾工作,等待任务结束并销毁线程池
  • pthread模块
    • pool_init() 创建线程池
    • pool_add_worker() 向线程池中添加任务
    • thread_routine() 线程等待队列调度,包括信号量与互斥锁
    • pool_destroy() 释放线程池资源
  • 任务模块
    • handle_clnt() 开辟缓冲区接收请求,调用函数解析请求、返回响应内容
    • get_request() 从请求头中解析请求的资源路径PATH
    • get_protocol() 从请求头中解析协议类型
    • send_answer() 根据资源路径查找文件,根据查找结果按照响应行格式返回状态码和、文件信息和长度
  • 读取文件
    read_file() : 请求路径PATH查询文件并返回一个结构体,若成功,则返回文件内容的起始地址和内容长度,否则返回空指针和0文件长度以表明查找失败
2.实现功能点
  • 并发处理:采用多线程实现任务并发处理。
    由于每个任务都遵循wait — accept — handle的工作方式,故多线程能够比多进程更好地实现资源共享,提高并发处理量。
    为了降低创建新线程的开销,预先使用线程池来创建多个线程。
  • 关于最佳线程数目(设处理器核数为n):
    -CPU密集型 (n+1) :能够保证每个核都在跑任务,且当某个线程出现异常暂停运行时,额外的线程能够及时填补,以保证CPU时钟不被浪费。 而如果线程数目超过n+1,只会白白增加上下文切换次数,反而降低了CPU的利用率,得不偿失。
    -IO密集型 (2n+1) : IO密集型任务相对而言对CPU的使用率比较低,因此可以多开一定数量的线程。统计结果表明,当线程数目为(2n+1)时较可能使CPU得到最好地利用。
    本实验中的任务属于IO密集型任务,故可以开大一些的线程池。
  • 错误、异常处理:
    在程序设计中对于连接失败、端口占用、队列超长、非法请求等情况进行了条件判断,并提供了相应的错误处理机制。
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<fcntl.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <assert.h>
#include <arpa/inet.h>
#include<stdio.h>


#define HEAD "%s %s \r\nContent-Type: %s\r\nContent-Length:%d\r\n\r\n%s"

/*
*线程池里所有运行和等待的任务都是一个CThread_worker
*由于所有任务都在链表里,所以是一个链表结构
*/

typedef struct worker
{
    /*回调函数,任务运行时会调用此函数,注意也可声明成其它形式*/
    void *(*process) (void *arg);
    void *arg;/*回调函数的参数*/
    struct worker *next;
} CThread_worker;

/*线程池结构*/
typedef struct
{
    pthread_mutex_t queue_lock;
    pthread_cond_t queue_ready;

    /*链表结构,线程池中所有等待任务*/
    CThread_worker *queue_head;

    /*是否销毁线程池*/
    int shutdown;
    pthread_t *threadid;
    /*线程池中允许的活动线程数目*/
    int max_thread_num;
    /*当前等待队列的任务数目*/
    int cur_queue_size;

} CThread_pool;

typedef struct file_type
{
	char *file;
	int len;
}file_type;

// 线程相关函数声明 
static CThread_pool *pool = NULL;
void pool_init (int max_thread_num);
int pool_add_worker (void *(*process) (void *arg), void *arg);
int pool_destroy ();
void * thread_routine (void *arg);

// 文件读取函数声明 
file_type read_file(char *p);

// 任务处理函数声明 
void* handle_clnt(void* sockConn);
char * get_request(char *p);
char *get_protocol(char *p);
void answer(char *p ,char *ptl ,int resp);

file_type read_file(char *p)
{

	FILE *fp;
	int i =0;
	char ch;

	if((fp = fopen(p,"rb")) != NULL)	// 文件可打开 
	{
		FILE *fsize = fopen(p,"rb") ;
		fseek(fsize,0L,SEEK_END) ;
		int len = ftell(fsize) ;
		fclose(fsize) ;			// 获得文件内容大小 

		char *text = (char*)malloc(sizeof(char)*len);

		while (!feof(fp))
		{
			ch = fgetc(fp);
			text[i] = ch;
			i++;
		}

	    text[i]=0;
		fclose(fp);

		file_type content;
		content.len = i-1;
		content.file = text;
		return content;
	}
	else
	{
		file_type content;
		content.len = 0 ;
		content.file = NULL;
		return content;
	}
}

// 从请求头中获取文件PATH
char *get_request(char *p)
{
	
	int flag = 0;
	int begin = 0, end = 0, i = 0 ;
	char *temp = p;	// 指向buffer addr
	while (p != NULL && *p != '\n')
	{
		if (*p == '/' && flag == 0)
		{
			flag = 1;		// 标志  找到文件
			begin = i;
			end = begin;
		}
		if (flag == 1)		// 读取路径
		{
			end++ ;
			if (*p == ' ')
				break;
		}
		p++;
		i++;
		if (i > 1024)	// 请求过长,illegal
			return NULL;
	}
	char *PATH = (char *)malloc(sizeof(char) * (end - begin));
	int j = begin ;
	for (i = 0 ; j < end - 1; i++ , j++)
		PATH[i] = temp[j];
	PATH[i] = '\0';
	return PATH ;
}

// 获取协议内容
char *get_protocol(char *p)
{
	int state = 0;
	int begin = 0, end = 0, i = 0, k = 0;
	char *temp = p;
	int flag = 0 ;
	while (p != NULL && *p != '\n')
	{
		if (*p == '/' && state == 0)
			state = 1;
		
		if (state == 1)
			if (*p == ' ')
				state = 2;
		
		if (state == 2)
			if (*p != ' ')
			{
				state = 3;
				begin = i;
				end = i;
			}
		if (state == 3)
		{
			end ++;
			if (*p == '\n' || *p == ' ')
				break;
		}
		p++;
		i++;
		if (i > 1024)		// 请求过长,illegal
			return NULL;
	}
	char *Protocol = (char *)malloc(sizeof(char) * (end - begin));
	i = 0 ;
	for (int j = begin; j < end - 1; i++ , j++)
	{
		Protocol[i] = temp[j];
	}
	Protocol[i] = '\0';
	return Protocol;
}

// 响应行
void answer(char *p, char *ptl, int resp)
{
	file_type res;
	char *sendBuf = (char *)malloc(1024*sizeof(char));
	int i = 0, j = 0, flag = 0;
	if (strcmp(p, "/") == 0 || strcmp(p, "/index") == 0)
	{
		res = read_file("index.html");
		sprintf(sendBuf, HEAD, ptl, "200 OK", "text/html;charset=utf-8;", res.len, p);
	}
	else
	{
		for (j = 0; j <= strlen(p); j++)
		{
			p[j] = p[j + 1];
		}
		p[j] = 0;

		while (p[i] != NULL)
			if (p[i++] == '.')
			{
				flag = 1;
				break;
			}

		if (flag == 0 && (access(p, F_OK)) == -1)
		{
			char *sendStr = "<html><head></head><body><p style=\'color:red\'>Failure to request resources 500 </p></body></html>";
			sprintf(sendBuf, HEAD, ptl, "500 Internal Server Error", "text/html;charset=utf-8;", strlen(sendStr),p);
			send(resp, sendBuf, strlen(sendBuf), 0);
			send(resp, sendStr, strlen(sendStr), 0);
			return;
		}
		res = read_file(p);
		if (res.file == NULL)
		{
			char *sendStr = "<html><head></head><body><p style=\'color:red\'>Failure to request resources  404</p></body></html>";
			sprintf(sendBuf, HEAD, ptl, "404 Not Found", "text/html;charset=utf-8;", strlen(sendStr),p);
			send(resp, sendBuf, strlen(sendBuf), 0);
			send(resp, sendStr, strlen(sendStr), 0);
			return;
		}

		char *type = (char *)malloc(sizeof(char) * i);
		for (j = 0; j <= i; j++)
		{
			type[j] = p[i + j];
		}
		type[j] = 0;
		if (strcmp(type, "jpg") == 0 || strcmp(type, "ico") == 0)
		{
			sprintf(sendBuf, HEAD, ptl, "200 OK", "image/jpeg", res.len, p);
		}
		else if (strcmp(type, "html") == 0)
		{
			sprintf(sendBuf, HEAD, ptl, "200 OK", "text/html;charset=utf-8;", res.len, p);
		}
	}

	send(resp, sendBuf, strlen(sendBuf), 0);
	send(resp, res.file, res.len, 0);
	free(sendBuf);
	// cout << "size:" << res.len << endl;
}

//server.c
static int port = 8000;

int main(int argc, char **argv)
{
	int server;
	int bind_state = 0 , listen_state = 0 ;
	socklen_t server_len, client_len;	// 设置服务队长
	struct sockaddr_in serveraddr, clientaddr;	
	server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);	//IP?
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);		// 绑定任意IP listen
	serveraddr.sin_port = htons(port);		// 设置listen 端口
	server_len = sizeof(serveraddr);
	client_len = sizeof(clientaddr);

	bind_state = bind(server, (struct sockaddr *)&serveraddr, server_len);
	if (bind_state == -1)
	{
		puts("Bind Error!") ;
		exit(1);
	}
	listen_state = listen(server, 5);	// backlog -- 已连接队列中等待accept()取走的最大连接数
	if (listen_state == -1)
	{
		puts("Listen Error!") ;
		exit(1);
	}
	// thread pool初始化
	pool_init(100);	// thread_pool中最多可允许的活动thread数

	puts("server running Port:8000");
	while (1)
	{
		// accept()会在没有客户端链接时阻塞  返回一个新的套接字描述符
		// server包含的是服务器的ip和port信息
		// client作为新的描述符,包含客户端的ip和port信息,当然也继承server中的服务器ip和port
		int client = accept(server, (struct sockaddr *)NULL, &client_len);
		if (client == -1)		// 错误处理
		{
			puts("Connect Error!");
			exit(1);
		}
		handle_clnt(&client);
	}
	
	// 程序结束 释放线程池
	pool_destroy();
	return 0;
}

void *handle_clnt(void *sockConn)
{
	int *sock = (int *)sockConn;	// 包含客户端和服务器信息的套接字
	char recvBuf[1024];		// 缓冲区 用于存放recv()收到的数据
	
	// 接收数据	需要增加错误处理 和 大于1MB的处理
	recv(*sock, recvBuf, 1024, 0);
	
	// 从缓冲区中解析请求
	char *req = get_request(recvBuf);
	char *ptl = get_protocol(recvBuf);
	printf("request: %s\n" , req);
	if (req != NULL)
	{
		answer(req, ptl, *sock);
	}
	return NULL;
}

//threadpool.c
void pool_init(int max_thread_num)
{
    pool = (CThread_pool *)malloc(sizeof(CThread_pool));
	// 互斥锁的初始化	以动态方式创建互斥锁
	// int pthread_mutex_init(pthread_mutex_t *restrict_mutex , const pthread_mutexattr_t *restrict_addr)
    pthread_mutex_init(&(pool->queue_lock), NULL);	//默认为快速互斥锁
    
	// 初始化条件变量
	// cond_attr 条件变量被同一进程内各个线程使用
	// 只有条件变量未被使用时才能重新初始化或释放
	pthread_cond_init(&(pool->queue_ready), NULL);

    pool->queue_head = NULL;
	
	// 最大活动线程数由main传入的参数决定
    pool->max_thread_num = max_thread_num;
    
	// 当前等待队列(缓冲区)的task数为0
	pool->cur_queue_size = 0;
	// destroy 时才设为1
    pool->shutdown = 0;
	// 链表结构存放线程
    pool->threadid = (pthread_t *)malloc(max_thread_num * sizeof(pthread_t));
    int i = 0;
    for (i = 0; i < max_thread_num; i++)
    {
		// 创建线程
        pthread_create(&(pool->threadid[i]), NULL, thread_routine, NULL);
    }
}

/*向线程池中加入任务*/
int pool_add_worker(void *(*process)(void *arg), void *arg)
{
    /*构造一个新任务*/
    CThread_worker *newworker = (CThread_worker *)malloc(sizeof(CThread_worker));
    newworker->process = process;
    newworker->arg = arg;
    newworker->next = NULL; /*别忘置空*/

    pthread_mutex_lock(&(pool->queue_lock));
    /*将任务加入到等待队列中*/
    CThread_worker *member = pool->queue_head;
    if (member != NULL)
    {
        while (member->next != NULL)
            member = member->next;
        member->next = newworker;
    }
    else
    {
        pool->queue_head = newworker;
    }

    assert(pool->queue_head != NULL);

    pool->cur_queue_size++;
    pthread_mutex_unlock(&(pool->queue_lock));
    
    //若所有线程busy,则无意义
	pthread_cond_signal(&(pool->queue_ready));
    return 0;
}

void *thread_routine(void *arg)
{
    while (1)
    {
        pthread_mutex_lock(&(pool->queue_lock));
        /*如果等待队列为0并且不销毁线程池,则处于阻塞状态; 注意
        pthread_cond_wait是一个原子操作,等待前会解锁,唤醒后会加锁*/
        while (pool->cur_queue_size == 0 && !pool->shutdown)
        {
            //printf("thread 0x%x is waiting\n", pthread_self());
            pthread_cond_wait(&(pool->queue_ready), &(pool->queue_lock));
        }

        if (pool->shutdown)
        {
            /*遇到break,continue,return等跳转语句,需要先解锁*/
            pthread_mutex_unlock(&(pool->queue_lock));
            //printf("thread 0x%x will exit\n", pthread_self());
            pthread_exit(NULL);
        }

        //printf("thread 0x%x is starting to work\n", pthread_self());

        assert(pool->cur_queue_size != 0);
        assert(pool->queue_head != NULL);

        /*等待队列长度减去1,并取出链表中的头元素*/
        pool->cur_queue_size--;
        CThread_worker *worker = pool->queue_head;
        pool->queue_head = worker->next;
        pthread_mutex_unlock(&(pool->queue_lock));

        /*调用回调函数,执行任务*/
        (*(worker->process))(worker->arg);
        free(worker);
        worker = NULL;
    }
    //pthread_exit(NULL);
}

/*不允许创建新线程,待所有线程资源释放后销毁线程池*/
int pool_destroy()
{
    if (pool->shutdown)
        return -1; /*防止两次调用*/
    pool->shutdown = 1;

    /*唤醒所有等待线程*/
    pthread_cond_broadcast(&(pool->queue_ready));

    /*阻塞等待线程全部退出*/
    int i;
    for (i = 0; i < pool->max_thread_num; i++)
        pthread_join(pool->threadid[i], NULL);
    free(pool->threadid);

    /*销毁等待队列*/
    CThread_worker *head = NULL;
    while (pool->queue_head != NULL)
    {
        head = pool->queue_head;
        pool->queue_head = pool->queue_head->next;
        free(head);
    }
    /*条件变量和互斥量*/
    pthread_mutex_destroy(&(pool->queue_lock));
    pthread_cond_destroy(&(pool->queue_ready));

    free(pool);
    /*销毁后指针置空*/
    pool = NULL;
    return 0;
}


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值