半同步/半反应堆线程池实现简单web服务器

半同步/半反应堆线程池实现简单web服务器,解析http请求

此篇为《linux高性能服务器编程》第15章线程池实例的学习笔记。
半同步/半反应堆线程池模型与进程池模型类似,不过需要考虑使用请求队列,互斥锁来同步线程之间的工作。

locker.h

首先,locker.h文件实现了NPTL线程的三种同步机制的封装。将其封装成对象,便于管理。

#pragma once
/*
	此文件是对三种线程同步机制的封装
*/
#ifndef LOCKER_H 
#define LOCKER_H
#include <exception>
#include <pthread.h>
#include <semaphore.h>

//封装信号量的类 
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

threadpool.h声明了线程池的成员,定义了各个成员函数的逻辑。

#pragma once
#ifndef THREADPOOL_H
#define THREADPOOL_H
#include <list>
#include <vector>
#include <exception>
#include <pthread.h>
#include <unistd.h>
#include <cstdio>
#include <string.h>
#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.h:模板参数类,实现处理http的逻辑

http_conn类作为传入线程池的模板参数T,需要实现process()函数,以完成对http的解析。

#pragma once
#ifndef HTTPCONNECTION_H
#define HTTPCONNECTION_H
#include <unistd.h>
#include <cstdio>
#include <string.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <pthread.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <errno.h>
#include <stdarg.h>
#include <assert.h>
#include <netinet/in.h>
#include <sys/stat.h>
#include <sys/mman.h>
#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等宏定义获取参数,定义在<cstdarg>头文件中
	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个

  1. void init(int sockfd, const sockaddr_in& addr);
    

    init()函数初始化了http_conn的连接socket,以及客户端socket地址addr,再通过调用private的init()函数完成其他参数的初始化,如清空缓冲区,初始化m_epollfd等。

  2. void close_conn(bool read_close = true);
    

    关闭连接,close掉连接socket并释放资源

  3. 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等宏定义获取参数,定义在<cstdarg>头文件中
    	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();
    

    函数功能即为名字描述,向结果缓冲区中加入一系列字符串即可。

  4. 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;
    }
    
  5. 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.cpp,实现了http_conn声明的各种函数

/*
	此文件为对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 = "<html><body></body></html>";
				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,为主线程的逻辑

在main.cpp中,主线程使用epoll进行io复用操作,将一个个连接任务分发给线程池中的工作线程处理(半同步/半反应堆模型),工作模式和半同步/半异步进程池大致相同,不在赘述。

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/epoll.h>
#include <pthread.h>

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

总结

最后整理一下,整个半同步/半反应堆线程池逻辑流程如下:

  1. 主线程创建线程池,完成初始化监听fd,绑定socket地址,创建epollfd等一系列操作
  2. 主线程通过epoll循环获取事件,根据事件不同做不同处理
    1. 如果是新连接(listenfd上的事件),则初始化新的http_conn对象,获取客户端地址
    2. 如果是读事件(EPOLLIN),则调用对应sockfd的http_conn对象,进行数据读取(read()函数),read函数则会解析http并将结果写入写缓冲区中
    3. 如果是写事件(EPOLLOUT),则调用对应sockfd的http_conn对象,进行数据写(write()函数),write函数则会将写缓冲区的数据反馈客户端。
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值