本文主要是介绍TinyWebSever源码逐行注释(一)_webserver.cpp,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
前言
项目源码地址
项目详细介绍
项目简介:
Linux下C++轻量级Web服务器,助力初学者快速实践网络编程,搭建属于自己的服务器.
- 使用 线程池 + 非阻塞socket + epoll(ET和LT均实现) + 事件处理(Reactor和模拟Proactor均实现) 的并发模型
- 使用状态机解析HTTP请求报文,支持解析GET和POST请求
- 访问服务器数据库实现web端用户注册、登录功能,可以请求服务器图片和视频文件
- 实现同步/异步日志系统,记录服务器运行状态
- 经Webbench压力测试可以实现上万的并发连接数据交换
webserver.cpp用于实现web服务器的核心配置和运行,包括但不限于监听套接字、数据库连接池、线程池、信号与管道配置、epoll(ET和LT模式)的配置,原项目地址的注释较少不适合初学者,于是我将每行都加上了注释帮助大家更好的理解:
源码注释
#include "webserver.h"
//构造函数
WebServer::WebServer()
{//创建客户端连接数组users = new http_conn[MAX_FD];//创建root文件夹路径char server_path[200];getcwd(server_path, 200);char root[6] = "/root";m_root = (char *)malloc(strlen(server_path) + strlen(root) + 1);strcpy(m_root, server_path);strcat(m_root, root);//创建定时器users_timer = new client_data[MAX_FD];
}//析构函数
WebServer::~WebServer()
{//释放epoll文件描述符close(m_epollfd);//释放监听文件描述符close(m_listenfd);//释放管道读端close(m_pipefd[1]);//释放管道写端close(m_pipefd[0]);//删除客户端连接及定时器数组delete[] users;delete[] users_timer;//删除线程池delete m_pool;
}//初始化函数(服务器相关参数)
void WebServer::init(int port, string user, string passWord, string databaseName, int log_write, int opt_linger, int trigmode, int sql_num, int thread_num, int close_log, int actor_model)
{//初始化端口m_port = port;//数据库用户名m_user = user;//数据库密码m_passWord = passWord;//数据库名m_databaseName = databaseName;//数据库连接池数量m_sql_num = sql_num;//线程池的数量m_thread_num = thread_num;//日志写入的方式 同步or异步m_log_write = log_write;//用于控制连接关闭的变量m_OPT_LINGER = opt_linger;//控制监听和连接采用ET or LT触发模式m_TRIGMode = trigmode;//日志的开关 0为开m_close_log = close_log;//控制事件处理的模型为Reactor还是Proactorm_actormodel = actor_model;
}//通过m_TRIGMode选择监听和连接的触发模式 LT: Level Triggered ET:Edge Triggered
void WebServer::trig_mode()
{//LT + LTif (0 == m_TRIGMode){m_LISTENTrigmode = 0;m_CONNTrigmode = 0;}//LT + ETelse if (1 == m_TRIGMode){m_LISTENTrigmode = 0;m_CONNTrigmode = 1;}//ET + LTelse if (2 == m_TRIGMode){m_LISTENTrigmode = 1;m_CONNTrigmode = 0;}//ET + ETelse if (3 == m_TRIGMode){m_LISTENTrigmode = 1;m_CONNTrigmode = 1;}
}//初始化日志
void WebServer::log_write()
{if (0 == m_close_log){//m_log_write控制异步还是同步if (1 == m_log_write)Log::get_instance()->init("./ServerLog", m_close_log, 2000, 800000, 800);else//每写一次刷新一次logLog::get_instance()->init("./ServerLog", m_close_log, 2000, 800000, 0);}
}//初始化数据库池
void WebServer::sql_pool()
{//初始化数据库连接池m_connPool = connection_pool::GetInstance();m_connPool->init("localhost", m_user, m_passWord, m_databaseName, 3306, m_sql_num, m_close_log);//初始化数据库读取表users->initmysql_result(m_connPool);
}//初始化线程池
void WebServer::thread_pool()
{//(事件相应模型,数据库连接池,线程池中的线程数量)m_pool = new threadpool<http_conn>(m_actormodel, m_connPool, m_thread_num);
}//配置监听套接字和管道
void WebServer::eventListen()
{//创建套接字 PF_INET=IPV4 SOCK_STREAM=TCPm_listenfd = socket(PF_INET, SOCK_STREAM, 0);//检查是否创建成功assert(m_listenfd >= 0);//优雅关闭连接if (0 == m_OPT_LINGER){struct linger tmp = {0, 1};//setsockopt函数设置套接字的相关属性setsockopt(m_listenfd, SOL_SOCKET, SO_LINGER, &tmp, sizeof(tmp));}else if (1 == m_OPT_LINGER){struct linger tmp = {1, 1};setsockopt(m_listenfd, SOL_SOCKET, SO_LINGER, &tmp, sizeof(tmp));}//用于捕获和检查绑定和监听是否成功int ret = 0;//该结构体定义在头文件 <netinet/in.h> 中 用于存放IP地址和端口号struct sockaddr_in address;//清除内存中的垃圾,即结构体中的非0随机值bzero(&address, sizeof(address));//表示使用的是IPV4地址address.sin_family = AF_INET;//存放IPV4地址具体的值 这里的htonl(INADDR_ANY)绑定了所有本地IP地址address.sin_addr.s_addr = htonl(INADDR_ANY);//存放端口号 htons(m_port) 即Host TO Network Short 设置监听的端口号的过程中将主机字节序的端口号转换为网络字节序。address.sin_port = htons(m_port);//1即启用SO_REUSEEADDR允许重用本地地址 使得在重启服务器后快速重新绑定原来的端口int flag = 1;setsockopt(m_listenfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));//绑定套接字到本地IP地址ret = bind(m_listenfd, (struct sockaddr *)&address, sizeof(address));//是否绑定成功assert(ret >= 0);//设置套接字为监听模式,最多5个连接请求排队ret = listen(m_listenfd, 5);//检测是否成功assert(ret >= 0);//初始化定时器 设置最小超时单位utils.init(TIMESLOT);//存储事件信息结构体的数组epoll_event events[MAX_EVENT_NUMBER];//epoll创建内核事件表 管理着所有的文件描述符及其事件m_epollfd = epoll_create(5);//检查是否创建成功assert(m_epollfd != -1);//添加监听描述符到epoll事件表//这里的这个布尔值决定是否启用 EPOLLONESHOT 模式。//如果为 true,则在这个文件描述符上触发一个事件后,epoll 不会再次监视这个文件描述符,直到你手动重置它。这在多线程环境中非常有用,避免同一个文件描述符的事件被多个线程处理。//如果为 false,则 epoll 在每次该文件描述符上有事件发生时都会通知你。utils.addfd(m_epollfd, m_listenfd, false, m_LISTENTrigmode);//设置客户端连接的epollhttp_conn::m_epollfd = m_epollfd;//创建用于信号处理的套接字(管道) 这两个套接字 sv[0] 和 sv[1] 之间可以进行双向通信。socketpair 通常用于进程间通信ret = socketpair(PF_UNIX, SOCK_STREAM, 0, m_pipefd);assert(ret != -1);//将管道的写段设置为非阻塞 避免写操作阻塞进程:如果 m_pipefd[1] 是阻塞的,当你尝试写入数据时,如果管道已满(即 m_pipefd[0] 没有被读取),写操作将阻塞进程,直到有足够的空间可用。这可能导致程序挂起,等待管道缓冲区变得可写。通过将 m_pipefd[1] 设置为非阻塞模式,如果管道已满,写操作将立即返回失败(通常返回 EAGAIN 或 EWOULDBLOCK),程序可以处理这个情况而不被阻塞。utils.setnonblocking(m_pipefd[1]);//将管道的读端添加到epoll 使其可以通过信号驱动utils.addfd(m_epollfd, m_pipefd[0], false, 0);//设置对关闭信号的处理(ignore)utils.addsig(SIGPIPE, SIG_IGN);//设置对定时器触发信号的处理()utils.addsig(SIGALRM, utils.sig_handler, false);utils.addsig(SIGTERM, utils.sig_handler, false);//设置定时器 每隔TIMESLOT秒发送一次SIGALRM用于检查超时alarm(TIMESLOT);//工具类,信号和描述符基础操作Utils::u_pipefd = m_pipefd;Utils::u_epollfd = m_epollfd;
}//定时器相关函数 参数为(连接描述符,客户端的网络信息结构体)
void WebServer::timer(int connfd, struct sockaddr_in client_address)
{//初始客户端相关信息users[connfd].init(connfd, client_address, m_root, m_CONNTrigmode, m_close_log, m_user, m_passWord, m_databaseName);//初始化client_data数据users_timer[connfd].address = client_address;users_timer[connfd].sockfd = connfd;//创建定时器util_timer *timer = new util_timer;//绑定用户数据timer->user_data = &users_timer[connfd];//设置回调函数,即定时器超时用cb_func来处理timer->cb_func = cb_func;//获取当前时间time_t cur = time(NULL);//超时时间为当前时间+3*TIMESLOTtimer->expire = cur + 3 * TIMESLOT;//将定时器与用户数据关联users_timer[connfd].timer = timer;//将定时器添加到定时器链表中,以便定时器可以参与时间轮机制或其他定时器管理机制。utils.m_timer_lst.add_timer(timer);
}//调整定时器
void WebServer::adjust_timer(util_timer *timer)
{//若有数据传输,则将定时器往后延迟3个单位time_t cur = time(NULL);timer->expire = cur + 3 * TIMESLOT;//并对新的定时器在链表上的位置进行调整utils.m_timer_lst.adjust_timer(timer);//写入logLOG_INFO("%s", "adjust timer once");
}//回调函数的调用(定时器,客户端的已有连接描述符)
void WebServer::deal_timer(util_timer *timer, int sockfd)
{//调用回调函数timer->cb_func(&users_timer[sockfd]);//避免删除一个空指针if (timer){utils.m_timer_lst.del_timer(timer);}//写入logLOG_INFO("close fd %d", users_timer[sockfd].sockfd);
}//根据监听模式处理客户端连接
bool WebServer::dealclientdata()
{struct sockaddr_in client_address;//知道客户端地址结构体大小,便于使用accept()socklen_t client_addrlength = sizeof(client_address);//LT触发模式下的acceptif (0 == m_LISTENTrigmode){int connfd = accept(m_listenfd, (struct sockaddr *)&client_address, &client_addrlength);//记录出错的logif (connfd < 0){LOG_ERROR("%s:errno is:%d", "accept error", errno);return false;}//当前排队的客户端连接已到峰值if (http_conn::m_user_count >= MAX_FD){//向客户端发送busy信息utils.show_error(connfd, "Internal server busy");LOG_ERROR("%s", "Internal server busy");return false;}//为这个新连接设置一个定时器timer(connfd, client_address);}//ET模式处理监听事件else{//一次性接收所有客户端连接while (1){int connfd = accept(m_listenfd, (struct sockaddr *)&client_address, &client_addrlength);if (connfd < 0){LOG_ERROR("%s:errno is:%d", "accept error", errno);break;}if (http_conn::m_user_count >= MAX_FD){utils.show_error(connfd, "Internal server busy");LOG_ERROR("%s", "Internal server busy");break;}timer(connfd, client_address);}return false;}return true;
}//信号处理函数(服务器操作系统层面上的通知)
bool WebServer::dealwithsignal(bool &timeout, bool &stop_server)
{int ret = 0;int sig;char signals[1024];//从管道中读取信号 将其存储在singals数组中ret = recv(m_pipefd[0], signals, sizeof(signals), 0);//出错if (ret == -1){return false;}//读完else if (ret == 0){return false;}//else{for (int i = 0; i < ret; ++i){//逐个处理信号switch (signals[i]){case SIGALRM:{timeout = true;break;}case SIGTERM:{stop_server = true;break;}}}}return true;
}//处理客户端读事件
void WebServer::dealwithread(int sockfd)
{//获取当前客户端的定时器util_timer *timer = users_timer[sockfd].timer;//reactor模式 以事件驱动if (1 == m_actormodel){//调整定时器时间if (timer){adjust_timer(timer);}//若监测到读事件,将该事件放入请求队列m_pool->append(users + sockfd, 0);//不断循环询问客户端该事件是否结束while (true){//improv为1代表事件处理完成if (1 == users[sockfd].improv){//timer_fkag代表定时器事件触发 需要deal_timer处理如超时或客户端关闭连接if (1 == users[sockfd].timer_flag){//调用回调函数deal_timer(timer, sockfd);users[sockfd].timer_flag = 0;}users[sockfd].improv = 0;break;}}}else//proactor模式{//服务器让操作系统来完成读取验证 而不是让应用程序自己去做读取if (users[sockfd].read_once()){LOG_INFO("deal with the client(%s)", inet_ntoa(users[sockfd].get_address()->sin_addr));//将该事件放入请求队列m_pool->append_p(users + sockfd);//同Reactorif (timer){adjust_timer(timer);}}else{//服务器读不出来就调用回调函数结束该事件deal_timer(timer, sockfd);}}
}//处理客户断写事件 同上
void WebServer::dealwithwrite(int sockfd)
{util_timer *timer = users_timer[sockfd].timer;//reactorif (1 == m_actormodel){if (timer){adjust_timer(timer);}m_pool->append(users + sockfd, 1);while (true){if (1 == users[sockfd].improv){if (1 == users[sockfd].timer_flag){deal_timer(timer, sockfd);users[sockfd].timer_flag = 0;}users[sockfd].improv = 0;break;}}}else{//proactorif (users[sockfd].write()){LOG_INFO("send data to the client(%s)", inet_ntoa(users[sockfd].get_address()->sin_addr));if (timer){adjust_timer(timer);}}else{deal_timer(timer, sockfd);}}
}//服务器主事件循环
void WebServer::eventLoop()
{//控制超时事件bool timeout = false;//主循环的开关bool stop_server = false;while (!stop_server){//epoll_wait阻塞(监听)已注册的套接字 (epoll套接字 epoll内核表,最大事件数量,-1代表无限等待)int number = epoll_wait(m_epollfd, events, MAX_EVENT_NUMBER, -1);//检测epoll_wait是否出错{LOG_ERROR("%s", "epoll failure");break;}for (int i = 0; i < number; i++){//哪个套接字发生了事件int sockfd = events[i].data.fd;//如果是监听套接字,则处理新的客户端连接if (sockfd == m_listenfd){//判断处理是否成功 调用处理客户端连接函数bool flag = dealclientdata();if (false == flag)continue;}//客户端有无异常断开、挂起、错误else if (events[i].events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)){//服务器端关闭连接,移除对应的定时器util_timer *timer = users_timer[sockfd].timer;deal_timer(timer, sockfd);}//当前事件是否来自管道中的信号else if ((sockfd == m_pipefd[0]) && (events[i].events & EPOLLIN)){//处理信号 超时或关闭bool flag = dealwithsignal(timeout, stop_server);//处理失败时的logif (false == flag)LOG_ERROR("%s", "dealclientdata failure");}//当前事件是否为读事件else if (events[i].events & EPOLLIN){dealwithread(sockfd);}//当前事件是否为写事件else if (events[i].events & EPOLLOUT){dealwithwrite(sockfd);}}//超时事件if (timeout){utils.timer_handler();LOG_INFO("%s", "timer tick");timeout = false;}}
}
这篇关于TinyWebSever源码逐行注释(一)_webserver.cpp的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!