此篇为《linux高性能服务器编程》第15章线程池实例的学习笔记。
半同步/半反应堆线程池模型与进程池模型类似,不过需要考虑使用请求队列,互斥锁来同步线程之间的工作。
首先,locker.h文件实现了NPTL线程的三种同步机制的封装。将其封装成对象,便于管理。
#pragma once
/*
此文件是对三种线程同步机制的封装
*/
#ifndef LOCKER_H
#define LOCKER_H
#include
#include
#include
//封装信号量的类
class sem {
public:
//创建一个信号量
sem() {
if (sem_init(&m_sem, 0, 0) != 0) {
throw std::exception();
}
}
//销毁信号量
~sem()
{
sem_destroy(&m_sem);
}
//等待信号量
bool wait() {
return sem_wait(&m_sem) == 0;
}
//增加信号量
bool post() {
return sem_post(&m_sem) == 0;
}
private:
//信号量
sem_t m_sem;
};
//封装互斥锁
class locker {
public:
//创建一个互斥锁
locker() {
if (pthread_mutex_init(&m_mutex, NULL) != 0) {
throw std::exception();
}
}
~locker() {
pthread_mutex_destroy(&m_mutex);
}
//上锁
bool lock() {
return pthread_mutex_lock(&m_mutex) == 0;
}
bool unlock() {
return pthread_mutex_unlock(&m_mutex) == 0;
}
private:
pthread_mutex_t m_mutex;
};
//条件变量
class cond {
public:
cond() {
if (pthread_mutex_init(&m_mutex, NULL) != 0) {
throw std::exception();
}
//如果条件变量申请出现问题,释放已经申请的互斥锁资源
if (pthread_cond_init(&m_cond, NULL) != 0) {
pthread_mutex_destroy(&m_mutex);
throw std::exception();
}
}
~cond() {
pthread_mutex_destroy(&m_mutex);
pthread_cond_destroy(&m_cond);
}
//等待条件变量
bool wait() {
int ret = 0;
pthread_mutex_lock(&m_mutex);
ret = pthread_cond_wait(&m_cond, &m_mutex);
pthread_mutex_unlock(&m_mutex);
return ret == 0;
}
//唤醒等待条件变量的线程
bool signal() {
return pthread_cond_signal(&m_cond) == 0;
}
private:
pthread_cond_t m_cond;
pthread_mutex_t m_mutex;
};
#endif // !LOCKER_H
threadpool.h声明了线程池的成员,定义了各个成员函数的逻辑。
#pragma once
#ifndef THREADPOOL_H
#define THREADPOOL_H
#include
#include
#include
#include
#include
#include
#include
#include "locker.h"
//线程池类
template<typename T>
class threadpool {
public:
threadpool(int thread_number = 8, int max_requests = 10000);
~threadpool();
//向请求队列中添加任务
bool append(T* request);
static void* worker(void *arg);
void run();
private:
int m_thread_number;
int m_max_requests;//请求队列中允许的最大请求数
pthread_t* m_threaeds;//线程池数组
std::list<T*>m_workqueue;//请求队列
locker m_queuelocker;//保护请求队列的互斥锁
sem m_queuestat;//是否有任务需要处理
bool m_stop; //是否结束线程
};
template<typename T>
threadpool<T>::threadpool(int thread_number = 8, int max_requests = 10000) :m_thread_number(thread_number), m_max_requests(max_requests), m_stop(false), m_threads(NULL) {
if ((thread_number <= 0 || (max_requests <= 0))) {
throw std::exception();
}
m_threads = new pthread_t[m_thread_number];
if (!m_threads) {
throw std::exception();
}
for (int i = 0; i < m_thread_number; i++) {
printf("create the %dth thread\n", i);
/*
worker是类的静态成员函数,为了调用动态成员,有两种方法
1.通过类的静态对象调用
2.将类的对象作为参数传递给该静态函数
所以这里使用第二种方法,将this指针传递给worker.
*/
if (pthread_create(m_threads + i, NULL, worker, this) != 0) {
delete[]m_threaeds;
throw std::exception();
}
/*
pthread_detach:pthread_detach将指定的线程指明为分离态,在线程执行完毕之后立即退出并释放所有资源,是非阻塞的
*/
if (pthread_detach(m_threads[i])) {
delete[]m_threads;
throw std::exception();
}
}
}
template<typename T>
threadpool<T>::~threadpool() {
delete[]m_threaeds;
m_stop = true;
}
template<typename T>
bool threadpool<T>::append(T* requests) {
//操作请求队列,一定需要加锁,请求队列属于临界资源
m_queuelocker.lock();
if (m_workqueue.size() > m_max_requests) {
m_queuelocker.unlock();
return false;
}
m_workqueue.push_back(request);
m_queuelocker.unlock();
//表明有任务需要处理
m_queuestat.post();
return true;
}
template<typename T>
void* threadpool<T>::worker(void *arg) {
//取出指针实例
threadpool* pool = (threadpool* arg);
pool->run();
return pool;
}
//工作线程的主逻辑
template<typename T>
void threadpool<T>::run() {
while (!m_stop) {
m_queuestat.wait();
m_queuelocker.lock();
if(m_workqueue.empty(){
m_queuelocker.unlock();
continue;
}
T* request=m_workqueue.front();
m_workqueue.pop_front();
m_queuelocker.unlock();
if(!request){
continue;
}
//取出请求之后,由工作线程完成process()处理请求的逻辑计算
request->process();
}
}
#endif // !THREADPOOL_H
代码中比较重要的就是pthread_create传入类静态成员函数的解决方法,因为静态成员函数无法调用动态成员,所以有两种方法解决这个问题:
1.通过类的静态对象来调用,比如单例模式中,维护了一个全局唯一的单例指针实例,可以通过这个实例来调用。
2.像代码中的一样,将类的对象指针this作为参数传给该静态函数,即可通过此指针调用动态成员。
在代码中,worker函数使用了传入的this指针,获取了类的实例,从而调用动态成员。
除此之外,run()函数则是工作线程的主要逻辑,为模板参数类的process()提供计算力。
http_conn类作为传入线程池的模板参数T,需要实现process()函数,以完成对http的解析。
#pragma once
#ifndef HTTPCONNECTION_H
#define HTTPCONNECTION_H
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "locker.h"
class http_conn {
public:
//文件名最大长度
static const int FILENAME_LEN = 200;
//读缓冲区大小
static const int READ_BUFFER_SIZE = 2048;
//写缓冲区大小
static const int WRITE_BUFFER_SIZE = 1024;
//HTTP的请求方法
enum METHOD{
GET = 0, POST = 1, HEAD = 2, PUT = 3, DELETE = 4, TRACE = 5, OPTIONS = 6, CONNECT = 7, PATCH = 8
};
enum CHECK_STATE {
CHECK_STATE_REQUESTLINE = 0,
CHECK_STATE_HEADER = 1,
CHECK_STATE_CONTENT = 2
};
enum HTTP_CODE {
NO_REQUEST, GET_REQUEST, BAD_REQUEST,
NO_RESOURCE,FORBIDDEN_REQUEST,FILE_REQUEST,
INTERNAL_ERROR,CLOSED_CONNECTION
};
enum LINE_STATUS {
LINE_OK = 0, LINE_BAD = 1, LINE_OPEN = 2
};
public:
http_conn();
~http_conn();
public:
//初始化新接受的连接
void init(int sockfd, const sockaddr_in& addr);
//关闭连接
void close_conn(bool read_close = true);
//处理客户请求
void process();
//非阻塞读入操作
bool read();
//非阻塞写操作
bool write();
private:
//初始化连接
void init();
HTTP_CODE process_read();
//填充http应答
bool process_write(HTTP_CODE ret);
//下面的一组函数由process_write函数调用,分析http请求
//解析HTTP请求行,获得请求方法,目标URL,HTTP版本号
HTTP_CODE parse_request_line(char *text);
//解析http请求的一个头部信息
HTTP_CODE parse_headers(char *text);
//我们没有真正解析HTTP请求的请求体,而是判断他是否被完整读入
HTTP_CODE parse_content(char *text);
//do_request进行内存映射
HTTP_CODE do_request();
char *get_line() {
return m_read_buf + m_start_line;
}
//从状态机,从buffer中解析一行http
//如果获得完整的行,返回LINE_OK,并从m_read_idx到m_check_idx即为请求行的内容
LINE_STATUS parse_line();
//下面这一组函数被process_write函数用以填充http应答
void unmap();
//c++省略号实现可变参数函数,可通过使用va_list,va_arg等宏定义获取参数,定义在头文件中
bool add_response(const char *format, ...);
bool add_content(const char* content);
bool add_status_line(int status, const char* title);
bool add_content_length(int content_length);
bool add_headers(int content_length);
bool add_linger();
bool add_blank_line();
public:
static int m_epollfd;
static int m_user_count;//统计用户数量
private:
//该http连接的socket
int m_sockfd;
//对法的socket地址
sockaddr_in m_address;
//读缓冲区
char m_read_buf[READ_BUFFER_SIZE];
//读下标地址
int m_read_idx;
//当前正在分析的行的起始位置
int m_check_idx;
//当前正在解析的行的起始地址
int m_start_line;
//写缓冲区
char m_write_buf[WRITE_BUFFER_SIZE];
//写缓冲区下标
int m_write_idx;
//主状态机当前处于的状态,采用有限状态机的思想
CHECK_STATE m_check_state;
//请求方法
METHOD m_method;
//客户请求的目标文件的完整路径,其内容等于doc_root+m_url,doc_root为网站的根目录
char m_real_file[FILENAME_LEN];
//客户请求的目标文件名
char *m_url;
//http协议版本号,支持HTTP/1.1
char *m_version;
//主机名
char *m_host;
//HTTP请求消息体长度
int m_content_length;
//http请求是否要求保持连接
bool m_linger;
//客户请求的目标文件被mmap到内存中的起始位置
char *m_file_address;
//目标文件的状态。判断目标文件是否存在,是否可读可写,并获取目标文件的大小信息。
struct stat m_file_stat;
//我们采用writev函数执行写操作,所以定义下面两个成员,其中m_iv_count表示被写内存块的数量
struct iovec m_iv[2];//将http分为两个连续存储区,一个存储请求头和请求行,一个存储请求体
int m_iv_count;
};
#endif
函数众多,但其实通过分类与梳理,就会发现,private的函数都是为public函数服务的,而public函数就只有5个
void init(int sockfd, const sockaddr_in& addr);
init()函数初始化了http_conn的连接socket,以及客户端socket地址addr,再通过调用private的init()函数完成其他参数的初始化,如清空缓冲区,初始化m_epollfd等。
void close_conn(bool read_close = true);
关闭连接,close掉连接socket并释放资源
void process();
process()函数最为重要,看一下函数主体
//http_conn的工作函数,由线程池中的工作线程调用,这是处理http请求的入口函数
void http_conn::process() {
HTTP_CODE read_ret = process_read();
if (read_ret == NO_REQUEST) {
//NO_REQUEST表示没有数据,提醒读入事件
modfd(m_epollfd, m_sockfd, EPOLLIN);
return;
}
//根据process_read的请求结果,返回特定的信息,如404,403
bool write_ret = process_write(read_ret);
if (!write_ret) {
close_conn();
}
//成功则说明缓冲区有数据可写,提醒写事件
modfd(m_epollfd, m_sockfd, EPOLLOUT);
}
我们发现,process()函数调用process_read()函数与process_write()函数,process_read()函数负责读取和解析服务器读取客户发送过来的请求,他调用了一系列解析http的状态机函数,如下:
//解析HTTP请求行,获得请求方法,目标URL,HTTP版本号
HTTP_CODE parse_request_line(char *text);
//解析http请求的一个头部信息
HTTP_CODE parse_headers(char *text);
//我们没有真正解析HTTP请求的请求体,而是判断他是否被完整读入
HTTP_CODE parse_content(char *text);
//do_request进行内存映射
HTTP_CODE do_request();
char *get_line() {
return m_read_buf + m_start_line;
}
//从状态机,从buffer中解析一行http
//如果获得完整的行,返回LINE_OK,并从m_read_idx到m_check_idx即为请求行的内容
LINE_STATUS parse_line();
这些函数使用有限状态机的思想,读取并解析http请求行,请求头以及请求体,并返回请求结果,我们只需根据请求结果进行process_write(),向客户端反馈即可。
process_write()函数负责将结果反馈给客户端,有以下函数的调用
//c++省略号实现可变参数函数,可通过使用va_list,va_arg等宏定义获取参数,定义在头文件中
bool add_response(const char *format, ...);
bool add_content(const char* content);
bool add_status_line(int status, const char* title);
bool add_content_length(int content_length);
bool add_headers(int content_length);
bool add_linger();
bool add_blank_line();
函数功能即为名字描述,向结果缓冲区中加入一系列字符串即可。
bool read();
read()函数即时从当前有事件的epollfd上循环读取数据,由process_read函数调用
//循环读取客户数据,直到无数据可读或者对方关闭连接
bool http_conn::read() {
if (m_read_idx >= READ_BUFFER_SIZE) {
return false;
}
int bytes_read = 0;
while (true) {
bytes_read = recv(m_sockfd, m_read_buf + m_read_idx, READ_BUFFER_SIZE - m_read_idx, 0);
if (bytes_read == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
continue;
}
return false;
}
if (bytes_read == 0) {
//有可能关闭连接,也有可能缓冲区冲爆了
return false;
}
m_read_idx += bytes_read;
if (m_read_idx >= READ_BUFFER_SIZE)break;
}
return true;
}
bool write()
write()函数则是向客户端socket写入反馈信息,由process_write函数调用
//写HTTP响应
bool http_conn::write() {
int temp = 0;
int bytes_have_send = 0;
int bytes_to_send = m_write_idx;
if (bytes_to_send == 0) {
//没东西可写,需要让m_sockfd去读
modfd(m_epollfd, m_sockfd, EPOLLIN);
init();
return true;
}
while (1) {
//writev以顺序iov[0]、iov[1]至iov[iovcnt-1]从各缓冲区中聚集输出数据到fd,减少了read和write的系统调用
//readv则相反,将fd的内容一个一个填满iov[0],iov[1]...iov[count-1]
temp = writev(m_sockfd, m_iv, m_iv_count);
if (temp <= -1) {
/*
如果TCP写缓冲区没有空间,则等待下一轮EPOLLOUT事件。虽然在此期间,服务器无法立即接受到同一个客户的下一个请求,
但这样可以保证连接的完整性
*/
if (errno == EAGAIN || errno == EWOULDBLOCK) {
modfd(m_epollfd, m_sockfd, EPOLLOUT);
return true;
}
unmap();
return false;
}
bytes_to_send -= temp;
bytes_have_send += temp;
if (bytes_to_send <= bytes_have_send) {
//发送HTTP响应成功,根据HTTP请求中的Connection字段决定是否关闭连接
unmap();
if (m_linger) {
init();
modfd(m_epollfd, m_sockfd, EPOLLIN);
return true;
}
else {
modfd(m_epollfd, m_sockfd, EPOLLIN);
return false;
}
}
}
}
/*
此文件为对http_conn.h头文件函数的实现
*/
#include "http_conn.h"
//定义HTTP响应的一些状态信息
const char* ok_200_title = "OK";
const char* error_400_title = "Bad Request";
const char* error_400_form = "Your request has bad syntax or is inherently impossible to satisfy.\n";
const char* error_403_title = "Forbidden";
const char* error_403_form = "You don't have permission to get file from this sever.\n";
const char* error_404_title = "Not Found";
const char* error_404_form = "The request file was not found on this server.\n";
const char* error_500_title = "Internal Error";
const char* error_500_form = "There was an unusual problem serving the requested file.\n";
//网站根目录
const char* doc_root = "/var/www/html";//根据自己情况进行修改
//设置文件描述符为非阻塞
int setnonblocking(int fd) {
int old_option = fcntl(fd, F_GETFL);
int new_option = old_option | O_NONBLOCK;
fcntl(fd, F_SETFL, new_option);
return old_option;
}
//one_shot为EPOLLONESHOT事件
void add_read_fd(int epollfd, int fd,bool one_shot) {
epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN | EPOLLET | EPOLLHUP;
if (one_shot) {
event.events |= EPOLLONESHOT;
}
epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
setnonblocking(fd);
}
void add_write_fd(int epollfd, int fd, bool one_shot) {
epoll_event event;
event.data.fd = fd;
event.events = EPOLLOUT | EPOLLET | EPOLLHUP;
if (one_shot) {
event.events |= EPOLLONESHOT;
}
epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
setnonblocking(fd);
}
void removefd(int epollfd, int fd) {
epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, 0);
close(fd);
}
//修改事件
void modfd(int epollfd, int fd, int ev) {
epoll_event event;
event.data.fd = fd;
event.events = ev | EPOLLET | EPOLLONESHOT | EPOLLHUP;
epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &event);
}
int http_conn::m_user_count = 0;
int http_conn::m_epollfd = -1;
void http_conn::close_conn(bool real_close) {
if (real_close && (m_sockfd != -1)) {
removefd(m_epollfd, m_sockfd);
m_sockfd = -1;
m_user_count--;//总连接数-1
}
}
//初始化,addr为client_address,即连接客户端地址,sockfd为连接fd
void http_conn::init(int sockfd, const sockaddr_in& addr) {
m_sockfd = sockfd;
m_address = addr;
//如下两行使用SO_REUSEADDR避免TIME_WAIT状态,仅作为调试时使用,实际使用应当删除
int reuse = 1;
setsockopt(m_sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
add_read_fd(m_epollfd, m_sockfd, true);
m_user_count++;
init();
}
void http_conn::init() {
m_check_state = CHECK_STATE_REQUESTLINE;
//是否保持连接
m_linger = false;
//方法,只支持GET
m_method = GET;
//初始化url
m_url = 0;
//初始化版本
m_version = 0;
m_content_length = 0;
m_host = 0;
m_start_line = 0;
m_check_idx = 0;
m_read_idx = 0;
m_write_idx = 0;
memset(m_read_buf, '\0', sizeof(m_read_buf));
memset(m_write_buf, '\0', sizeof(m_write_buf));
memset(m_real_file, '\0', sizeof(m_real_file));
}
//从状态机,从buffer中解析一行http
//如果获得完整的行,返回LINE_OK,并从m_read_idx到m_check_idx即为请求行的内容
//否则返回LINE_OPEN:表示一行未读取完;LINE_BAD:请求行错误
http_conn::LINE_STATUS http_conn::parse_line() {
char temp;
for (; m_check_idx < m_read_idx; m_check_idx++) {
temp = m_read_buf[m_check_idx];
//http请求行结束的标志是一个回车换行符\r\n
if (temp == '\r') {
if (m_check_idx + 1 == m_read_idx) {
//一行请求没有读完,LINE_OPEN表示请求继续读
return LINE_OPEN;
}
else if (m_read_buf[m_check_idx + 1] == '\n') {
//说明读到了一个完整的行
m_read_buf[m_check_idx++] = '\0';
m_read_buf[m_check_idx++] = '\0';
return LINE_OK;
}
else return LINE_BAD;
}
else if (temp == '\n') {
if (m_check_idx > 1 && m_read_buf[m_check_idx - 1] == '\r') {
m_read_buf[m_check_idx - 1] = '\0';
m_read_buf[m_check_idx++] = '\0';
return LINE_OK;
}
else return LINE_BAD;
}
}
return LINE_OPEN;
}
//循环读取客户数据,直到无数据可读或者对方关闭连接
bool http_conn::read() {
if (m_read_idx >= READ_BUFFER_SIZE) {
return false;
}
int bytes_read = 0;
while (true) {
bytes_read = recv(m_sockfd, m_read_buf + m_read_idx, READ_BUFFER_SIZE - m_read_idx, 0);
if (bytes_read == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
continue;
}
return false;
}
if (bytes_read == 0) {
//有可能关闭连接,也有可能缓冲区冲爆了
return false;
}
m_read_idx += bytes_read;
if (m_read_idx >= READ_BUFFER_SIZE)break;
}
return true;
}
//解析HTTP请求行,获得请求方法,目标URL,HTTP版本号
http_conn::HTTP_CODE http_conn::parse_request_line(char* text) {
m_url = strpbrk(text, "\t");
if (!m_url) {
return BAD_REQUEST;
}
*m_url++ = '\0';
char* method = text;
if (strcasecmp(method, "GET") == 0) {
m_method = GET;
}
else {
return BAD_REQUEST;
}
m_url += strspn(m_url, "\t");
m_version = strpbrk(m_url, "\t");
if (!m_version) {
return BAD_REQUEST;
}
*m_version++ = '\0';
m_version += strspn(m_version, "\t");
if (strcasecmp(m_version, "HTTP/1.1") != 0) {
return BAD_REQUEST;
}
if (strncasecmp(m_url, "http://", 7) == 0) {
m_url += 7;
m_url = strchr(m_url, '/');
}
if (!m_url || m_url[0] != '/') {
return BAD_REQUEST;
}
m_check_state = CHECK_STATE_HEADER;
return NO_REQUEST;
}
//解析http请求的一个头部信息
//请求头
http_conn::HTTP_CODE http_conn::parse_headers(char* text) {
//遇到空行,表示头部信息处理完毕,接下来是正文
if (text[0] == '\0') {
//如果http请求有消息体,则还需要读取m_content_length字节的消息体
if (m_content_length) {
m_check_state = CHECK_STATE_CONTENT;
return NO_REQUEST;
}
//否则表示没有请求体,http请求处理完成
return GET_REQUEST;
}
//处理Connection头部字段
else if (strncasecmp(text, "Connention:", 11) == 0) {
text += 11;
text += strspn(text, "\t");
if (strcasecmp(text, "keep-alive") == 0) {
m_linger = true;
}
}
//处理Content-Length头部字段
else if (strncasecmp(text, "Content-Length:", strlen("Content-Length:") == 0)) {
text += strlen("Content-Length:");
text += strspn(text, "\t");
m_content_length = atol(text);
}
//处理Host头部字段
else if (strncasecmp(text, "Host:", strlen("Host:")) == 0) {
text += strlen("Host:");
text += strspn(text, "\t");
m_host = text;
}
else {
printf("oops!The server don't know the header!\n");
}
return NO_REQUEST;
}
//我们没有真正解析HTTP请求的请求体,而是判断他是否被完整读入
http_conn::HTTP_CODE http_conn::parse_content(char *text) {
if (m_read_idx >= (m_content_length + m_check_idx)) {
text[m_content_length] = '\0';
return GET_REQUEST;
}
return NO_REQUEST;
}
//主状态机,根据m_check_stat的状态进行转移
/*
CHECK_STATE_CONTENT:表示请求行解析成功,准备解析请求体
CHECK_STATE_REQUESTLINE:表示准备解析请求行,LINE_OK状态只表示行形式正确
CHECK_STATE_HEADER:表示准备解析请求头,必须是请求行解析完成之后
*/
http_conn::HTTP_CODE http_conn::process_read() {
LINE_STATUS line_status = LINE_OK;
HTTP_CODE ret = NO_REQUEST;
char* text = 0;
while (((m_check_state == CHECK_STATE_CONTENT) && (line_status == LINE_OK)) || ((line_status == parse_line()) == LINE_OK)) {
text = get_line();
m_start_line = m_check_idx;
printf("got 1 http line: %s\n", text);
//根据m_check_state进行转移
switch (m_check_state) {
//如果准备解析请求行,调用parse_request_line函数
case CHECK_STATE_REQUESTLINE:
{
ret = parse_request_line(text);
if (ret == BAD_REQUEST) {
return BAD_REQUEST;
}
break;
}
//如果准备解析请求头,调用parse_headers函数
case CHECK_STATE_HEADER: {
ret = parse_headers(text);
if (ret == BAD_REQUEST) {
return BAD_REQUEST;
}
else if (ret == GET_REQUEST) {
//解析完请求头,如果没有请求体,则会返回GET_REQUEST,此时表示http请求已经解析完成,do_request执行
return do_request();
}
break;
}
//如果准备解析请求体,调用parse_content函数
case CHECK_STATE_CONTENT: {
ret = parse_content(text);
if (ret == GET_REQUEST) {
//解析完请求体,返回GET_REQUEST,do_request执行
return do_request();
}
//恢复line_status状态
line_status = LINE_OPEN;
break;
}
default: {
return INTERNAL_ERROR;
}
}
}
return NO_REQUEST;
}
/*
do_request:当得到一个完整的,正确的http请求时,我们需要分析目标文件的属性。如果目标文件存在,对所有用户可读,且不是目录,则使用
mmap将其映射到内存地址m_file_address处,并告诉调用者获取文件成功。
*/
http_conn::HTTP_CODE http_conn::do_request() {
strcpy(m_real_file, doc_root);
int len = strlen(doc_root);
strncpy(m_real_file + len, m_url, FILENAME_LEN - len + 1);
if (stat(m_real_file, &m_file_stat) < 0) {
return NO_REQUEST;
}
if (!(m_file_stat.st_mode & S_IROTH)) {
return FORBIDDEN_REQUEST;
}
if (S_ISDIR(m_file_stat.st_mode)) {
return BAD_REQUEST;
}
int fd=open(m_real_file,O_RDONLY);
m_file_address = (char*)mmap(0, m_file_stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
close(fd);
return FILE_REQUEST;
}
//收回共享内存
void http_conn::unmap() {
if (m_file_address) {
munmap(m_file_address, m_file_stat.st_size);
m_file_address = 0;
}
}
//写HTTP响应
bool http_conn::write() {
int temp = 0;
int bytes_have_send = 0;
int bytes_to_send = m_write_idx;
if (bytes_to_send == 0) {
//没东西可写,需要让m_sockfd去读
modfd(m_epollfd, m_sockfd, EPOLLIN);
init();
return true;
}
while (1) {
//writev以顺序iov[0]、iov[1]至iov[iovcnt-1]从各缓冲区中聚集输出数据到fd,减少了read和write的系统调用
//readv则相反,将fd的内容一个一个填满iov[0],iov[1]...iov[count-1]
temp = writev(m_sockfd, m_iv, m_iv_count);
if (temp <= -1) {
/*
如果TCP写缓冲区没有空间,则等待下一轮EPOLLOUT事件。虽然在此期间,服务器无法立即接受到同一个客户的下一个请求,
但这样可以保证连接的完整性
*/
if (errno == EAGAIN || errno == EWOULDBLOCK) {
modfd(m_epollfd, m_sockfd, EPOLLOUT);
return true;
}
unmap();
return false;
}
bytes_to_send -= temp;
bytes_have_send += temp;
if (bytes_to_send <= bytes_have_send) {
//发送HTTP响应成功,根据HTTP请求中的Connection字段决定是否关闭连接
unmap();
if (m_linger) {
init();
modfd(m_epollfd, m_sockfd, EPOLLIN);
return true;
}
else {
modfd(m_epollfd, m_sockfd, EPOLLIN);
return false;
}
}
}
}
//向写缓冲区中写入待发送的数据,由add_stauts_line,add_content_length等函数调用
//将需要写的请求行,请求头等信息写入缓冲区
bool http_conn::add_response(const char* format, ...) {
if (m_write_idx >= WRITE_BUFFER_SIZE) {
return false;
}
va_list arg_list;//获取省略号的可变参数的内容
va_start(arg_list, format);
//vsnprintf:将格式化数据从可变参数列表写入大小缓冲区
int len = vsnprintf(m_write_buf + m_write_idx, WRITE_BUFFER_SIZE - 1 - m_write_idx, format, arg_list);
if (len >= (WRITE_BUFFER_SIZE - 1 - m_write_idx)) {
return false;
}
m_write_idx += len;
va_end(arg_list);
return true;
}
bool http_conn::add_status_line(int status, const char* title) {
return add_response("%s %d %s\r\n", "HTTP/1.1", status, title);
}
bool http_conn::add_headers(int content_len) {
add_content_length(content_len);
add_linger();
add_blank_line();
}
bool http_conn::add_content_length(int content_len) {
return add_response("Content-Length: %d\r\n", content_len);
}
bool http_conn::add_linger() {
return add_response("Connecetion %s\r\n", (m_linger) ? "keep-alive" : "close" );
}
bool http_conn::add_blank_line() {
return add_response("%s", "\r\n");
}
bool http_conn::add_content(const char * content) {
return add_response("%s", content);
}
//根据服务器处理http请求的结果,决定返回客户端的内容
bool http_conn::process_write(HTTP_CODE ret) {
switch (ret) {
//500服务器内部错误
case INTERNAL_ERROR: {
add_status_line(500, error_500_title);
add_headers(strlen(error_500_form));
if (!add_content(error_500_form)) {
return false;
}
break;
}
//400请求错误
case BAD_REQUEST: {
add_status_line(400, error_400_title);
add_headers(strlen(error_400_form));
if (!add_content(error_400_form)) {
return false;
}
break;
}
//404资源确实,没有响应
case NO_REQUEST: {
add_status_line(404, error_404_title);
add_headers(strlen(error_404_form));
if (!add_content(error_404_form)) {
return false;
}
break;
}
//403禁止访问
case FORBIDDEN_REQUEST: {
add_status_line(403, error_403_title);
add_headers(strlen(error_400_form));
if (!add_content(error_403_form)) {
return false;
}
break;
}
//请求文件成功
case FILE_REQUEST: {
add_status_line(200, ok_200_title);
if (m_file_stat.st_size != 0) {
add_headers(m_file_stat.st_size);
//HTTP响应信息位于m_iv[0],HTTP请求的文件内容放入m_iv[1]
m_iv[0].iov_base = m_write_buf;
m_iv[0].iov_len = m_write_idx;
m_iv[1].iov_base = m_file_address;
m_iv[1].iov_len = m_file_stat.st_size;
m_iv_count = 2;
return true;
}
else {
const char* ok_string = "";
add_headers(strlen(ok_string));
if (!add_content(ok_string)) {
return false;
}
}
}
default: {
return true;
}
}
m_iv[0].iov_base = m_write_buf;
m_iv[0].iov_len = m_write_idx;
m_iv_count = 1;
return true;
}
//http_conn的工作函数,由线程池中的工作线程调用,这是处理http请求的入口函数
void http_conn::process() {
HTTP_CODE read_ret = process_read();
if (read_ret == NO_REQUEST) {
//NO_REQUEST表示没有数据,提醒读入事件
modfd(m_epollfd, m_sockfd, EPOLLIN);
return;
}
//根据process_read的请求结果,返回特定的信息,如404,403
bool write_ret = process_write(read_ret);
if (!write_ret) {
close_conn();
}
//成功则说明缓冲区有数据可写,提醒写事件
modfd(m_epollfd, m_sockfd, EPOLLOUT);
}
在main.cpp中,主线程使用epoll进行io复用操作,将一个个连接任务分发给线程池中的工作线程处理(半同步/半反应堆模型),工作模式和半同步/半异步进程池大致相同,不在赘述。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "locker.h"
#include "http_conn.h"
#include "threadpool.h"
#define MAX_FD 65536
#define MAX_EVENT_NUMBER 10000
extern int add_read_fd(int epollfd, int fd, bool one_shot);
extern int add_write_fd(int epollfd, int fd, bool one_shot);
//处理信号
void addsig(int sig, void(handler)(int), bool restart = true) {
//用sigaction 捕捉信号
struct sigaction sa;
memset(&sa, '\0', sizeof(sa));
sa.sa_handler = handler;//这是个宏定义
if (restart) {
sa.sa_flags |= SA_RESTART;
}
sigfillset(&sa.sa_mask);
assert(sigaction(sig, &sa, NULL) != -1);
}
//向connfd打印错误信息
void show_error(int connfd, const char* info) {
printf("%s", info);
send(connfd, info, strlen(info), 0);
close(connfd);
}
signed main(int argc, char** argv) {
if (argc <= 2) {
printf("usage: %s ip_address port_number\n", basename(argv[0]));
return 1;
}
const char* ip = argv[1];
int port = atoi(argv[2]);
//忽略SIGPIPE信号
addsig(SIGPIPE, SIG_IGN);
//创建线程池
threadpool<http_conn>* pool = NULL;
try {
pool = new threadpool<http_conn>;
}
catch (...) {
return 1;
}
//预先为每个可能的客户分配一个http_conn对象
http_conn* users = new http_conn[MAX_FD];
assert(users);
int user_count = 0;
//绑定socket
int listenfd = socket(PF_INET, SOCK_STREAM, 0);
assert(listenfd >= 0);
/*
socket中的SO_LINGER可以决定close(socketfd)之后的行为:
1.默认情况下close会立即关闭tcp连接,可能会导致四次挥手的close_wait1状态丢失,TIME_WAIT状态也会丢失
2.linger的两个参数,如果都为非0的情况下,则可以实现真正的四次挥手,linger结构体的第二个参数指定了延时关闭的时间。
所以如果实际应用记得将temp的第二个参数改过来,这里只是demo测试
*/
struct linger temp = { 1,0 };
setsockopt(listenfd, SOL_SOCKET, SO_LINGER, &temp, sizeof(temp));
//socket地址初始化
int ret = 0;
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_family = AF_INET;
inet_pton(AF_INET, ip, &address.sin_addr);
address.sin_port = htons(port);
ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address));
assert(ret >= 0);
ret = listen(listenfd, 5);
assert(ret >= 0);
epoll_event events[MAX_EVENT_NUMBER];
int epollfd = epoll_create(5);
assert(epollfd >= 0);
add_read_fd(epollfd, listenfd, false);
http_conn::m_epollfd = epollfd;
while (true) {
int number = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);
if ((number < 0) && errno != EINTR) {
printf("epoll_wait failure!\n");
break;
}
for (int i = 0; i < number; i++) {
int sockfd = events[i].data.fd;
//说明有一个新的连接
if (sockfd == listenfd) {
struct sockaddr_in client_address;
socklen_t client_addrlength = sizeof(client_address);
int connfd = accept(listenfd, (struct sockaddr*)&client_address, &client_addrlength);
if (connfd < 0) {
printf("accept failure! errno is %s\n.", errno);
continue;
}
//如果有MAX_FD个连接了,返回500错误表示服务器繁忙
if (http_conn::m_user_count >= MAX_FD) {
show_error(connfd, "Internal server busy");
continue;
}
//用文件描述符唯一索引
users[connfd].init(connfd, client_address);
}
//如果有异常,直接关闭客户连接
else if (events[i].events & (EPOLLHUP | EPOLLRDHUP | EPOLLERR)) {
users[sockfd].close_conn();
}
//读事件
else if (events[i].events & (EPOLLIN)) {
//根据读的结果,决定是将任务添加到线程池,还是关闭连接
if (users[sockfd].read()) {
pool->append(users + sockfd);
}
else {
users[sockfd].close_conn();
}
}
//写事件
else if (events[i].events & (EPOLLOUT)) {
if (users[sockfd].write()) {
//可以在今后添加写事件完成后的逻辑,现在暂时不需要
}
else {
users[sockfd].close_conn();
}
}
}
}
close(epollfd);
close(listenfd);
delete[] users;
delete pool;
return 0;
}
最后整理一下,整个半同步/半反应堆线程池逻辑流程如下: