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;
}