TinywebServer代码详解– 服务器主程序(14) 原项目地址(点击跳转)
博主添加注释后项目地址(点击跳转)
本文将介绍TinyWebServer服务器主程序的代码
一、代码详解 1.头文件 webserver.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 #pragma once #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #include <unistd.h> #include <errno.h> #include <fcntl.h> #include <stdlib.h> #include <cassert> #include <sys/epoll.h> #include "./threadpool/threadpool.h" #include "./http/http_conn.h" const int MAX_FD = 65536 ;const int MAX_EVENT_NUMBER = 10000 ;const int TIMESLOT = 5 ;class WebServer { public : WebServer (); ~WebServer (); void init (int port, std::string user, std::string passWord, std::string databaseName, int log_write, int opt_linger, int trigmode, int sql_num, int thread_num, int close_log, int actor_model,int db_port = 3306 ) ; void thread_pool () ; void sql_pool () ; void log_write () ; void trig_mode () ; void eventListen () ; void eventLoop () ; void timer (int connfd, struct sockaddr_in client_address) ; void adjust_timer (util_timer *timer) ; void deal_timer (util_timer *timer, int sockfd) ; bool dealclinetdata () ; bool dealwithsignal (bool & timeout, bool & stop_server) ; void dealwithread (int sockfd) ; void dealwithwrite (int sockfd) ; public : int m_port; char *m_root; int m_log_write; int m_close_log; int m_actormodel; int m_pipefd[2 ]; int m_epollfd; http_conn *users; connection_pool *m_connPool; std::string m_user; std::string m_passWord; std::string m_databaseName; int m_sql_num; int m_db_port; threadpool<http_conn> *m_pool; int m_thread_num; epoll_event events[MAX_EVENT_NUMBER]; int m_listenfd; int m_OPT_LINGER; int m_TRIGMode; int m_LISTENTrigmode; int m_CONNTrigmode; client_data *users_timer; Utils utils; };
2.源文件 (1)WebServer() 该函数是服务器构造函数,用于对服务器进行初始化,创建http
连接对象数组、设置资源根目录、创建定时器连接资源数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 WebServer::WebServer () { users = new http_conn[MAX_FD]; 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]; }
(2)trig_mode() 该函数通过WebServer
类的成员变量m_TRIMode
设置epoll
上挂载的套接字的事件触发模式
0 == m_TRIGMode
监听连接套接字,水平工作模式LT
通信套接字,水平工作模式LT
1 == m_TRIGMode
监听连接套接字,水平工作模式LT
通信套接字,边沿工作模式ET
2 == m_TRIGMode
监听连接套接字,边沿工作模式ET
通信套接字,水平工作模式LT
3 == m_TRIGMode
监听连接套接字,边沿工作模式ET
通信套接字,边沿工作模式ET
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 void WebServer::trig_mode () { if (0 == m_TRIGMode) { m_LISTENTrigmode = 0 ; m_CONNTrigmode = 0 ; } else if (1 == m_TRIGMode) { m_LISTENTrigmode = 0 ; m_CONNTrigmode = 1 ; } else if (2 == m_TRIGMode) { m_LISTENTrigmode = 1 ; m_CONNTrigmode = 0 ; } else if (3 == m_TRIGMode) { m_LISTENTrigmode = 1 ; m_CONNTrigmode = 1 ; } }
(3)log_write() 该函数,根据成员变量m_close_log
判断是否开启系统日志,若开启,则通过变量m_log_write
判断日志开启的类型,异步或者同步
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void WebServer::log_write () { if (0 == m_close_log) { if (1 == m_log_write) { Log::get_instance ()->init ("./ServerLog" , m_close_log, 200 , 800000 , 800 ); } else { Log::get_instance ()->init ("./ServerLog" , m_close_log, 2000 , 800000 , 0 ); } } }
(4)sql_pool() 该函数用于初始化数据库连接池:
通过获取数据库连接池唯一实例对象,并对其进行初始化,为连接池创建一定数量的数据库连接,将这些连接push
进阻塞的连接队列中
读取数据库中的user
表,将其存储至本地的map
中,用于CGI
注册登录校验
1 2 3 4 5 6 7 8 9 10 void WebServer::sql_pool () { m_connPool = connection_pool::GetInstance (); m_connPool->init ("localhost" , m_user, m_passWord, m_databaseName, m_db_port, m_sql_num, m_close_log); users->initmysql_result (m_connPool); }
(5)eventListen() 该函数:
用于创建用于监听客户端连接的套接字,并且为套接字设置IP以及端口快速复用等属性
IO复用系统epoll实例的创建与设置,将用于监听的套接字挂载到epoll实例上,进行监测
为定时器系统创建管道套接字,用于于主循环进行通信,并且将管道的读端挂载epoll实例上,实现事件源的统一
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 void WebServer::eventListen () { m_listenfd = socket (PF_INET, SOCK_STREAM, 0 ); assert (m_listenfd >= 0 ); if (0 == m_OPT_LINGER) { struct linger tmp = {0 , 1 }; 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 ; struct sockaddr_in address; bzero (&address, sizeof address); address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons (m_port); int flag = 1 ; setsockopt (m_listenfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof flag); ret = bind (m_listenfd, (struct sockaddr *)&address, sizeof address); assert (ret >= 0 ); ret = listen (m_listenfd, 5 ); assert (ret >= 0 ); utils.init (TIMESLOT); epoll_event events[MAX_EVENT_NUMBER]; m_epollfd = epoll_create (5 ); assert (m_epollfd != -1 ); utils.addfd (m_epollfd, m_listenfd, false , m_LISTENTrigmode); http_conn::m_epollfd = m_epollfd; ret = socketpair (PF_UNIX, SOCK_STREAM, 0 , m_pipefd); assert (ret != -1 ); utils.setnonblocking (m_pipefd[1 ]); utils.addfd (m_epollfd, m_pipefd[0 ], false , 0 ); utils.addsig (SIGPIPE, SIG_IGN); utils.addsig (SIGALRM, utils.sig_handler, false ); utils.addsig (SIGTERM, utils.sig_handler, false ); alarm (TIMESLOT); Utils::u_pipefd = m_pipefd; Utils::u_epollfd = m_epollfd; }
(6)dealclientdata() 该函数,根据监听套接字事件触发方式,接收客户端的连接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 bool WebServer::dealclientdata () { struct sockaddr_in client_address; socklen_t client_addrlength = sizeof client_address; if (0 == m_LISTENTrigmode) { int connfd = accept (m_listenfd, (struct sockaddr *)&client_address, &client_addrlength); if (connfd < 0 ) { LOG_ERROR ("%s:errno is:%d" , "accept error" , errno); return false ; } if (http_conn::m_user_count >= MAX_FD) { utils.show_error (connfd, "Internal server busy" ); LOG_ERROR ("%s" , "Internal server busy" ); return false ; } timer (connfd, client_address); } 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 ; }
(7)dealwithsignal() 该函数用于处理定时器通过管道套接字发送给主循环的信号
在管道的读端,通过recv读取读端套接字读缓冲区的数据,若信号为
SIGALRM
,将timeout=true
,之后可以通过该标志,在主循环中,触发定时器处理任务函数,对定时器双向链表容器进行检查,查看是否有超时的定时器,并且重新使用alarm
函数进行定时
SIGTERM
,将stop_server
,表示关闭服务器,主循环可以通过该标志结束循环
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 bool WebServer::dealwithsignal (bool &timeout, bool &stop_server) { int ret = 0 ; int sig; char signals[1024 ]; 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 ; }
(8)dealwithread() 该函数用于处理与客户端通信接收到的数据(通信套接字读操作)
根据成员变量m_actormodel
判断事件处理模式(Reactor/Proactor)
Reactor模式下,主线程只需要负责epoll
实例中的文件描述符监听,若有套接字就绪,则将套接字上的可读事件进行封装放到线程池的请求队列中,由线程池中的工作线程进行竞争执行,IO数据读取以及数据的逻辑处理
Proactor模式下,主线程负责epoll
实例中的文件描述符监听,以及IO
的读操作;而工作线程仅仅负责数据的逻辑处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 void WebServer::dealwithread (int sockfd) { util_timer *timer = users_timer[sockfd].timer; if (1 == m_actormodel) { if (timer) { adjust_timer (timer); } m_pool->append (users + sockfd, 0 ); 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 { 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); if (timer) { adjust_timer (timer); } } else { deal_timer (timer, sockfd); } } }
(9)dealwithwrite() 该函数用于处理与客户端套接字的写操作
根据成员变量m_actormodel
判断事件处理模式(Reactor/Proactor)
Reactor模式下,主线程只需要负责epoll
实例中的文件描述符监听,若有套接字就绪,则将套接字上的可写事件进行封装放到线程池的请求队列中,由线程池中的工作线程进行竞争执行,IO数据写入至通信套接字的写缓冲区(将响应报文发送给客户端)以及数据的逻辑处理
Proactor模式下,主线程负责epoll
实例中的文件描述符监听,以及IO
的写操作;而工作线程仅仅负责数据的逻辑处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 void WebServer::dealwithwrite (int sockfd) { util_timer *timer = users_timer[sockfd].timer; if (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 { if (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); } } }
(10)eventLoop() 该函数用于事件回环,即服务器主线程循环。通过epoll_wait对套接字进行监听,并且根据就绪的套接字的类型以及套接字对应的就绪事件进行处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 void WebServer::eventLoop () { bool timeout = false ; bool stop_server = false ; while (!stop_server) { int number = epoll_wait (m_epollfd, events, MAX_EVENT_NUMBER, -1 ); if (number < 0 && errno != EINTR) { 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); if (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 ; } } }
3.主函数 main.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #include "config.h" #include <iostream> int main (int argc, char *argv[]) { Config config; config.parseJsonFile (); config.parse_arg (argc,argv); std::cout << config.user <<std::endl; std::cout << config.password << std::endl; std::cout << config.databasename << std::endl; std::cout << config.db_Port << std::endl; WebServer server; server.init (config.PORT, config.user, config.password, config.databasename, config.LOGWrite, config.OPT_LINGER, config.TRIGMode, config.sql_num, config.thread_num, config.close_log, config.actor_model, config.db_Port); server.log_write (); server.sql_pool (); server.thread_pool (); server.trig_mode (); server.eventListen (); server.eventLoop (); return 0 ; }
4.CMakeLists.txt 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 cmake_minimum_required (VERSION 3.16 )project (TinyWebServerBymyself)set (CMAKE_CXX_STANDARD 14 )set (EXECUTABLE_FILE_OUTPUT ../)set (EXECUTABLE_OUTPUT_PATH ${EXECUTABLE_FILE_OUTPUT} )find_package (jsoncpp REQUIRED)include_directories (${JSONCPP_INCLUDE_DIRS} )include_directories (/usr/include /mysql)link_directories (/usr/lib/mysql)add_executable (TinyWebServerBymyself main.cpp ./timer/lst_timer.cpp ./log/log.cpp http/http_conn.cpp ./CGImysql/sql_connection_pool.cpp ./config.cpp ./webserver.cpp) target_link_libraries (TinyWebServerBymyself mysqlclient)target_link_libraries (TinyWebServerBymyself jsoncpp_lib)target_link_libraries (TinyWebServerBymyself pthread)