项目介绍:最近在阅读游双的《linux高性能服务器编程》,看到后面跟着里面的代码敲了一个webserver。该项目采用同步模拟的proactor框架,采用半同步半异步的并法模式,用epoll实现io多路复用。
(1)locker.h文件是一个线程同步机制包装类,封装了sem_t,pthread_mutex_t,pthread_cond_t 三个用于线程同步的机制。
(2)threadpool.h 为线程池类,成员变量主要有任务队列m_workqueue 和线程数组 m_threads。各个线程通过竞争sem_t 信号量来从 任务队列获得任务,执行任务对象的process()函数来处理http请求与写http响应。
(3)http_conn.h 为任务类,主要封装了如何处理http请求 与 如何写http响应,主要流程为将读入的http请求写入缓存区m_read_buf,通过对m_read_buf 的操作来获得请求的信息。在对请求的信息判断正确后,将http响应报文写入 m_write_buf ,然后发送该响应报文来完成与浏览器的交互。
http_conn.cpp文件中的doc_root为网站根目录,将文件放入该根目录中即可被访问。
编译:先在终端输入如下指令生成 server 可执行文件
g++ -o server main.cpp http_conn.cpp -lpthread -g
运行:
./server ip地址 端口号
Locker.h
/线程同步机制包装类
#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;
};
//phtread
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_mutex_t m_mutex;
pthread_cond_t m_cond;
};
#endif
threadpool.h
#ifndef THREADPOOL_H
#define THREADPOOL_H
#include
#include
#include
#include
#include"locker.h"
template
class threadpool
{
private:
std::list m_workequeue;
pthread_t* m_threads;
int m_thread_number;
int m_max_requests;
locker m_queuelocker;
sem m_queuestat;
bool m_stop;
private:
static void* worker(void* arg);
void run();
public:
threadpool(int thread_number = 8 , int max_requests = 10000);
~threadpool();
bool append(T* request);
};
template
threadpool::threadpool(int thread_number,int max_requests):m_thread_number(thread_number),m_max_requests(max_requests),m_threads(NULL),m_stop(false)
{
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
threadpool::~threadpool()
{
delete[] m_threads;
m_stop = true;
}
template
bool threadpool::append(T* request){
m_queuelocker.lock();
if(m_workequeue.size()>=m_max_requests){
m_queuelocker.unlock();
return false;
}
m_workequeue.push_back(request);
m_queuelocker.unlock();
m_queuestat.post();
return true;
}
template
void* threadpool::worker(void* arg){
threadpool* pool = (threadpool*)arg;
pool->run();
return pool;
}
template
void threadpool::run(){
while(!m_stop){
m_queuestat.wait();
m_queuelocker.lock();
if(m_workequeue.empty()){
m_queuelocker.unlock();
continue;
}
T* request = m_workequeue.front();
m_workequeue.pop_front();
m_queuelocker.unlock();
if(!request){
continue;
}
request->process();
}
}
#endif
http_conn.h
#ifndef HTTP_CONN_h
#define HTTP_CONN_h
#include
#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请求方法
//HTTP请求方法,但本代码中仅仅支持GET
enum METHOD{
GET = 0,
POST,
HEAD,
PUT,
DELETE,
TRACE,
OPTIONS,
CONNECT,
PATCH
};
//主状态机可能的状态
enum CHECK_STATE
{
CHECK_STATE_REQUESTION = 0,//正在分析当前请求行
CHECK_STATE_HEADER,//正在分析头部字段
CHECK_STATE_CONTENT
};
//从状态机可能的状态
enum LINE_STATUS
{
LINE_OK = 0,//读取到一个完整的行
LINE_BAD,//行出错
LINE_OPEN//行数据尚且不完整
};
//服务器处理http请求的结果
enum HTTP_CODE
{
NO_REQUEST,//请求不完整需要继续读取
GET_REQUEST,//得到了一个完整的请求
BAD_REQUEST,//请求有语法错误
NO_RESOURCE,//没有资源
FORBIDDEN_REQUEST,//没有足够的权限
FILE_REQUEST,//文件已被请求
INTERNAL_ERROR,//服务器内部错误
CLOSED_CONNECTION//客户端连接已关闭
};
public:
http_conn(){}
~http_conn(){}
public:
void init(int sockfd,const sockaddr_in& addr);
void close_conn(bool real_close = true);
bool read();
bool write();
void process();
private:
void init();
HTTP_CODE process_read();
bool process_write(HTTP_CODE ret);
//下面一组函数被process_read调用以分析HTTP请求
HTTP_CODE parse_request_line(char* text);
HTTP_CODE parse_header(char* text);
HTTP_CODE parse_content(char* text);
HTTP_CODE do_request();
char* get_line(){ return m_read_buf + m_start_line; }
LINE_STATUS parse_line();
//下面一组函数被process_write调用以填充HTTP应答
void unmap();
bool add_response(const char* format, ...);
bool add_content(const char* content);
bool add_status_line(int status, const char* title);
bool add_headers(int content_length);
bool add_content_length(int content_length);
bool add_linger();
bool add_blank_line();
public:
static int m_epollfd;
static int m_user_count;
private:
int m_sockfd;
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;
char m_real_file[FILENAME_LEN];
char* m_url;
char* m_version;
char* m_host;
int m_content_length;
bool m_linger;
char* m_file_address;
struct stat m_file_stat;
struct iovec m_iv[2];
int m_iv_count;
};
#endif
http_conn.cpp
#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 do not have permisson to get file from this server.\n";
const char* error_404_title = "Not Found";
const char* error_404_form = "The requested 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 = "/home/ross/Documents";
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;
}
void addfd(int epollfd,int fd,bool one_shot){
epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN | EPOLLET | EPOLLRDHUP;
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 | EPOLLRDHUP;
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 --;
}
}
void http_conn::init(int sockfd,const sockaddr_in& addr){
m_sockfd = sockfd;
m_address = addr;
addfd(m_epollfd,sockfd,true);
m_user_count ++;
init();
}
void http_conn::init(){
m_check_state = CHECK_STATE_REQUESTION;
m_linger = false;
m_method = GET;
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',READ_BUFFER_SIZE);
memset(m_write_buf,'\0',WRITE_BUFFER_SIZE);
memset(m_real_file,'\0',FILENAME_LEN);
}
http_conn::LINE_STATUS http_conn::parse_line(){
char temp;
for(;m_check_idx1) && (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,READ_BUFFER_SIZE-m_read_idx,0);
if(bytes_read == -1){
if( errno == EAGAIN || errno == EWOULDBLOCK){
break;
}
return false;
}
else if(bytes_read == 0){
return false;
}
m_read_idx += bytes_read;
}
return true;
}
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';
m_url += strspn(m_url," \t");
char* method = text;
if(strcasecmp(method,"GET") == 0){
m_method = GET;
}
else{
return BAD_REQUEST;
}
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_conn::HTTP_CODE http_conn::parse_header(char* text){
if(text[0] == '\0'){
if(m_content_length != 0){
m_check_state = CHECK_STATE_CONTENT;
return NO_REQUEST;
}
printf("parse_header OK\n");
return GET_REQUEST;
}
else if(strncasecmp(text,"Connection:",11) == 0){
text+=11;
text += strspn(text," \t");
if(strcasecmp(text,"keep-alive") == 0){
m_linger = true;
}
}
else if(strncasecmp(text,"Content-Length",15)==0){
text += 15;
text += strspn(text," \t");
m_content_length += atol(text);
}
else if(strncasecmp(text,"Host:",5) == 0){
text+=5;
text+=strspn(text," \t");
m_host = text;
}
else{
printf("oh no! I can't handle this header %s\n",text);
}
return NO_REQUEST;
}
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;
}
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("I got 1 line %s\n",text);
switch(m_check_state)
{
case CHECK_STATE_REQUESTION:{
ret = parse_request_line(text);
if(ret == BAD_REQUEST){
return BAD_REQUEST;
}
break;
}
case CHECK_STATE_HEADER:{
ret = parse_header(text);
if(ret == BAD_REQUEST){
return BAD_REQUEST;
}
else if(ret == GET_REQUEST){
return do_request();
}
break;
}
case CHECK_STATE_CONTENT:{
ret = parse_content(text);
if(ret == GET_REQUEST){
return do_request();
}
line_status = LINE_OPEN;
break;
}
default:
return INTERNAL_ERROR;
}
}
return NO_REQUEST;
}
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_RESOURCE;
}
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;
}
}
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);
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();
return true;
}
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("Connection: %s\r\n",(m_linger == true)?"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);
}
bool http_conn::process_write(HTTP_CODE ret){
switch ((ret))
{
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;
}
case BAD_REQUEST:
{
add_status_line(400,error_400_title);
add_headers(strlen(error_400_form));
if(! add_content(error_500_form)){
return false;
}
break;
}
case NO_RESOURCE:
{
add_status_line(404,error_404_title);
add_headers(strlen(error_404_form));
if(! add_content(error_404_form)){
return false;
}
break;
}
case FORBIDDEN_REQUEST:
{
add_status_line(403,error_403_title);
add_headers(strlen(error_403_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);
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;
printf("process_write ok\n");
return true;
}
else{
const char* ok_string = "";
add_headers(strlen(ok_string));
if(! add_content(ok_string)){
return false;
}
break;
}
}
default:
{
return false;
}
}
m_iv[0].iov_base = m_write_buf;
m_iv[0].iov_len = m_write_idx;
m_iv_count = 1;
return true;
}
bool http_conn::write(){
int temp = 0;
int bytes_have_send = 0;
int bytes_to_send = m_write_idx;
if(bytes_to_send == 0){
modfd(m_epollfd,m_sockfd,EPOLLOUT);
init();
return true;
}
while(1)
{
temp = writev(m_sockfd,m_iv,m_iv_count);
if(temp < 0){
if(errno == EAGAIN){
modfd(m_epollfd,m_sockfd,EPOLLOUT);
return true;
}
unmap();
return false;
}
bytes_have_send+=temp;
bytes_to_send-= temp;
if(bytes_to_send<=bytes_have_send){
unmap();
if(m_linger){
init();
modfd(m_epollfd,m_sockfd,EPOLLIN);
return true;
}
else{
modfd(m_epollfd,m_sockfd,EPOLLIN);
return false;
}
}
}
}
void http_conn::process()
{
HTTP_CODE read_ret = process_read();
if(read_ret == NO_REQUEST){
modfd(m_epollfd,m_sockfd,EPOLLIN);
return;
}
bool write_ret = process_write(read_ret);
if(!write_ret){
close_conn();
}
modfd(m_epollfd,m_sockfd,EPOLLOUT);
}
main.cpp
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "locker.h"
#include "threadpool.h"
#include "http_conn.h"
#define MAX_FD 65536
#define MAX_EVENT_NUMBER 10000
extern int addfd(int epollfd, int fd, bool one_shot);
extern int removefd(int epollfd, int fd);
void addsig(int sig,void(handler)(int),bool restart = true){
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);
}
void show_error(int sockfd,const char* info){
printf("%s\n",info);
send(sockfd,info,strlen(info),0);
close(sockfd);
}
int main(int argc,char* argv[]){
if(argc <= 2){
printf("usage:%s ip port\n",basename(argv[0]));
return 1;
}
const char* ip = argv[1];
int port = atoi(argv[2]);
addsig(SIGPIPE,SIG_IGN);
threadpool* pool = NULL;
try
{
pool = new threadpool;
}
catch(...)
{
printf("error\n");
return 1;
}
printf("creat a threadpool\n");
http_conn* users = new http_conn[MAX_FD];
assert(users);
int user_count = 0;
printf("create some users\n");
sockaddr_in addr;
bzero(&addr,sizeof(addr));
addr.sin_family = AF_INET;
inet_pton(AF_INET6,ip,&addr.sin_addr);
addr.sin_port = htons(port);
int listenfd = socket(PF_INET,SOCK_STREAM,0);
assert(listenfd>=0);
linger tmp = {1,0};
setsockopt(listenfd,SOL_SOCKET,SO_LINGER,&tmp,sizeof(tmp));
int ret = bind(listenfd,(sockaddr*)&addr,sizeof(addr));
assert(ret != -1);
ret = listen(listenfd,5);
assert(ret != -1);
printf("listening\n");
int epollfd = epoll_create(5);
assert(epollfd != -1);
epoll_event events[MAX_EVENT_NUMBER];
addfd(epollfd,listenfd,false);
http_conn::m_epollfd = epollfd;
while(true){
int number = epoll_wait(epollfd,events,MAX_EVENT_NUMBER,-1);
if((number < 0) && (errno!=EAGAIN)){
printf("epoll failure\n");
break;
}
for(int i=0;i= MAX_FD){
show_error(connfd,"Internal server busy\n");
continue;
}
users[connfd].init(connfd,clientAddr);
}
else if(events[i].events & (EPOLLRDBAND | EPOLLHUP | 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()){
users[sockfd].close_conn();
}
printf("write ok\n");
}
else{}
}
}
close(epollfd);
close(listenfd);
delete[] users;
delete pool;
return 0;
}