diff --git a/CGImysql/sql_connection_pool.cpp b/CGImysql/sql_connection_pool.cpp index f4109b79..73604e01 100644 --- a/CGImysql/sql_connection_pool.cpp +++ b/CGImysql/sql_connection_pool.cpp @@ -58,7 +58,6 @@ void connection_pool::init(string url, string User, string PassWord, string DBNa m_MaxConn = m_FreeConn; } - //当有请求时,从数据库连接池中返回一个可用连接,更新使用和空闲连接数 MYSQL *connection_pool::GetConnection() { @@ -68,7 +67,7 @@ MYSQL *connection_pool::GetConnection() return NULL; reserve.wait(); - + lock.lock(); con = connList.front(); @@ -131,13 +130,15 @@ connection_pool::~connection_pool() DestroyPool(); } -connectionRAII::connectionRAII(MYSQL **SQL, connection_pool *connPool){ +connectionRAII::connectionRAII(MYSQL **SQL, connection_pool *connPool) +{ *SQL = connPool->GetConnection(); - + conRAII = *SQL; poolRAII = connPool; } -connectionRAII::~connectionRAII(){ +connectionRAII::~connectionRAII() +{ poolRAII->ReleaseConnection(conRAII); } \ No newline at end of file diff --git a/CGImysql/sql_connection_pool.h b/CGImysql/sql_connection_pool.h index 00d285be..9c4a3286 100644 --- a/CGImysql/sql_connection_pool.h +++ b/CGImysql/sql_connection_pool.h @@ -13,6 +13,26 @@ using namespace std; +/* + * m_MaxConn 最大连接数 + m_CurConn 当前已使用的连接数 + m_FreeConn 当前空闲的连接数 + connList 连接池 + * m_url 主机地址 + m_Port 数据库端口号 + m_User 登陆数据库用户名 + m_PassWord 登陆数据库密码 + m_DatabaseName 使用数据库名 + int m_close_log 日志开关 + * 单例模式:将构造写入private,通过指针进行实例化,这样保证只能实例化一次 + * 懒汉模式:需要用到创建实例了程序再去创建实例 + * GetConnection 获取数据库连接,从连接池中返回一个可用连接 + ReleaseConnection 回收连接,将连接放回连接池中 + GetFreeConn 获取当前连接池中空闲(可用)的连接数 + DestroyPool 释放连接池,即释放所有连接 + GetInstance 懒汉模式+单例模式 + init 实例化后真正的构造函数 + */ class connection_pool { public: @@ -24,34 +44,39 @@ class connection_pool //单例模式 static connection_pool *GetInstance(); - void init(string url, string User, string PassWord, string DataBaseName, int Port, int MaxConn, int close_log); + void init(string url, string User, string PassWord, string DataBaseName, int Port, int MaxConn, int close_log); private: connection_pool(); ~connection_pool(); - int m_MaxConn; //最大连接数 - int m_CurConn; //当前已使用的连接数 + int m_MaxConn; //最大连接数 + int m_CurConn; //当前已使用的连接数 int m_FreeConn; //当前空闲的连接数 locker lock; list connList; //连接池 sem reserve; public: - string m_url; //主机地址 - string m_Port; //数据库端口号 - string m_User; //登陆数据库用户名 - string m_PassWord; //登陆数据库密码 + string m_url; //主机地址 + string m_Port; //数据库端口号 + string m_User; //登陆数据库用户名 + string m_PassWord; //登陆数据库密码 string m_DatabaseName; //使用数据库名 - int m_close_log; //日志开关 + int m_close_log; //日志开关 }; -class connectionRAII{ +/* + * 采用RAII封装数据库连接池 + * RAII:是使用一个对象,在其构造时获取资源,在对象生命期控制对资源的访问使之始终保持有效,最后在对象析构的时候释放资源 + */ +class connectionRAII +{ public: connectionRAII(MYSQL **con, connection_pool *connPool); ~connectionRAII(); - + private: MYSQL *conRAII; connection_pool *poolRAII; diff --git a/README.md b/README.md index 7de0b76e..65d0caea 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ TinyWebServer =============== Linux下C++轻量级Web服务器,助力初学者快速实践网络编程,搭建属于自己的服务器. +* [测试页](http://175.24.234.48:9007) * 使用 **线程池 + 非阻塞socket + epoll(ET和LT均实现) + 事件处理(Reactor和模拟Proactor均实现)** 的并发模型 * 使用**状态机**解析HTTP请求报文,支持解析**GET和POST**请求 * 访问服务器数据库实现web端用户**注册、登录**功能,可以请求服务器**图片和视频文件** @@ -119,6 +120,7 @@ Demo演示 * 服务器测试环境 * Ubuntu版本16.04 * MySQL版本5.7.29 + * Centos 7 + MYSQL 8.0.22 * 浏览器测试环境 * Windows、Linux均可 * Chrome @@ -245,4 +247,4 @@ CPP11实现 ------------ Linux高性能服务器编程,游双著. -感谢以下朋友的PR和帮助: [@RownH](https://github.com/RownH),[@mapleFU](https://github.com/mapleFU),[@ZWiley](https://github.com/ZWiley),[@zjuHong](https://github.com/zjuHong),[@mamil](https://github.com/mamil),[@byfate](https://github.com/byfate),[@MaJun827](https://github.com/MaJun827),[@BBLiu-coder](https://github.com/BBLiu-coder),[@smoky96](https://github.com/smoky96),[@yfBong](https://github.com/yfBong),[@liuwuyao](https://github.com/liuwuyao),[@Huixxi](https://github.com/Huixxi),[@markparticle](https://github.com/markparticle). +感谢以下朋友的PR和帮助: [@RownH](https://github.com/RownH),[@mapleFU](https://github.com/mapleFU),[@ZWiley](https://github.com/ZWiley),[@zjuHong](https://github.com/zjuHong),[@mamil](https://github.com/mamil),[@byfate](https://github.com/byfate),[@MaJun827](https://github.com/MaJun827),[@BBLiu-coder](https://github.com/BBLiu-coder),[@smoky96](https://github.com/smoky96),[@yfBong](https://github.com/yfBong),[@liuwuyao](https://github.com/liuwuyao),[@Huixxi](https://github.com/Huixxi),[@markparticle](https://github.com/markparticle),[@Zhenghao-Liu](https://github.com/Zhenghao-Liu)。 diff --git a/config.cpp b/config.cpp index f16a69ec..7f3b6333 100644 --- a/config.cpp +++ b/config.cpp @@ -1,6 +1,8 @@ #include "config.h" +#include -Config::Config(){ +Config::Config() +{ //端口号,默认9006 PORT = 9006; @@ -32,7 +34,8 @@ Config::Config(){ actor_model = 0; } -void Config::parse_arg(int argc, char*argv[]){ +void Config::parse_arg(int argc, char *argv[]) +{ int opt; const char *str = "p:l:m:o:s:t:c:a:"; while ((opt = getopt(argc, argv, str)) != -1) @@ -80,7 +83,7 @@ void Config::parse_arg(int argc, char*argv[]){ break; } default: - break; + exit(EXIT_FAILURE); } } } \ No newline at end of file diff --git a/config.h b/config.h index d57db57e..b118f4eb 100644 --- a/config.h +++ b/config.h @@ -11,7 +11,7 @@ class Config Config(); ~Config(){}; - void parse_arg(int argc, char*argv[]); + void parse_arg(int argc, char *argv[]); //端口号 int PORT; diff --git a/http/http_conn.h b/http/http_conn.h index b36d3652..d70b57f3 100644 --- a/http/http_conn.h +++ b/http/http_conn.h @@ -26,6 +26,102 @@ #include "../timer/lst_timer.h" #include "../log/log.h" +/* + * 线程池的工作类型类 + * FILENAME_LEN 文件名的最大长度 + READ_BUFFER_SIZE 度缓冲区的大小 + WRITE_BUFFER_SIZE 写缓冲区的大小 + METHOD HTTP请求方法 + GET 申请获取资源 + POST 客户端向服务器提交数据的方法 + HEAD 要求服务器返回头部信息 + PUT 上传某个资源 + DELETE 删除某个资源 + TRACE 要求目标服务器返回原始HTTP请求的内容 + OPTIONS 查看服务器对某个特定URL都支持哪些请求方法 + CONNECT 用于某些代理服务器,能把请求的连接转化为一个安全隧道 + PATCH 对某个资源做部分修改 + CHECK_STATE 解析客户请求时,主状态机所处的状态 + CHECK_STATE_REQUESTLINE 分析请求行 + CHECK_STATE_HEADER 分析头部域 + CHECK_STATE_CONTENT 分析消息体 + HTTP_CODE 服务器处理HTTP请求可能的结果 + NO_REQUEST 请求不完整,需要继续读取数据 + GET_REQUEST 获得了一个完整的客户请求 + BAD_REQUEST 客户请求有语法错误 + NO_RESOURCE 没有对应的资源 + FORBIDDEN_REQUEST 客户对资源没有足够访问权限 + FILE_REQUEST 客户请求是一个可读的文件 + INTERAL_ERROR 服务器内部错误 + CLOSED_CONNECTION 客户端已经关闭连接 + LINE_STATUS 行的读取状态 + LINE_OK 读取到一个完整的行 + LINE_BAD 行出错 + LINE_OPEN 行数据尚且不完整 + * init 初始化接受新的连接 + close_conn 关闭连接 + process 处理客户请求 + read_once 循环读取客户数据,知道无数据刻度或对方关闭连接 + write 非阻塞写入数据 + get_address 返回服务器端通信的socket地址 + initmysql_result 初始化mysql连接,取出数据库保存的账号和密码,存入map中 + timer_flag 是否开启了定时器 + improv + * init 初始化连接 + process_read 解析HTTP请求 + 以下一组函数被process_read调用以分析HTTP请求 + parse_request_line 解析http请求行,获得请求方法,目标url及http版本号 + parse_headers 解析http请求的一个头部信息 + parse_content 判断http请求是否被完整读入,读完消息体,这里实质上没有真正解析消息体 + do_request 当得到一个完整、正确的HTTP请求时,分析用户的请求内容:登陆、注册、获取文件 + get_line 移动指针读取一行 + parse_line 从状态机,用于分析出一行内容 + process_write 填充HTTP应答 + 以下一组函数被process_write调用用以填充HTTP应答 + unmap 释放共享内存映射区 + add_response 往HTTP应答中写入待发送的数据 + add_content 往HTTP应答写入消息体 + add_status_line 往HTTP应答写入状态行 + add_content_type 往HTTP应答写入头部字段的目标文档的MIME类型 + add_content_length 往HTTP应答写入头部字段的目标文档的长度 + add_linger 往HTTP应答写入头部字段的此次通信后是否关闭TCP连接 + add_blank_line 往HTTP应答写入一个空行 \r\n + * m_pollfd epoll的事件表文件描述符 + m_user_count 统计用户数量 + mysql 连接的数据库 + m_state 读为0,写为1 + * m_sockfd HTTP连接的socket文件描述符 + m_address HTTP对方的socket地址 + m_read_buf 读缓冲区 + m_read_idx 表示读缓冲中已经读入的客户数据的最后一个字节的下一个位置 + m_check_idx 当前正在分析的字符在读缓冲区中的位置 + m_start_line 当前正在解析的行的起始位置 + m_write_buf 写缓冲区 + m_write_idx 写缓冲区中待发送的字节数 + m_check_state 主状态机当前所处的状态 + m_method 请求方法 + m_real_file 客户请求的目标文件的完整路径,其内容等于网站根目录doc_root+m_url + m_url 客户请求的目标文件的文件名 + m_version HTTP 协议版本号,此程序只支持HTTP/1.1 + m_host 主机名 + m_content_length HTTP请求的消息体的长度 + m_linger HTTP其你去是否要求保持连接 + m_file_address 客户请求的目标文件被mmap到内存中的起始位置 + m_file_stat 目标文件的状态:是否存在、是否为目录、是否刻度、文件信息 + m_iv 采用writev写操作的内存分块 + m_iv_count 采用writev写操作的内存分块数量 + cgi 是否启用新的POST + m_string 存储请求头数据 + bytes_to_send 需要发送的字节数 + bytes_have_send 已经发送的字节数 + doc_root 服务器端网站根目录 + m_users 客户账号和密码 + m_TRIGMode 是否采用ET工作模式 + m_close_log 是否关闭日志功能 + sql_uesr MYSQL用户名 + sql_passwd MYSQL密码 + sql_name MYSQL数据库名 + */ class http_conn { public: @@ -42,7 +138,7 @@ class http_conn TRACE, OPTIONS, CONNECT, - PATH + PATCH }; enum CHECK_STATE { @@ -86,7 +182,6 @@ class http_conn int timer_flag; int improv; - private: void init(); HTTP_CODE process_read(); @@ -111,7 +206,7 @@ class http_conn static int m_epollfd; static int m_user_count; MYSQL *mysql; - int m_state; //读为0, 写为1 + int m_state; //读为0, 写为1 private: int m_sockfd; diff --git a/lock/locker.h b/lock/locker.h index de38a06e..3ed9d3f0 100644 --- a/lock/locker.h +++ b/lock/locker.h @@ -5,6 +5,13 @@ #include #include +/* + * 封装信号量的类 + * sem_init函数用于初始化一个未命名的信号量 + * sem_destory函数用于销毁信号量 + * sem_wait函数将以原子操作方式将信号量减一,信号量为0时,sem_wait阻塞 + * sem_post函数以原子操作方式将信号量加一,信号量大于0时,唤醒调用sem_post的线程 + */ class sem { public: @@ -38,6 +45,14 @@ class sem private: sem_t m_sem; }; + +/* + * 封装互斥锁的类 + * pthread_mutex_init函数用于初始化互斥锁 + * pthread_mutex_destory函数用于销毁互斥锁 + * pthread_mutex_lock函数以原子操作方式给互斥锁加锁 + * pthread_mutex_unlock函数以原子操作方式给互斥锁解锁 + */ class locker { public: @@ -68,6 +83,19 @@ class locker private: pthread_mutex_t m_mutex; }; + +/* + * 封装条件变量的类,此类与书本上的实现有所不同 + * 与条件变量协作的互斥锁不是封装在类里,而是以参数形式传入 + * pthread_cond_init函数用于初始化条件变量 + * pthread_cond_destory函数销毁条件变量 + * pthread_cond_wait函数用于等待目标条件变量.该函数调用时需要传入 mutex参数(必须是一个已经加锁的互斥锁), + 函数执行时,先把调用线程放入条件变量的请求队列,然后将互斥锁mutex解锁,当函数成功返回为0时,互斥锁会再次被锁上. + 也就是说函数内部会有一次解锁和加锁操作. + * pthread_cond_broadcast函数以广播的方式唤醒所有等待目标条件变量的线程 + * pthread_cond_signal函数用于唤醒一个等待目标条件变量的线程, + 至于哪个线程被唤醒,则取决于线程的优先级和调度策略 + */ class cond { public: @@ -83,6 +111,14 @@ class cond { pthread_cond_destroy(&m_cond); } + /* + * 参数m_mutex必须是一个已经上锁的互斥变量 + * 如果m_mutex的type是一个PTHREAD_MUTEX_NORMAL普通锁 + 可以通过pthread_mutex_trylock的方式判断返回值来检测原先是否已经加锁 + 别忘了如果trylock成功要记得解锁 + * 但是涉及到pthread_mutex_t的属性type还有别的类型的锁 + * 只能将安全性结果交给函数外保证 + */ bool wait(pthread_mutex_t *m_mutex) { int ret = 0; diff --git a/log/block_queue.h b/log/block_queue.h index 34c77bd5..ad67ac0b 100644 --- a/log/block_queue.h +++ b/log/block_queue.h @@ -13,6 +13,19 @@ #include "../lock/locker.h" using namespace std; +/* + * 将生产者-消费者模型封装为阻塞队列,创建一个写线程, + * 工作线程将要写的内容push进队列,写线程从队列中取出内容,写入日志文件 + * 用一个循环数组来模拟队列 + * m_mutex 互斥锁 + cond 条件变量 + m_array 队列实例化后的元素类型 + m_size 队列当前实际使用大小 + m_max_size 队列最大容量 + m_front 指向队列的头指针 + m_back 指向队列的尾指针,采取左闭右闭[m_front,m_back] + * m_back = (m_back + 1) % m_max_size; 将事件增加到队尾 + */ template class block_queue { @@ -44,12 +57,12 @@ class block_queue { m_mutex.lock(); if (m_array != NULL) - delete [] m_array; + delete[] m_array; m_mutex.unlock(); } //判断队列是否满了 - bool full() + bool full() { m_mutex.lock(); if (m_size >= m_max_size) @@ -62,7 +75,7 @@ class block_queue return false; } //判断队列是否为空 - bool empty() + bool empty() { m_mutex.lock(); if (0 == m_size) @@ -74,7 +87,7 @@ class block_queue return false; } //返回队首元素 - bool front(T &value) + bool front(T &value) { m_mutex.lock(); if (0 == m_size) @@ -87,7 +100,7 @@ class block_queue return true; } //返回队尾元素 - bool back(T &value) + bool back(T &value) { m_mutex.lock(); if (0 == m_size) @@ -100,7 +113,7 @@ class block_queue return true; } - int size() + int size() { int tmp = 0; @@ -124,76 +137,70 @@ class block_queue //往队列添加元素,需要将所有使用队列的线程先唤醒 //当有元素push进队列,相当于生产者生产了一个元素 //若当前没有线程等待条件变量,则唤醒无意义 - bool push(const T &item) + bool push(const T &in_elem) { - m_mutex.lock(); if (m_size >= m_max_size) { - m_cond.broadcast(); m_mutex.unlock(); return false; } - - m_back = (m_back + 1) % m_max_size; - m_array[m_back] = item; - - m_size++; - + if (m_back == -1 && m_front == -1) + { + m_back = 0; + m_front = 0; + } + else + m_back = (m_back + 1) % m_max_size; + m_array[m_back] = in_elem; + ++m_size; m_cond.broadcast(); m_mutex.unlock(); return true; } //pop时,如果当前队列没有元素,将会等待条件变量 - bool pop(T &item) + bool pop(T &out_elem) { - m_mutex.lock(); while (m_size <= 0) - { - if (!m_cond.wait(m_mutex.get())) { m_mutex.unlock(); return false; } - } - + out_elem = m_array[m_front]; m_front = (m_front + 1) % m_max_size; - item = m_array[m_front]; - m_size--; + --m_size; m_mutex.unlock(); return true; } //增加了超时处理 - bool pop(T &item, int ms_timeout) + bool pop(T &out_elem, int ms_timeout) { - struct timespec t = {0, 0}; struct timeval now = {0, 0}; gettimeofday(&now, NULL); + struct timespec t = {0, 0}; m_mutex.lock(); if (m_size <= 0) { t.tv_sec = now.tv_sec + ms_timeout / 1000; - t.tv_nsec = (ms_timeout % 1000) * 1000; + t.tv_nsec = (now.tv_usec + (ms_timeout % 1000) * 1000) * 1000; if (!m_cond.timewait(m_mutex.get(), t)) { m_mutex.unlock(); return false; } } - if (m_size <= 0) { m_mutex.unlock(); return false; } - + out_elem = m_array[m_front]; m_front = (m_front + 1) % m_max_size; - item = m_array[m_front]; - m_size--; + --m_size; m_mutex.unlock(); return true; } diff --git a/log/log.cpp b/log/log.cpp index 65e8ad46..93ce3ab6 100644 --- a/log/log.cpp +++ b/log/log.cpp @@ -31,7 +31,7 @@ bool Log::init(const char *file_name, int close_log, int log_buf_size, int split //flush_log_thread为回调函数,这里表示创建线程异步写日志 pthread_create(&tid, NULL, flush_log_thread, NULL); } - + m_close_log = close_log; m_log_buf_size = log_buf_size; m_buf = new char[m_log_buf_size]; @@ -42,7 +42,6 @@ bool Log::init(const char *file_name, int close_log, int log_buf_size, int split struct tm *sys_tm = localtime(&t); struct tm my_tm = *sys_tm; - const char *p = strrchr(file_name, '/'); char log_full_name[256] = {0}; @@ -58,7 +57,7 @@ bool Log::init(const char *file_name, int close_log, int log_buf_size, int split } m_today = my_tm.tm_mday; - + m_fp = fopen(log_full_name, "a"); if (m_fp == NULL) { @@ -100,14 +99,14 @@ void Log::write_log(int level, const char *format, ...) if (m_today != my_tm.tm_mday || m_count % m_split_lines == 0) //everyday log { - + char new_log[256] = {0}; fflush(m_fp); fclose(m_fp); char tail[16] = {0}; - + snprintf(tail, 16, "%d_%02d_%02d_", my_tm.tm_year + 1900, my_tm.tm_mon + 1, my_tm.tm_mday); - + if (m_today != my_tm.tm_mday) { snprintf(new_log, 255, "%s%s%s", dir_name, tail, log_name); @@ -120,7 +119,7 @@ void Log::write_log(int level, const char *format, ...) } m_fp = fopen(new_log, "a"); } - + m_mutex.unlock(); va_list valst; @@ -133,7 +132,7 @@ void Log::write_log(int level, const char *format, ...) int n = snprintf(m_buf, 48, "%d-%02d-%02d %02d:%02d:%02d.%06ld %s ", my_tm.tm_year + 1900, my_tm.tm_mon + 1, my_tm.tm_mday, my_tm.tm_hour, my_tm.tm_min, my_tm.tm_sec, now.tv_usec, s); - + int m = vsnprintf(m_buf + n, m_log_buf_size - 1, format, valst); m_buf[n + m] = '\n'; m_buf[n + m + 1] = '\0'; diff --git a/log/log.h b/log/log.h index 64972af4..39175463 100644 --- a/log/log.h +++ b/log/log.h @@ -10,6 +10,23 @@ using namespace std; +/* + * dir_name 路径名 + log_name 日志文件名 + m_split_lines 日志最大行数 + m_log_bug_size 日志缓冲区大小 + m_count 日志当前已用行数 + m_today 当前时间是哪一天 + m_fp 打开log的文件指针 + m_buf 缓冲区指针 + m_log_queue 阻塞队列 + m_is_async 是否同步标志位 + m_mutex 互斥锁 + m_clog_log 是否关闭日志 + * 单例模式:将构造写入private,通过指针进行实例化,这样保证只能实例化一次 + * 懒汉模式:需要用到创建实例了程序再去创建实例 + * async_write_log 同步的从消息队列中取出消息,写入日志文件中 + */ class Log { public: @@ -61,9 +78,29 @@ class Log int m_close_log; //关闭日志 }; -#define LOG_DEBUG(format, ...) if(0 == m_close_log) {Log::get_instance()->write_log(0, format, ##__VA_ARGS__); Log::get_instance()->flush();} -#define LOG_INFO(format, ...) if(0 == m_close_log) {Log::get_instance()->write_log(1, format, ##__VA_ARGS__); Log::get_instance()->flush();} -#define LOG_WARN(format, ...) if(0 == m_close_log) {Log::get_instance()->write_log(2, format, ##__VA_ARGS__); Log::get_instance()->flush();} -#define LOG_ERROR(format, ...) if(0 == m_close_log) {Log::get_instance()->write_log(3, format, ##__VA_ARGS__); Log::get_instance()->flush();} +#define LOG_DEBUG(format, ...) \ + if (0 == m_close_log) \ + { \ + Log::get_instance()->write_log(0, format, ##__VA_ARGS__); \ + Log::get_instance()->flush(); \ + } +#define LOG_INFO(format, ...) \ + if (0 == m_close_log) \ + { \ + Log::get_instance()->write_log(1, format, ##__VA_ARGS__); \ + Log::get_instance()->flush(); \ + } +#define LOG_WARN(format, ...) \ + if (0 == m_close_log) \ + { \ + Log::get_instance()->write_log(2, format, ##__VA_ARGS__); \ + Log::get_instance()->flush(); \ + } +#define LOG_ERROR(format, ...) \ + if (0 == m_close_log) \ + { \ + Log::get_instance()->write_log(3, format, ##__VA_ARGS__); \ + Log::get_instance()->flush(); \ + } #endif diff --git a/main.cpp b/main.cpp index 19a4fff7..4e7b5268 100644 --- a/main.cpp +++ b/main.cpp @@ -1,7 +1,11 @@ #include "config.h" +#include int main(int argc, char *argv[]) { + //将下句代码取消注释即可将此程序以守护进程运行 + //daemon(1,0); + //需要修改的数据库信息,登录名,密码,库名 string user = "root"; string passwd = "root"; @@ -14,10 +18,9 @@ int main(int argc, char *argv[]) WebServer server; //初始化 - server.init(config.PORT, user, passwd, databasename, config.LOGWrite, - config.OPT_LINGER, config.TRIGMode, config.sql_num, config.thread_num, + server.init(config.PORT, user, passwd, databasename, config.LOGWrite, + config.OPT_LINGER, config.TRIGMode, config.sql_num, config.thread_num, config.close_log, config.actor_model); - //日志 server.log_write(); diff --git a/threadpool/README.md b/threadpool/README.md index f015fb02..9942919a 100644 --- a/threadpool/README.md +++ b/threadpool/README.md @@ -1,4 +1,4 @@ - + 半同步/半反应堆线程池 =============== 使用一个工作队列完全解除了主线程和工作线程的耦合关系:主线程往工作队列中插入任务,工作线程通过竞争来取得任务并执行它。 @@ -6,7 +6,7 @@ > * 半同步/半反应堆 > * 线程池 - +![image](./half-sync-reactive.bmp) diff --git a/threadpool/half-sync-reactive.bmp b/threadpool/half-sync-reactive.bmp new file mode 100644 index 00000000..865a917b Binary files /dev/null and b/threadpool/half-sync-reactive.bmp differ diff --git a/threadpool/threadpool.h b/threadpool/threadpool.h index b2848a61..c5b6a097 100644 --- a/threadpool/threadpool.h +++ b/threadpool/threadpool.h @@ -8,6 +8,19 @@ #include "../lock/locker.h" #include "../CGImysql/sql_connection_pool.h" +/* + * m_thread_number 线程池中的线程数 + m_max_requests 请求队列中允许的最大请求数 + m_threads 描述线程池的数组,其大小为m_thread_number + m_workqueue 请求队列 + m_queuelocker 保护请求队列的互斥锁 + m_queuestat 是否有任务需要处理 + m_connPool 数据库 + m_actor_model 模型切换 + * worker 工作线程运行的函数 + run 工作线程的真正运行函数,它不断从工作队列中取出任务并执行 + * append 往请求队列中添加任务 + */ template class threadpool { @@ -24,17 +37,17 @@ class threadpool void run(); private: - int m_thread_number; //线程池中的线程数 - int m_max_requests; //请求队列中允许的最大请求数 - pthread_t *m_threads; //描述线程池的数组,其大小为m_thread_number - std::list m_workqueue; //请求队列 - locker m_queuelocker; //保护请求队列的互斥锁 - sem m_queuestat; //是否有任务需要处理 - connection_pool *m_connPool; //数据库 - int m_actor_model; //模型切换 + int m_thread_number; //线程池中的线程数 + int m_max_requests; //请求队列中允许的最大请求数 + pthread_t *m_threads; //描述线程池的数组,其大小为m_thread_number + std::list m_workqueue; //请求队列 + locker m_queuelocker; //保护请求队列的互斥锁 + sem m_queuestat; //是否有任务需要处理 + connection_pool *m_connPool; //数据库 + int m_actor_model; //模型切换 }; template -threadpool::threadpool( int actor_model, connection_pool *connPool, int thread_number, int max_requests) : m_actor_model(actor_model),m_thread_number(thread_number), m_max_requests(max_requests), m_threads(NULL),m_connPool(connPool) +threadpool::threadpool(int actor_model, connection_pool *connPool, int thread_number, int max_requests) : m_actor_model(actor_model), m_thread_number(thread_number), m_max_requests(max_requests), m_threads(NULL), m_connPool(connPool) { if (thread_number <= 0 || max_requests <= 0) throw std::exception(); diff --git a/timer/lst_timer.cpp b/timer/lst_timer.cpp index 7efba9bd..e3f0b8cc 100644 --- a/timer/lst_timer.cpp +++ b/timer/lst_timer.cpp @@ -99,7 +99,7 @@ void sort_timer_lst::tick() { return; } - + time_t cur = time(NULL); util_timer *tmp = head; while (tmp) diff --git a/timer/lst_timer.h b/timer/lst_timer.h index 5a64c7fa..9f407816 100644 --- a/timer/lst_timer.h +++ b/timer/lst_timer.h @@ -24,77 +24,122 @@ #include #include "../log/log.h" +/* + * 前向声明:两个元素之前互相调用,但是因为声明顺序的关系, + 导致一个元素找不到另一个元素的声明,所以要提前声明 + */ class util_timer; +/* + * 用户数据结构 + * address 客户端socket地址 + sockfd 客户端对应的socket文件描述符 + timer 定时器 + */ struct client_data { - sockaddr_in address; - int sockfd; - util_timer *timer; + sockaddr_in address; + int sockfd; + util_timer *timer; }; +/* + * 定时器类 + * expire 任务的超时时间,这里设定为绝对时间,即Unix时间,以s为单位 + user_data 指向用户数据 + prev 指向前一个定时器 + next 指向下一个定时器 + cb_func 定时器回调函数(要执行的函数) + */ class util_timer { public: - util_timer() : prev(NULL), next(NULL) {} + util_timer() : prev(NULL), next(NULL) {} public: - time_t expire; - - void (* cb_func)(client_data *); - client_data *user_data; - util_timer *prev; - util_timer *next; + time_t expire; + + void (*cb_func)(client_data *); + client_data *user_data; + util_timer *prev; + util_timer *next; }; +/* + * 定时器链表 + 一个按照过期时间升序,双向链表,且带有头尾指针 + * head 头指针指向第一个定时器 + tail 尾指针指向最后一个定时器 + private:add_timer 一个重载的辅助函数,被public的add_timer和adjust_timer函数调用, + 该函数用于将timer添加到节点lst_head之后的部分链表中 + * add_timer 将定时器添加到链表中 + adjust_timer 修改链表中的某个定时器,只支持过期时间延长 + del_timer 将定时器从链表中删除 + tick SIGALRM信号被触发,即有任务到期,执行此函数处理链表上所有到期的任务 + */ class sort_timer_lst { public: - sort_timer_lst(); - ~sort_timer_lst(); + sort_timer_lst(); + ~sort_timer_lst(); - void add_timer(util_timer *timer); - void adjust_timer(util_timer *timer); - void del_timer(util_timer *timer); - void tick(); + void add_timer(util_timer *timer); + void adjust_timer(util_timer *timer); + void del_timer(util_timer *timer); + void tick(); private: - void add_timer(util_timer *timer, util_timer *lst_head); + void add_timer(util_timer *timer, util_timer *lst_head); - util_timer *head; - util_timer *tail; + util_timer *head; + util_timer *tail; }; +/* + * 封装客户类 + * u_pipefd 通信的管道 + m_timer_lst 定时器链表 + u_epollfd epoll事件表的文件描述符 + m_TIMESLOT 设定一个定时时间触发SIGALRM信号 + * init 初始化 + setnonblocking 对文件描述符设置非阻塞 + 因为epoll采用ET边沿触发,如果是阻塞的,socket的读写操作将会因为没有后续的事件而一直处于阻塞状态 + addfd 将事件添加到epoll时间表中,根据判断TRIGMode==1决定是否开启ET工作模式 + sig_handler 信号处理函数 + addsig 设置信号对应的信号处理函数 + timer_handler 定时处理超时的任务,并通过alarm重新定时以不断触发SIGALRM信号 + show_error 向客户端发送错误信息,并关闭该连接 + */ class Utils { public: - Utils() {} - ~Utils() {} + Utils() {} + ~Utils() {} - void init(int timeslot); + void init(int timeslot); - //对文件描述符设置非阻塞 - int setnonblocking(int fd); + //对文件描述符设置非阻塞 + int setnonblocking(int fd); - //将内核事件表注册读事件,ET模式,选择开启EPOLLONESHOT - void addfd(int epollfd, int fd, bool one_shot, int TRIGMode); + //将内核事件表注册读事件,ET模式,选择开启EPOLLONESHOT + void addfd(int epollfd, int fd, bool one_shot, int TRIGMode); - //信号处理函数 - static void sig_handler(int sig); + //信号处理函数 + static void sig_handler(int sig); - //设置信号函数 - void addsig(int sig, void(handler)(int), bool restart = true); + //设置信号函数 + void addsig(int sig, void(handler)(int), bool restart = true); - //定时处理任务,重新定时以不断触发SIGALRM信号 - void timer_handler(); + //定时处理任务,重新定时以不断触发SIGALRM信号 + void timer_handler(); - void show_error(int connfd, const char *info); + void show_error(int connfd, const char *info); public: - static int *u_pipefd; - sort_timer_lst m_timer_lst; - static int u_epollfd; - int m_TIMESLOT; + static int *u_pipefd; + sort_timer_lst m_timer_lst; + static int u_epollfd; + int m_TIMESLOT; }; void cb_func(client_data *user_data); diff --git a/webserver.cpp b/webserver.cpp index a9dfae0b..31420218 100644 --- a/webserver.cpp +++ b/webserver.cpp @@ -28,7 +28,7 @@ WebServer::~WebServer() delete m_pool; } -void WebServer::init(int port, string user, string passWord, string databaseName, int log_write, +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; diff --git a/webserver.h b/webserver.h index 7381a13e..8e891cf2 100644 --- a/webserver.h +++ b/webserver.h @@ -25,8 +25,8 @@ class WebServer WebServer(); ~WebServer(); - void init(int port , string user, string passWord, string databaseName, - int log_write , int opt_linger, int trigmode, int sql_num, + void 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); void thread_pool(); @@ -39,7 +39,7 @@ class WebServer void adjust_timer(util_timer *timer); void deal_timer(util_timer *timer, int sockfd); bool dealclinetdata(); - bool dealwithsignal(bool& timeout, bool& stop_server); + bool dealwithsignal(bool &timeout, bool &stop_server); void dealwithread(int sockfd); void dealwithwrite(int sockfd);