├── .gitignore ├── LICENSE ├── README.md ├── arch.jpg ├── code ├── CGImysql │ ├── README.md │ ├── sql_connection_pool.cpp │ └── sql_connection_pool.h ├── config │ ├── config.cpp │ └── config.h ├── http │ ├── README.md │ ├── http_conn.cpp │ └── http_conn.h ├── locker │ ├── README.md │ └── locker.h ├── log │ ├── README.md │ ├── block_queue.h │ ├── log.cpp │ └── log.h ├── main.cpp ├── server │ ├── webserver.cpp │ └── webserver.h ├── threadpool │ ├── README.md │ └── threadpool.h └── timer │ ├── README.md │ ├── lst_timer.cpp │ └── lst_timer.h ├── makefile └── resource ├── README.md ├── fans.html ├── favicon.ico ├── frame.jpg ├── judge.html ├── log.html ├── logError.html ├── login.gif ├── loginnew.gif ├── picture.gif ├── picture.html ├── register.gif ├── register.html ├── registerError.html ├── registernew.gif ├── test1.jpg ├── video.gif ├── video.html ├── welcome.html ├── xxx.jpg └── xxx.mp4 /.gitignore: -------------------------------------------------------------------------------- 1 | /.vscode 2 | /log 3 | /bin 4 | /webbench -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TinyWebSever-CPP 2 | 3 | [![LICENSE](https://img.shields.io/badge/license-Apache2.0-green)]() 4 | [![ubuntu](https://img.shields.io/badge/ubuntu-18.04-%237732a8)]() 5 | [![mysql](https://img.shields.io/badge/mysql-5.7.36-blue)]() 6 | 7 | 基于C++开发的简单Web服务器:sunny: 8 | 9 | ## 更新日志 10 | - [x] 2021.11.30 发布第一个版本,来自于[qinguoyi/TinyWebServer](https://github.com/qinguoyi/TinyWebServer)。 11 | - [x] 2021.12.09 添加WebBench压力测试。 12 | - [ ] 暂无。 13 | 14 | ## 目录 15 | 1. [概述](#概述) 16 | 2. [运行](#运行) 17 | 3. [测试](#测试) 18 | 4. [庖丁解牛](#庖丁解牛) 19 | 20 | ## 概述 21 | ### 框架 22 | ![image](arch.jpg) 23 | 24 | ## 运行 25 | ### 快速运行 26 | + 服务器测试环境 27 | + Ubuntu版本18.04 28 | + MySQL版本5.7.36 29 | + 浏览器测试环境 30 | + Windows、Linux均可 31 | + Chrome、FireFox 32 | + 其他浏览器暂无测试 33 | + 测试前确认已安装MySQL数据库并启动MySQL服务 34 | ```C++ 35 | // 建立yourdb库 36 | create database yourdb; 37 | 38 | // 创建user表 39 | USE yourdb; 40 | CREATE TABLE user( 41 | username char(50) NULL, 42 | passwd char(50) NULL 43 | )ENGINE=InnoDB; 44 | 45 | // 添加数据 46 | INSERT INTO user(username, passwd) VALUES('name', 'passwd'); 47 | ``` 48 | + 修改[config.h](./code/config/config.h)中的数据库初始化信息 49 | ```C++ 50 | // 数据库用户名、密码、库名 51 | string user = "username"; 52 | string password = "password"; 53 | string database_name = "yourdb"; 54 | ``` 55 | + build 56 | ``` 57 | make server 58 | ``` 59 | + 启动server 60 | ``` 61 | ./bin/server 62 | ``` 63 | + 浏览器端 64 | ``` 65 | http://ip:port 66 | ``` 67 | 68 | ### 个性化运行 69 | 70 | ```C++ 71 | ./bin/server [-p port] [-l LOGWrite] [-m TRIGMode] [-o OPT_LINGER] [-s sql_num] [-t thread_num] [-c close_log] [-a actor_model] 72 | ``` 73 | 74 | + -p,自定义端口号 75 | + 默认8081 76 | + -l,选择日志写入方式,默认同步写入 77 | + 0,同步写入 78 | + 1,异步写入 79 | + -m,listenfd和connfd的模式组合,默认使用LT + LT 80 | + 0,表示使用LT + LT 81 | + 1,表示使用LT + ET 82 | + 2,表示使用ET + LT 83 | + 3,表示使用ET + ET 84 | + -o,优雅关闭连接,默认不使用 85 | + 0,不使用 86 | + 1,使用 87 | + -s,数据库连接数量 88 | + 默认为8 89 | + -t,线程数量 90 | + 默认为8 91 | + -c,关闭日志,默认打开 92 | + 0,打开日志 93 | + 1,关闭日志 94 | + -a,选择反应堆模型,默认Proactor 95 | + 0,Proactor模型 96 | + 1,Reactor模型 97 | 98 | 示例 99 | ```C++ 100 | ./bin/server -p 8081 -l 1 -m 0 -o 1 -s 10 -t 10 -c 1 -a 1 101 | ``` 102 | 103 | ## 测试 104 | 105 | ### 测试工具 106 | [WebBench](https://github.com/EZLippi/WebBench) 107 | 108 | ### 测试示例 109 | ``` 110 | webbench -c 10000 -t 5 http://127.0.0.1:port/ 111 | ``` 112 | + -c,客户端数 113 | + -t,运行时间 114 | 115 | ### 测试结果 116 | Note:关闭日志后再测试。 117 | ``` 118 | Webbench - Simple Web Benchmark 1.5 119 | Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software. 120 | 121 | Request: 122 | GET / HTTP/1.0 123 | User-Agent: WebBench 1.5 124 | Host: 127.0.0.1 125 | 126 | 127 | Runing info: 10000 clients, running 5 sec. 128 | 129 | Speed=3345264 pages/min, 6244448 bytes/sec. 130 | Requests: 278772 susceed, 0 failed. 131 | ``` 132 | + Proactor,LT + LT,55754 QPS 133 | 134 | ## 庖丁解牛 135 | 136 | + [小白视角:一文读懂社长的TinyWebServer](https://huixxi.github.io/2020/06/02/%E5%B0%8F%E7%99%BD%E8%A7%86%E8%A7%92%EF%BC%9A%E4%B8%80%E6%96%87%E8%AF%BB%E6%87%82%E7%A4%BE%E9%95%BF%E7%9A%84TinyWebServer/#more) 137 | + [最新版Web服务器项目详解 - 01 线程同步机制封装类](https://mp.weixin.qq.com/s?__biz=MzAxNzU2MzcwMw==&mid=2649274278&idx=3&sn=5840ff698e3f963c7855d702e842ec47&chksm=83ffbefeb48837e86fed9754986bca6db364a6fe2e2923549a378e8e5dec6e3cf732cdb198e2&scene=0&xtrack=1#rd) 138 | + [最新版Web服务器项目详解 - 02 半同步半反应堆线程池(上)](https://mp.weixin.qq.com/s?__biz=MzAxNzU2MzcwMw==&mid=2649274278&idx=4&sn=caa323faf0c51d882453c0e0c6a62282&chksm=83ffbefeb48837e841a6dbff292217475d9075e91cbe14042ad6e55b87437dcd01e6d9219e7d&scene=0&xtrack=1#rd) 139 | + [最新版Web服务器项目详解 - 03 半同步半反应堆线程池(下)](https://mp.weixin.qq.com/s/PB8vMwi8sB4Jw3WzAKpWOQ) 140 | + [最新版Web服务器项目详解 - 04 http连接处理(上)](https://mp.weixin.qq.com/s/BfnNl-3jc_x5WPrWEJGdzQ) 141 | + [最新版Web服务器项目详解 - 05 http连接处理(中)](https://mp.weixin.qq.com/s/wAQHU-QZiRt1VACMZZjNlw) 142 | + [最新版Web服务器项目详解 - 06 http连接处理(下)](https://mp.weixin.qq.com/s/451xNaSFHxcxfKlPBV3OCg) 143 | + [最新版Web服务器项目详解 - 07 定时器处理非活动连接(上)](https://mp.weixin.qq.com/s/mmXLqh_NywhBXJvI45hchA) 144 | + [最新版Web服务器项目详解 - 08 定时器处理非活动连接(下)](https://mp.weixin.qq.com/s/fb_OUnlV1SGuOUdrGrzVgg) 145 | + [最新版Web服务器项目详解 - 09 日志系统(上)](https://mp.weixin.qq.com/s/IWAlPzVDkR2ZRI5iirEfCg) 146 | + [最新版Web服务器项目详解 - 10 日志系统(下)](https://mp.weixin.qq.com/s/f-ujwFyCe1LZa3EB561ehA) 147 | + [最新版Web服务器项目详解 - 11 数据库连接池](https://mp.weixin.qq.com/s?__biz=MzAxNzU2MzcwMw==&mid=2649274326&idx=1&sn=5af78e2bf6552c46ae9ab2aa22faf839&chksm=83ffbe8eb4883798c3abb82ddd124c8100a39ef41ab8d04abe42d344067d5e1ac1b0cac9d9a3&token=1450918099&lang=zh_CN#rd) 148 | + [最新版Web服务器项目详解 - 12 注册登录](https://mp.weixin.qq.com/s?__biz=MzAxNzU2MzcwMw==&mid=2649274431&idx=4&sn=7595a70f06a79cb7abaebcd939e0cbee&chksm=83ffb167b4883871ce110aeb23e04acf835ef41016517247263a2c3ab6f8e615607858127ea6&token=1686112912&lang=zh_CN#rd) 149 | + [最新版Web服务器项目详解 - 13 踩坑与面试题](https://mp.weixin.qq.com/s?__biz=MzAxNzU2MzcwMw==&mid=2649274431&idx=1&sn=2dd28c92f5d9704a57c001a3d2630b69&chksm=83ffb167b48838715810b27b8f8b9a576023ee5c08a8e5d91df5baf396732de51268d1bf2a4e&token=1686112912&lang=zh_CN#rd) 150 | -------------------------------------------------------------------------------- /arch.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vatica/TinyWebSever-CPP/d8bacaee34a5eba32479a00457e3e0a928841179/arch.jpg -------------------------------------------------------------------------------- /code/CGImysql/README.md: -------------------------------------------------------------------------------- 1 | # 校验、数据库连接池 2 | 3 | 工作线程从数据库连接池取得一个连接,访问数据库中的数据,访问完毕后将连接交还连接池。 4 | 5 | ## 数据库连接池 6 | + 单例模式 7 | + 线程安全 8 | 9 | ## 校验 10 | + HTTP请求采用POST方式 11 | + 用户名密码校验 12 | + 线程安全 -------------------------------------------------------------------------------- /code/CGImysql/sql_connection_pool.cpp: -------------------------------------------------------------------------------- 1 | #include "sql_connection_pool.h" 2 | 3 | connection_pool::connection_pool(){ 4 | m_cur_conn = 0; 5 | m_free_conn = 0; 6 | } 7 | 8 | connection_pool::~connection_pool(){ 9 | list::iterator iter; 10 | for(iter = conn_list.begin(); iter != conn_list.end(); ++iter){ 11 | MYSQL *conn = *iter; 12 | mysql_close(conn); 13 | } 14 | } 15 | 16 | // 初始化连接池 17 | void connection_pool::init(string url, string user, string password, string database_name, int port, int max_conn, int close_log){ 18 | m_url = url; 19 | m_port = port; 20 | m_user = user; 21 | m_password = password; 22 | m_database_name = database_name; 23 | m_close_log = close_log; 24 | 25 | // 构造连接 26 | for(int i = 0; i < max_conn; ++i){ 27 | // 初始化一个mysql连接的实例对象,MYSQL* mysql_init(MYSQL *mysql); 28 | MYSQL* conn = nullptr; 29 | conn = mysql_init(conn); 30 | if(conn == nullptr){ 31 | LOG_ERROR("MySQL init Error"); 32 | exit(1); 33 | } 34 | 35 | // 与数据库引擎建立连接 36 | conn = mysql_real_connect(conn, url.c_str(), user.c_str(), password.c_str(), database_name.c_str(), port, nullptr, 0); 37 | if(conn == nullptr){ 38 | LOG_ERROR("MySQL real connect Error"); 39 | exit(1); 40 | } 41 | 42 | // 添加到连接链表 43 | conn_list.push_back(conn); 44 | ++m_free_conn; 45 | } 46 | 47 | // 创建信号量 48 | reserve = semaphore(m_free_conn); 49 | m_max_conn = m_free_conn; 50 | } 51 | 52 | // 有请求时,从数据库连接池返回一个可用连接 53 | MYSQL* connection_pool::get_connection(){ 54 | // 无空闲连接 55 | if(conn_list.size() == 0) 56 | return nullptr; 57 | 58 | MYSQL *conn = nullptr; 59 | // 等待空闲连接 60 | reserve.wait(); 61 | // 加互斥锁 62 | lock.lock(); 63 | // 取连接池第一个连接 64 | conn = conn_list.front(); 65 | conn_list.pop_front(); 66 | 67 | --m_free_conn; 68 | ++m_cur_conn; 69 | 70 | lock.unlock(); 71 | return conn; 72 | } 73 | 74 | // 释放当前使用的连接,成功返回true 75 | bool connection_pool::release_connection(MYSQL *conn){ 76 | if(conn == nullptr) 77 | return false; 78 | 79 | lock.lock(); 80 | conn_list.push_back(conn); 81 | ++m_free_conn; 82 | --m_cur_conn; 83 | lock.unlock(); 84 | 85 | reserve.post(); 86 | return true; 87 | } 88 | 89 | // 当前空闲连接数 90 | int connection_pool::get_freeconn(){ 91 | return this->m_free_conn; 92 | } 93 | 94 | // 从连接池获取一个数据库连接 95 | connectionRAII::connectionRAII(MYSQL **conn, connection_pool *connPool){ 96 | *conn = connPool->get_connection(); 97 | connRAII = *conn; 98 | poolRAII = connPool; 99 | } 100 | 101 | // 释放持有的数据库连接 102 | connectionRAII::~connectionRAII(){ 103 | poolRAII->release_connection(connRAII); 104 | } -------------------------------------------------------------------------------- /code/CGImysql/sql_connection_pool.h: -------------------------------------------------------------------------------- 1 | #ifndef CONNECTION_POOL_H 2 | #define CONNECTION_POOL_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "../locker/locker.h" 12 | #include "../log/log.h" 13 | 14 | using namespace std; 15 | 16 | // 数据库连接池 17 | class connection_pool{ 18 | public: 19 | ~connection_pool(); 20 | 21 | // 单例模式 22 | static connection_pool* get_instance(){ 23 | static connection_pool connPool; 24 | return &connPool; 25 | } 26 | 27 | void init(string url, string user, string password, string database_name, int port, int max_conn, int close_log); 28 | MYSQL* get_connection(); 29 | bool release_connection(MYSQL *conn); 30 | int get_freeconn(); 31 | 32 | private: 33 | connection_pool(); 34 | 35 | string m_url; // 主机地址 36 | string m_port; // 数据库端口号 37 | string m_user; // 数据库用户名 38 | string m_password; // 数据库密码 39 | string m_database_name; // 数据库名 40 | int m_close_log; // 日志开关 41 | 42 | int m_max_conn; // 最大连接数 43 | int m_cur_conn; // 当前已使用连接数 44 | int m_free_conn; // 当前空闲连接数 45 | mutexlocker lock; // 互斥锁 46 | list conn_list; // 连接池 47 | semaphore reserve; // 信号量,指示是否有空闲连接 48 | }; 49 | 50 | // 资源获取即初始化 51 | class connectionRAII{ 52 | public: 53 | connectionRAII(MYSQL **conn, connection_pool *connPool); 54 | ~connectionRAII(); 55 | 56 | private: 57 | MYSQL *connRAII; 58 | connection_pool *poolRAII; 59 | }; 60 | 61 | #endif -------------------------------------------------------------------------------- /code/config/config.cpp: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | 3 | // 解析输入参数 4 | void Config::parse_arg(int argc, char*argv[]){ 5 | int opt; 6 | const char *str = "p:c:l:s:t:a:m:o:"; // 一个冒号表示必有一个参数 7 | while((opt = getopt(argc, argv, str)) != -1){ 8 | switch (opt){ 9 | case 'p': port = atoi(optarg); break; 10 | case 'c': close_log = atoi(optarg); break; 11 | case 'l': async_log = atoi(optarg); break; 12 | case 's': sql_num = atoi(optarg); break; 13 | case 't': thread_num = atoi(optarg); break; 14 | case 'a': actor_model = atoi(optarg); break; 15 | case 'm': trig_mode = atoi(optarg); break; 16 | case 'o': opt_linger = atoi(optarg); break; 17 | default: throw invalid_argument("check your arguments"); break; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /code/config/config.h: -------------------------------------------------------------------------------- 1 | #ifndef CONFIG_H 2 | #define CONFIG_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace std; 9 | 10 | class Config{ 11 | public: 12 | Config(){} 13 | ~Config(){} 14 | 15 | void parse_arg(int argc, char *argv[]); 16 | 17 | public: 18 | string user = "root"; // 数据库用户名 19 | string password = "4869"; // 数据库密码 20 | string database_name = "webserver"; // 数据库名 21 | const char *root = "./resource"; // 资源根目录 22 | 23 | int port = 8081; // 端口号,默认8081 24 | int close_log = 0; // 关闭日志,默认不关闭,1关闭 25 | int async_log = 0; // 日志写入方式,默认同步,1异步 26 | int sql_num = 8; // 数据库连接池数量,默认8 27 | int thread_num = 8; // 线程池内的线程数量,默认8 28 | int actor_model = 0; // 并发模型,默认proactor,1reactor 29 | int trig_mode = 0; // 触发组合模式,默认listenfd LT + connfd LT,1 LT + ET,2 ET + LT,3 ET + ET 30 | int opt_linger = 0; // 优雅关闭链接,默认不使用,1使用 31 | }; 32 | 33 | #endif -------------------------------------------------------------------------------- /code/http/README.md: -------------------------------------------------------------------------------- 1 | # 处理HTTP请求 2 | 3 | ## epoll 4 | ```c++ 5 | int epoll_create(int size) 6 | ``` 7 | 创建一个指示epoll内核事件表的文件描述符,该描述符将用作其他epoll系统调用的第一个参数,size不起作用。 8 | 9 | ```c++ 10 | int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) 11 | ``` 12 | 用于操作内核事件表监控的文件描述符上的事件:注册、修改、删除。 13 | 14 | ```c++ 15 | struct epoll_event { 16 | __uint32_t events; /* Epoll events */ 17 | epoll_data_t data; /* User data variable */ 18 | }; 19 | ``` 20 | EPOLLIN:表示对应的文件描述符可以读(包括对端SOCKET正常关闭)\ 21 | EPOLLOUT:表示对应的文件描述符可以写\ 22 | EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)\ 23 | EPOLLERR:表示对应的文件描述符发生错误\ 24 | EPOLLHUP:表示对应的文件描述符被挂断\ 25 | EPOLLET:将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)而言的\ 26 | EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里,防止多个线程处理一个socket 27 | 28 | ```c++ 29 | int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) 30 | ``` 31 | 用于等待所监控文件描述符上有事件的产生,返回就绪的文件描述符个数。 32 | 33 | 34 | ## HTTP报文 35 | 36 | ### 请求报文 37 | HTTP请求报文由请求行(request line)、请求头部(header)、空行和请求数据四个部分组成。 38 | ``` 39 | 请求方法 URL 协议版本\r\n (请求行) 40 | 头部字段名:值\r\n(请求头部) 41 | ... 42 | 头部字段名:值\r\n 43 | \r\n 44 | xxxxx (请求数据) 45 | ``` 46 | 47 | ### 响应报文 48 | HTTP响应也由四个部分组成,分别是:状态行、消息报头、空行和响应正文。 49 | ``` 50 | 协议版本 状态码 状态码描述\r\n (状态行) 51 | 头部字段名:值\r\n(消息报头) 52 | ... 53 | 头部字段名:值\r\n 54 | \r\n 55 | xxxxx (响应正文) 56 | ``` 57 | 58 | ### 报文处理流程 59 | + 浏览器端发出http连接请求,主线程创建http对象接收请求并将所有数据读入对应buffer,将该对象插入任务队列,工作线程从任务队列中取出一个任务进行处理。 60 | + 工作线程取出任务后,调用process_read函数,通过主、从状态机对请求报文进行解析。 61 | + 解析完之后,跳转do_request函数生成响应报文,通过process_write写入buffer,返回给浏览器端。 -------------------------------------------------------------------------------- /code/http/http_conn.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "http_conn.h" 5 | 6 | const char *ok_200_title = "OK"; 7 | const char *error_400_title = "Bad Request"; 8 | const char *error_400_form = "Yor request has bad syntax or is inherently impossible to staisfy.\n"; 9 | const char *error_403_title = "Forbidden"; 10 | const char *error_403_form = "You do not have permission to get file form this server.\n"; 11 | const char *error_404_title = "Not Found"; 12 | const char *error_404_form = "The requested file was not found on this server.\n"; 13 | const char *error_500_title = "Internal Error"; 14 | const char *error_500_form = "There was an unusual problem serving the request file.\n"; 15 | 16 | mutexlocker m_lock; // 表互斥锁 17 | map users; // 内存用户表 18 | 19 | void http_conn::initmysql_result(connection_pool *connPool){ 20 | // 从数据库连接池取一个连接 21 | MYSQL *mysql = nullptr; 22 | connectionRAII mysqlcon(&mysql, connPool); 23 | 24 | // 在user表中检索 25 | if(mysql_query(mysql, "SELECT username, password FROM user")){ 26 | LOG_ERROR("SELECT error:%s\n", mysql_error(mysql)); 27 | } 28 | 29 | // 从表中检索完整的结果集 30 | MYSQL_RES *result = mysql_store_result(mysql); 31 | 32 | // 结果集的列数 33 | // int num_fields = mysql_num_fields(result); 34 | 35 | // 所有字段结构的数组 36 | // MYSQL_FIELD *fields = mysql_fetch_field(result); 37 | 38 | // 将用户名和密码存入map中 39 | while(MYSQL_ROW row = mysql_fetch_row(result)){ 40 | string temp1(row[0]); 41 | string temp2(row[1]); 42 | users[temp1] = temp2; 43 | } 44 | } 45 | 46 | // 对文件描述符设置非阻塞 47 | int setnonblocking(int fd){ 48 | int old_option = fcntl(fd, F_GETFL); 49 | int new_option = old_option | O_NONBLOCK; 50 | fcntl(fd, F_SETFL, new_option); 51 | return old_option; 52 | } 53 | 54 | // 将内核事件表注册读事件,ET模式,选择开启EPOLLONESHOT 55 | void addfd(int epollfd, int fd, bool one_shot, int TRIGMode){ 56 | epoll_event event; 57 | event.data.fd = fd; 58 | 59 | if(TRIGMode == 1) 60 | // 读事件、ET模式、对方断开连接 61 | event.events = EPOLLIN | EPOLLET | EPOLLRDHUP; 62 | else 63 | event.events = EPOLLIN | EPOLLRDHUP; 64 | 65 | if(one_shot) 66 | event.events |= EPOLLONESHOT; 67 | 68 | // 注册事件 69 | epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event); 70 | setnonblocking(fd); 71 | } 72 | 73 | // 从内核事件表删除描述符,关闭描述符 74 | void removefd(int epollfd, int fd){ 75 | epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, 0); 76 | close(fd); 77 | } 78 | 79 | // 将事件重置为EPOLLONESHOT 80 | void modfd(int epollfd, int fd, int ev, int TRIGMode){ 81 | epoll_event event; 82 | event.data.fd = fd; 83 | 84 | if(TRIGMode == 1) 85 | event.events = ev | EPOLLET | EPOLLONESHOT | EPOLLRDHUP; 86 | else 87 | event.events = ev | EPOLLONESHOT | EPOLLRDHUP; 88 | 89 | epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &event); 90 | } 91 | 92 | int http_conn::m_user_count = 0; 93 | int http_conn::m_epollfd = -1; 94 | 95 | // 关闭一个客户连接 96 | void http_conn::close_conn(bool real_close){ 97 | if(real_close && (m_sockfd != -1)){ 98 | printf("close %d\n", m_sockfd); 99 | removefd(m_epollfd, m_sockfd); 100 | m_sockfd = -1; 101 | m_user_count--; 102 | } 103 | } 104 | 105 | // 初始化连接 106 | void http_conn::init(int sockfd, const sockaddr_in &addr, const char *root, int TRIGMode, 107 | int close_log, string user, string passwd, string sqlname){ 108 | m_sockfd = sockfd; 109 | m_address = addr; 110 | 111 | addfd(m_epollfd, sockfd, true, m_TRIGMode); 112 | m_user_count++; 113 | 114 | doc_root = root; 115 | m_TRIGMode = TRIGMode; 116 | m_close_log = close_log; 117 | 118 | strcpy(sql_user, user.c_str()); 119 | strcpy(sql_passwd, passwd.c_str()); 120 | strcpy(sql_name, sqlname.c_str()); 121 | 122 | init(); 123 | } 124 | 125 | // 初始化新接受的连接 126 | void http_conn::init(){ 127 | mysql = nullptr; 128 | bytes_to_send = 0; 129 | bytes_have_send = 0; 130 | m_check_state = CHECK_STATE_REQUESTLINE; 131 | m_linger = false; 132 | m_method = GET; 133 | m_url = 0; 134 | m_version = 0; 135 | m_host = 0; 136 | m_start_line = 0; 137 | m_checked_idx = 0; 138 | m_read_idx = 0; 139 | m_write_idx = 0; 140 | cgi = 0; 141 | m_state = 0; 142 | timer_flag = 0; 143 | improv = 0; 144 | 145 | memset(m_read_buf, 0, READ_BUFFER_SIZE); 146 | memset(m_write_buf, 0, WRITE_BUFFER_SIZE); 147 | memset(m_real_file, 0, FILENAME_LEN); 148 | } 149 | 150 | // 从状态机,用于分析一行内容 151 | http_conn::LINE_STATUS http_conn::parse_line(){ 152 | char temp; 153 | //m_read_idx指向缓冲区m_read_buf的数据末尾的下一个字节 154 | //m_checked_idx指向从状态机当前正在分析的字节 155 | for(; m_checked_idx < m_read_idx; ++m_checked_idx){ 156 | temp = m_read_buf[m_checked_idx]; 157 | // 可能读取到完整行 158 | if(temp == '\r'){ 159 | // 不完整 160 | if((m_checked_idx + 1) == m_read_idx){ 161 | return LINE_OPEN; 162 | } 163 | // 完整,\r\n替换为\0\0 164 | else if(m_read_buf[m_checked_idx + 1] == '\n'){ 165 | m_read_buf[m_checked_idx++] = '\0'; 166 | m_read_buf[m_checked_idx++] = '\0'; 167 | return LINE_OK; 168 | } 169 | // 格式错误 170 | return LINE_BAD; 171 | } 172 | // 可能读取到完整行 173 | else if(temp == '\n'){ 174 | // 完整,\r\n替换为\0\0 175 | if(m_checked_idx > 1 && m_read_buf[m_checked_idx - 1] == '\r'){ 176 | m_read_buf[m_checked_idx - 1] = '\0'; 177 | m_read_buf[m_checked_idx++] = '\0'; 178 | return LINE_OK; 179 | } 180 | return LINE_BAD; 181 | } 182 | } 183 | // 继续接收 184 | return LINE_OPEN; 185 | } 186 | 187 | // 循环读取客户数据,直到无数据可读或对方关闭连接 188 | bool http_conn::read_once(){ 189 | if(m_read_idx >= READ_BUFFER_SIZE){ 190 | return false; 191 | } 192 | int bytes_read = 0; 193 | 194 | // LT模式 195 | if(m_TRIGMode == 0){ 196 | // 读取数据到缓冲区 197 | bytes_read = recv(m_sockfd, m_read_buf + m_read_idx, READ_BUFFER_SIZE - m_read_idx, 0); 198 | m_read_idx += bytes_read; 199 | 200 | if(bytes_read <= 0){ 201 | return false; 202 | } 203 | return true; 204 | } 205 | // ET模式 206 | else{ 207 | while(true){ 208 | bytes_read = recv(m_sockfd, m_read_buf + m_read_idx, READ_BUFFER_SIZE - m_read_idx, 0); 209 | // 无数据可读 210 | if(bytes_read == -1){ 211 | // 非阻塞、连接正常 212 | if(errno == EAGAIN || errno == EWOULDBLOCK) 213 | break; 214 | return false; 215 | } 216 | // 另一端已关闭 217 | else if(bytes_read == 0){ 218 | return false; 219 | } 220 | m_read_idx += bytes_read; 221 | } 222 | return true; 223 | } 224 | } 225 | 226 | // 解析HTTP请求行,获得请求方法、目标url及http版本号 227 | http_conn::HTTP_CODE http_conn::parse_request_line(char *text){ 228 | // 寻找空格和\t位置 229 | m_url = strpbrk(text, " \t"); 230 | if(!m_url){ 231 | return BAD_REQUEST; 232 | } 233 | // 将该位置改为\0,方便取出 234 | *m_url++ = '\0'; 235 | // 取出请求方法并比较 236 | char *method = text; 237 | if(strcasecmp(method, "GET") == 0) 238 | m_method = GET; 239 | else if(strcasecmp(method, "POST") == 0){ 240 | m_method = POST; 241 | cgi = 1; 242 | } 243 | else return BAD_REQUEST; 244 | 245 | // 跳过空格和\t 246 | m_url += strspn(m_url, " \t"); 247 | // 再寻找空格和\t 248 | m_version = strpbrk(m_url, " \t"); 249 | if(!m_version) 250 | return BAD_REQUEST; 251 | *m_version++ = '\0'; 252 | // 跳过空格和\t 253 | m_version += strspn(m_version, " \t"); 254 | 255 | // 仅支持HTTP1.1 256 | if(strcasecmp(m_version, "HTTP/1.1") != 0) 257 | return BAD_REQUEST; 258 | // 去除http:// 259 | if(strncasecmp(m_url, "http://", 7) == 0){ 260 | m_url += 7; 261 | m_url = strchr(m_url, '/'); 262 | } 263 | // 去除https:// 264 | if(strncasecmp(m_url, "https://", 8) == 0){ 265 | m_url += 8; 266 | m_url = strchr(m_url, '/'); 267 | } 268 | 269 | if(!m_url || m_url[0] != '/') 270 | return BAD_REQUEST; 271 | // url为/,显示主页 272 | if(strlen(m_url) == 1) 273 | strcat(m_url, "judge.html"); 274 | 275 | // 主状态机状态修改为处理请求头 276 | m_check_state = CHECK_STATE_HEADER; 277 | return NO_REQUEST; 278 | } 279 | 280 | // 解析HTTP请求的一个头部信息 281 | http_conn::HTTP_CODE http_conn::parse_headers(char *text){ 282 | // 首位为\0是空行 283 | if(text[0] == '\0'){ 284 | if(m_content_length != 0){ 285 | // POST继续解析消息体 286 | m_check_state = CHECK_STATE_CONTENT; 287 | return NO_REQUEST; 288 | } 289 | // GET请求解析完成 290 | return GET_REQUEST; 291 | } 292 | // 连接字段 293 | else if(strncasecmp(text, "connection:", 11) == 0){ 294 | text += 11; 295 | text += strspn(text, " \t"); 296 | if(strcasecmp(text, "keep-alive") == 0){ 297 | // 长连接 298 | m_linger = true; 299 | } 300 | } 301 | // 内容长度字段 302 | else if(strncasecmp(text, "Content-length:", 15) == 0){ 303 | text += 15; 304 | text += strspn(text, "\t"); 305 | m_content_length = atol(text); 306 | } 307 | // Host字段,请求站点 308 | else if(strncasecmp(text, "Host:", 5) == 0){ 309 | text += 5; 310 | text += strspn(text, " \t"); 311 | m_host = text; 312 | } 313 | else{ 314 | LOG_INFO("oop! unknow header: %s", text); 315 | } 316 | return NO_REQUEST; 317 | } 318 | 319 | // 判断HTTP请求是否被完全读入 320 | http_conn::HTTP_CODE http_conn::parse_content(char *text){ 321 | // 判断buffer中是否读取了消息体 322 | if(m_read_idx >= (m_content_length + m_checked_idx)){ 323 | // 最后填充\0 324 | text[m_content_length] = '\0'; 325 | // 获取消息体 326 | m_string = text; 327 | return GET_REQUEST; 328 | } 329 | return NO_REQUEST; 330 | } 331 | 332 | http_conn::HTTP_CODE http_conn::process_read() 333 | { 334 | // 初始化状态 335 | LINE_STATUS line_status = LINE_OK; 336 | HTTP_CODE ret = NO_REQUEST; 337 | char *text = nullptr; 338 | 339 | // 消息体末尾没有字符,POST请求报文不能用从状态机LINE_OK状态判断 340 | // 加上&& line_status == LINE_OK防止陷入死循环 341 | while((m_check_state == CHECK_STATE_CONTENT && line_status == LINE_OK) || ((line_status = parse_line()) == LINE_OK)){ 342 | // 取一行数据 343 | text = get_line(); 344 | m_start_line = m_checked_idx; 345 | LOG_INFO("get line: %s", text); 346 | 347 | // 主状态机状态 348 | switch (m_check_state){ 349 | case CHECK_STATE_REQUESTLINE:{ 350 | ret = parse_request_line(text); 351 | if(ret == BAD_REQUEST) 352 | return BAD_REQUEST; 353 | break; 354 | } 355 | case CHECK_STATE_HEADER:{ 356 | ret = parse_headers(text); 357 | if(ret == BAD_REQUEST) 358 | return BAD_REQUEST; 359 | // 完整解析GET请求 360 | else if(ret == GET_REQUEST) 361 | return do_request(); 362 | break; 363 | } 364 | case CHECK_STATE_CONTENT:{ 365 | ret = parse_content(text); 366 | // 完整解析POST请求 367 | if(ret == GET_REQUEST) 368 | return do_request(); 369 | // 完成报文解析,防止进入循环 370 | line_status = LINE_OPEN; 371 | break; 372 | } 373 | default: 374 | return INTERNAL_ERROR; 375 | } 376 | } 377 | return NO_REQUEST; 378 | } 379 | 380 | http_conn::HTTP_CODE http_conn::do_request(){ 381 | // 网站根目录 382 | strcpy(m_real_file, doc_root); 383 | int len = strlen(doc_root); 384 | 385 | // 找到最后一个/的位置 386 | const char *p = strrchr(m_url, '/'); 387 | 388 | // POST请求,实现登录和注册校验 389 | if(cgi == 1 && (*(p + 1) == '2' || *(p + 1) == '3')){ 390 | char *m_url_real = (char *)malloc(sizeof(char) * 200); 391 | strcpy(m_url_real, "/"); 392 | strcat(m_url_real, m_url + 2); 393 | strncpy(m_real_file + len, m_url_real, FILENAME_LEN - len - 1); 394 | free(m_url_real); 395 | 396 | // 将用户名和密码提取出来 397 | // user=123&password=123 398 | char name[100], password[100]; 399 | int i; 400 | for(i = 5; m_string[i] != '&'; ++i) 401 | name[i - 5] = m_string[i]; 402 | name[i - 5] = '\0'; 403 | 404 | int j = 0; 405 | for(i = i + 10; m_string[i] != '\0'; ++i, ++j) 406 | password[j] = m_string[i]; 407 | password[j] = '\0'; 408 | 409 | if(*(p + 1) == '3'){ 410 | //如果是注册,先检测数据库中是否有重名的 411 | //没有重名的,进行增加数据 412 | char *sql_insert = (char *)malloc(sizeof(char) * 200); 413 | strcpy(sql_insert, "INSERT INTO user(username, password) VALUES("); 414 | strcat(sql_insert, "'"); 415 | strcat(sql_insert, name); 416 | strcat(sql_insert, "', '"); 417 | strcat(sql_insert, password); 418 | strcat(sql_insert, "')"); 419 | 420 | if(users.find(name) == users.end()){ 421 | m_lock.lock(); 422 | int res = mysql_query(mysql, sql_insert); 423 | users.insert(pair(name, password)); 424 | m_lock.unlock(); 425 | 426 | if(!res) 427 | strcpy(m_url, "/log.html"); 428 | else 429 | strcpy(m_url, "/registerError.html"); 430 | } 431 | else 432 | strcpy(m_url, "/registerError.html"); 433 | } 434 | //如果是登录,直接判断 435 | //若浏览器端输入的用户名和密码在表中可以查找到,返回1,否则返回0 436 | else if(*(p + 1) == '2') 437 | { 438 | if(users.find(name) != users.end() && users[name] == password) 439 | strcpy(m_url, "/welcome.html"); 440 | else 441 | strcpy(m_url, "/logError.html"); 442 | } 443 | } 444 | // GET请求,跳转到注册页面 445 | if(*(p + 1) == '0'){ 446 | char *m_url_real = (char *)malloc(sizeof(char) * 200); 447 | strcpy(m_url_real, "/register.html"); 448 | strncpy(m_real_file + len, m_url_real, strlen(m_url_real)); 449 | 450 | free(m_url_real); 451 | } 452 | // GET请求,跳转到登录页面 453 | else if(*(p + 1) == '1'){ 454 | char *m_url_real = (char *)malloc(sizeof(char) * 200); 455 | strcpy(m_url_real, "/log.html"); 456 | strncpy(m_real_file + len, m_url_real, strlen(m_url_real)); 457 | 458 | free(m_url_real); 459 | } 460 | // POST请求,图片页面 461 | else if(*(p + 1) == '5'){ 462 | char *m_url_real = (char *)malloc(sizeof(char) * 200); 463 | strcpy(m_url_real, "/picture.html"); 464 | strncpy(m_real_file + len, m_url_real, strlen(m_url_real)); 465 | 466 | free(m_url_real); 467 | } 468 | // POST请求,视频页面 469 | else if(*(p + 1) == '6'){ 470 | char *m_url_real = (char *)malloc(sizeof(char) * 200); 471 | strcpy(m_url_real, "/video.html"); 472 | strncpy(m_real_file + len, m_url_real, strlen(m_url_real)); 473 | 474 | free(m_url_real); 475 | } 476 | // POST请求,关注页面 477 | else if(*(p + 1) == '7'){ 478 | char *m_url_real = (char *)malloc(sizeof(char) * 200); 479 | strcpy(m_url_real, "/fans.html"); 480 | strncpy(m_real_file + len, m_url_real, strlen(m_url_real)); 481 | 482 | free(m_url_real); 483 | } 484 | else 485 | // 都不是则直接拼接 486 | strncpy(m_real_file + len, m_url, FILENAME_LEN - len - 1); 487 | 488 | // 获取不到文件信息,资源不存在 489 | if(stat(m_real_file, &m_file_stat) < 0) 490 | return NO_RESOURCE; 491 | 492 | // 文件是否可读 493 | if(!(m_file_stat.st_mode & S_IROTH)) 494 | return FORBIDDEN_REQUEST; 495 | 496 | // 文件是否为目录 497 | if(S_ISDIR(m_file_stat.st_mode)) 498 | return BAD_REQUEST; 499 | 500 | // 以只读方式打开文件并映射到内存中 501 | int fd = open(m_real_file, O_RDONLY); 502 | m_file_address = (char *)mmap(0, m_file_stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0); 503 | // 关闭文件描述符 504 | close(fd); 505 | return FILE_REQUEST; 506 | } 507 | 508 | // 关闭文件映射 509 | void http_conn::unmap(){ 510 | if(m_file_address){ 511 | munmap(m_file_address, m_file_stat.st_size); 512 | m_file_address = 0; 513 | } 514 | } 515 | 516 | // 向客户端写响应 517 | bool http_conn::write(){ 518 | int temp = 0; 519 | 520 | // 响应报文为空 521 | if(bytes_to_send == 0){ 522 | modfd(m_epollfd, m_sockfd, EPOLLIN, m_TRIGMode); 523 | init(); 524 | return true; 525 | } 526 | 527 | while(1){ 528 | // 发送响应 529 | temp = writev(m_sockfd, m_iv, m_iv_count); 530 | 531 | if(temp < 0){ 532 | // 重试,继续监听写事件 533 | if(errno == EAGAIN) 534 | { 535 | modfd(m_epollfd, m_sockfd, EPOLLOUT, m_TRIGMode); 536 | // 不要断开连接 537 | return true; 538 | } 539 | // 断开连接 540 | unmap(); 541 | return false; 542 | } 543 | 544 | // 正常发送 545 | bytes_have_send += temp; 546 | bytes_to_send -= temp; 547 | // 第一个元素已经发送完 548 | if(bytes_have_send >= m_iv[0].iov_len){ 549 | m_iv[0].iov_len = 0; 550 | m_iv[1].iov_base = m_file_address + (bytes_have_send - m_write_idx); 551 | m_iv[1].iov_len = bytes_to_send; 552 | } 553 | // 继续发送第一个元素 554 | else{ 555 | m_iv[0].iov_base = m_write_buf + bytes_have_send; 556 | m_iv[0].iov_len = m_iv[0].iov_len - bytes_have_send; 557 | } 558 | 559 | // 全部发送完毕 560 | if(bytes_to_send <= 0){ 561 | unmap(); 562 | modfd(m_epollfd, m_sockfd, EPOLLIN, m_TRIGMode); 563 | 564 | // 长连接 565 | if(m_linger){ 566 | init(); 567 | return true; 568 | } 569 | else{ 570 | return false; 571 | } 572 | } 573 | } 574 | } 575 | 576 | // 写响应 577 | bool http_conn::add_response(const char *format, ...){ 578 | // 写入内容超过buffer长度则报错 579 | if(m_write_idx >= WRITE_BUFFER_SIZE) 580 | return false; 581 | 582 | // 可变参数列表 583 | va_list arg_list; 584 | // 初始化列表 585 | va_start(arg_list, format); 586 | 587 | // 按照format写入缓存 588 | int len = vsnprintf(m_write_buf + m_write_idx, WRITE_BUFFER_SIZE - 1 - m_write_idx, format, arg_list); 589 | // 超出缓存则报错 590 | if(len >= (WRITE_BUFFER_SIZE - 1 - m_write_idx)){ 591 | va_end(arg_list); 592 | return false; 593 | } 594 | 595 | // 更新idx 596 | m_write_idx += len; 597 | va_end(arg_list); 598 | 599 | LOG_INFO("add response: %s", m_write_buf); 600 | return true; 601 | } 602 | 603 | // 状态行 604 | bool http_conn::add_status_line(int status, const char *title){ 605 | return add_response("%s %d %s\r\n", "HTTP/1.1", status, title); 606 | } 607 | 608 | // 响应报头 609 | bool http_conn::add_headers(int content_len){ 610 | return add_content_length(content_len) && add_linger() && 611 | add_blank_line(); 612 | } 613 | 614 | // 消息报头 615 | bool http_conn::add_content_length(int content_len){ 616 | return add_response("Content-Length:%d\r\n", content_len); 617 | } 618 | 619 | bool http_conn::add_content_type(){ 620 | return add_response("Content-Type:%s\r\n", "text/html"); 621 | } 622 | 623 | bool http_conn::add_linger(){ 624 | return add_response("Connection:%s\r\n", (m_linger == true) ? "keep-alive" : "close"); 625 | } 626 | 627 | // 空行 628 | bool http_conn::add_blank_line(){ 629 | return add_response("%s", "\r\n"); 630 | } 631 | 632 | // 响应正文 633 | bool http_conn::add_content(const char *content){ 634 | return add_response("%s", content); 635 | } 636 | 637 | // 向缓冲区写响应 638 | bool http_conn::process_write(HTTP_CODE ret){ 639 | switch (ret){ 640 | case INTERNAL_ERROR:{ 641 | add_status_line(500, error_500_title); 642 | add_headers(strlen(error_500_form)); 643 | if(!add_content(error_500_form)) 644 | return false; 645 | break; 646 | } 647 | case BAD_REQUEST:{ 648 | add_status_line(404, error_404_title); 649 | add_headers(strlen(error_404_form)); 650 | if(!add_content(error_404_form)) 651 | return false; 652 | break; 653 | } 654 | case FORBIDDEN_REQUEST:{ 655 | add_status_line(403, error_403_title); 656 | add_headers(strlen(error_403_form)); 657 | if(!add_content(error_403_form)) 658 | return false; 659 | break; 660 | } 661 | case FILE_REQUEST:{ 662 | add_status_line(200, ok_200_title); 663 | if(m_file_stat.st_size != 0){ 664 | add_headers(m_file_stat.st_size); 665 | // 第一个元素指向响应报文写缓冲 666 | m_iv[0].iov_base = m_write_buf; 667 | m_iv[0].iov_len = m_write_idx; 668 | // 第二个元素指向mmap返回的文件指针 669 | m_iv[1].iov_base = m_file_address; 670 | m_iv[1].iov_len = m_file_stat.st_size; 671 | m_iv_count = 2; 672 | bytes_to_send = m_write_idx + m_file_stat.st_size; 673 | return true; 674 | } 675 | // 资源大小为0则返回空白html 676 | else{ 677 | const char *ok_string = ""; 678 | add_headers(strlen(ok_string)); 679 | if(!add_content(ok_string)) 680 | return false; 681 | } 682 | } 683 | default: 684 | return false; 685 | } 686 | // 除FILE_REQUEST外只指向响应报文缓冲 687 | m_iv[0].iov_base = m_write_buf; 688 | m_iv[0].iov_len = m_write_idx; 689 | m_iv_count = 1; 690 | bytes_to_send = m_write_idx; 691 | return true; 692 | } 693 | 694 | // http处理 695 | void http_conn::process(){ 696 | HTTP_CODE read_ret = process_read(); 697 | // 请求不完整,继续注册读事件 698 | if(read_ret == NO_REQUEST){ 699 | modfd(m_epollfd, m_sockfd, EPOLLIN, m_TRIGMode); 700 | return; 701 | } 702 | bool write_ret = process_write(read_ret); 703 | if(!write_ret){ 704 | close_conn(); 705 | } 706 | // 准备好写缓冲,加入监听可写事件 707 | modfd(m_epollfd, m_sockfd, EPOLLOUT, m_TRIGMode); 708 | } -------------------------------------------------------------------------------- /code/http/http_conn.h: -------------------------------------------------------------------------------- 1 | #ifndef HTTP_CONN_H 2 | #define HTTP_CONN_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "../locker/locker.h" 26 | #include "../CGImysql/sql_connection_pool.h" 27 | #include "../timer/lst_timer.h" 28 | #include "../log/log.h" 29 | 30 | class http_conn{ 31 | public: 32 | // 文件名称长度 33 | static const int FILENAME_LEN = 200; 34 | // 读缓冲大小 35 | static const int READ_BUFFER_SIZE = 2048; 36 | // 写缓冲大小 37 | static const int WRITE_BUFFER_SIZE = 1024; 38 | // 请求方法 39 | enum METHOD{ 40 | GET = 0, POST, HEAD, PUT, DELETE, TRACE, OPTIONS, CONNECT, PATH 41 | }; 42 | // 主状态机状态 43 | enum CHECK_STATE{ 44 | CHECK_STATE_REQUESTLINE = 0, 45 | CHECK_STATE_HEADER, 46 | CHECK_STATE_CONTENT 47 | }; 48 | // 报文解析结果 49 | enum HTTP_CODE{ 50 | NO_REQUEST = 0, GET_REQUEST, BAD_REQUEST, NO_RESOURCE, FORBIDDEN_REQUEST, FILE_REQUEST, INTERNAL_ERROR, CLOSED_CONNECTION 51 | }; 52 | // 从状态机状态 53 | enum LINE_STATUS{ 54 | LINE_OK = 0, LINE_BAD, LINE_OPEN 55 | }; 56 | 57 | public: 58 | http_conn(){} 59 | ~http_conn(){} 60 | 61 | public: 62 | // 初始化连接 63 | void init(int sockfd, const sockaddr_in &addr, const char *, int, int, string user, string passwd, string sqlname); 64 | // 关闭连接 65 | void close_conn(bool real_close=true); 66 | void process(); 67 | // 读取客户端全部数据 68 | bool read_once(); 69 | // 写响应报文 70 | bool write(); 71 | sockaddr_in* get_address(){ 72 | return &m_address; 73 | } 74 | // 初始化数据库读取表 75 | void initmysql_result(connection_pool *connPool); 76 | int timer_flag; // reactor是否处理数据 77 | int improv; // reactor是否处理失败 78 | 79 | private: 80 | void init(); 81 | // 从m_read_buf读取,处理请求报文 82 | HTTP_CODE process_read(); 83 | // 向m_write_buf写入响应报文 84 | bool process_write(HTTP_CODE ret); 85 | // 主状态机解析请求行数据 86 | HTTP_CODE parse_request_line(char *text); 87 | // 主状态机解析请求头数据 88 | HTTP_CODE parse_headers(char *text); 89 | // 主状态机解析请求内容 90 | HTTP_CODE parse_content(char *text); 91 | // 生成响应报文 92 | HTTP_CODE do_request(); 93 | // 获得未解读数据位置 94 | //m_start_line是行在buffer中的起始位置,将该位置后面的数据赋给text 95 | //此时从状态机已提前将一行的末尾字符\r\n变为\0\0,所以text可以直接取出完整的行进行解析 96 | char* get_line(){return m_read_buf + m_start_line;}; 97 | // 从状态机读取一行 98 | LINE_STATUS parse_line(); 99 | void unmap(); 100 | 101 | // 生成具体响应报文 102 | bool add_response(const char *format, ...); 103 | bool add_content(const char *content); 104 | bool add_status_line(int status, const char *title); 105 | bool add_headers(int content_length); 106 | bool add_content_type(); 107 | bool add_content_length(int content_length); 108 | bool add_linger(); 109 | bool add_blank_line(); 110 | 111 | public: 112 | static int m_epollfd; // epoll事件表 113 | static int m_user_count; // 客户数量 114 | MYSQL *mysql; // 数据库连接 115 | int m_state; // reactor区分读写任务,0读,1写 116 | 117 | private: 118 | int m_sockfd; // 客户socket 119 | sockaddr_in m_address; // 客户地址 120 | char m_read_buf[READ_BUFFER_SIZE]; 121 | int m_read_idx; // 已读数据结尾 122 | int m_checked_idx; // 解析进行处 123 | int m_start_line; // 解析开始处 124 | char m_write_buf[WRITE_BUFFER_SIZE]; 125 | int m_write_idx; // 已写数据结尾 126 | CHECK_STATE m_check_state; // 主状态机状态 127 | METHOD m_method; // 请求方法 128 | 129 | // 解析请求报文变量 130 | char m_real_file[FILENAME_LEN]; // 实际文件路径 131 | char *m_url; 132 | char *m_version; 133 | char *m_host; 134 | int m_content_length; 135 | bool m_linger; // 是否为长连接 136 | 137 | char *m_file_address; // 服务器上文件指针 138 | struct stat m_file_stat;// 文件信息结构体 139 | struct iovec m_iv[2]; // 向量元素 140 | int m_iv_count; // 向量元素个数 141 | int cgi; // 是否启用POST 142 | char *m_string; 143 | uint32_t bytes_to_send; // 剩余发送字节 144 | uint32_t bytes_have_send; // 已发送字节 145 | const char *doc_root; // 资源根目录 146 | 147 | map m_users; // 用户表 148 | int m_TRIGMode; // ET模式 149 | int m_close_log; // 是否关闭日志 150 | 151 | char sql_user[100]; // 数据库用户名 152 | char sql_passwd[100]; // 数据库密码 153 | char sql_name[100]; // 数据库名 154 | }; 155 | 156 | #endif -------------------------------------------------------------------------------- /code/locker/README.md: -------------------------------------------------------------------------------- 1 | # 线程同步机制包装类 2 | 3 | 锁机制的功能:实现多线程同步,通过锁机制,确保任一时刻只能有一个线程能进入关键代码段。 4 | 5 | 封装的功能:将锁的创建于销毁函数封装在类的构造与析构函数中,实现RAII机制。 6 | + RAII 在构造函数中申请分配资源,在析构函数中释放资源,核心思想是将资源或者状态与对象的生命周期绑定,通过c++语言机制实现资源和状态的安全管理。 7 | 8 | ## 信号量 9 | 10 | P,如果SV的值大于0,则将其减一;若SV的值为0,则挂起执行。 11 | 12 | V,如果有其他进行因为等待SV而挂起,则唤醒;若没有,则将SV值加一。 13 | 14 | + sem_init 函数用于初始化一个未命名的信号量 15 | + sem_destory 函数用于销毁信号量 16 | + sem_wait 函数将以原子操作方式将信号量减一,信号量为0时,sem_wait阻塞 17 | + sem_post 函数以原子操作方式将信号量加一,信号量大于0时,唤醒调用sem_post的线程 18 | 19 | ## 互斥锁 20 | 21 | 进入关键代码段,获得互斥锁将其加锁;离开关键代码段,唤醒等待该互斥锁的线程。 22 | 23 | + pthread_mutex_init 函数用于初始化互斥锁 24 | + pthread_mutex_destory 函数用于销毁互斥锁 25 | + pthread_mutex_lock 函数以原子操作方式给互斥锁加锁 26 | + pthread_mutex_unlock 函数以原子操作方式给互斥锁解锁 27 | 28 | ## 条件变量 29 | 30 | 提供一种线程间的通知机制,当某个共享数据达到某个值时,唤醒等待这个共享数据的线程。 31 | 32 | + pthread_cond_init 函数用于初始化条件变量 33 | + pthread_cond_destory 函数销毁条件变量 34 | + pthread_cond_broadcast 函数以广播的方式唤醒所有等待目标条件变量的线程 35 | + pthread_cond_wait 函数用于等待目标条件变量,该函数调用时需要传入 mutex参数(加锁的互斥锁),函数执行时先把调用线程放入条件变量的请求队列,然后将互斥锁mutex解锁;当函数成功返回为0时,互斥锁会再次被锁上,也就是说函数内部会有一次解锁和加锁操作 -------------------------------------------------------------------------------- /code/locker/locker.h: -------------------------------------------------------------------------------- 1 | #ifndef LOCKER_H 2 | #define LOCKER_H 3 | 4 | #include // std异常 5 | #include // Posix线程接口,提供互斥锁、条件变量 6 | #include // Posix信号量 7 | 8 | /* 9 | 信号量封装类 10 | 11 | 初始化信号量 12 | int sem_init(sem_t *sem, int pshared, unsigned int value); 13 | sem 信号量对象 14 | pshared 不为0信号量在进程间共享,否则在当前进程的所有线程共享 15 | value 信号量值大小 16 | 成功返回0,失败返回-1并设置errno 17 | 18 | 原子操作V 19 | int sem_wait(sem_t *sem); 20 | 信号量不为0则-1,为0则阻塞 21 | 成功返回0,失败返回-1并设置errno 22 | 23 | 原子操作P 24 | int sem_post(sem_t *sem); 25 | 信号量+1 26 | 成功返回0,失败返回-1并设置errno 27 | 28 | 销毁信号量 29 | int sem_destroy(sem_t *sem); 30 | 成功返回0,失败返回-1并设置errno 31 | */ 32 | class semaphore{ 33 | public: 34 | semaphore(){ 35 | if(sem_init(&m_sem, 0, 0) != 0){ 36 | throw std::exception(); 37 | } 38 | } 39 | 40 | semaphore(int num){ 41 | if(sem_init(&m_sem, 0, num) != 0){ 42 | throw std::exception(); 43 | } 44 | } 45 | 46 | ~semaphore(){ 47 | sem_destroy(&m_sem); 48 | } 49 | 50 | bool wait(){ 51 | return sem_wait(&m_sem) == 0; 52 | } 53 | 54 | bool post(){ 55 | return sem_post(&m_sem) == 0; 56 | } 57 | 58 | private: 59 | sem_t m_sem; 60 | }; 61 | 62 | 63 | /* 64 | 互斥锁封装类 65 | 66 | 初始化互斥锁 67 | int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); 68 | mutex 互斥锁对象 69 | attr 互斥锁属性,默认普通锁 70 | value信号量值大小 71 | 成功返回0 72 | 73 | 加锁 74 | int pthread_mutex_lock (pthread_mutex_t *mutex); 75 | 请求锁线程形成等待序列,解锁后按优先级获得锁 76 | 成功返回0 77 | 78 | 解锁 79 | int pthread_mutex_unlock (pthread_mutex_t *mutex); 80 | 成功返回0 81 | 82 | 销毁互斥锁 83 | int pthread_mutex_destroy (pthread_mutex_t *mutex); 84 | 成功返回0 85 | */ 86 | class mutexlocker{ 87 | public: 88 | mutexlocker(){ 89 | if(pthread_mutex_init(&m_mutex, NULL) != 0){ 90 | throw std::exception(); 91 | } 92 | } 93 | 94 | ~mutexlocker(){ 95 | pthread_mutex_destroy(&m_mutex); 96 | } 97 | 98 | bool lock(){ 99 | return pthread_mutex_lock(&m_mutex) == 0; 100 | } 101 | 102 | bool unlock(){ 103 | return pthread_mutex_unlock(&m_mutex) == 0; 104 | } 105 | 106 | pthread_mutex_t* get(){ 107 | return &m_mutex; 108 | } 109 | 110 | private: 111 | pthread_mutex_t m_mutex; 112 | }; 113 | 114 | 115 | /* 116 | 条件变量封装类 117 | 118 | 初始化条件变量 119 | int pthread_cond_init(pthread_cond_t *cv, const pthread_condattr_t *cattr); 120 | cv 条件变量对象 121 | cattr 条件变量属性 122 | 成功返回0 123 | 124 | 等待条件变量成立 125 | int pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mutex); 126 | 调用该函数时,线程总处于某个临界区,持有某个互斥锁 127 | 释放mutex防止死锁,阻塞等待唤醒,然后再获取mutex 128 | 成功返回0 129 | 130 | 计时等待条件变量成立 131 | int pthread_cond_timedwait(pthread_cond_t *cv, pthread_mutex_t *mutex, struct timespec *abstime); 132 | 成功返回0,超时返回ETIMEDOUT 133 | 134 | 唤醒一个wait的线程 135 | int pthread_cond_signal(pthread_cond_t *cv); 136 | 成功返回0 137 | 138 | 唤醒所有wait的线程 139 | int pthread_cond_broadcast(pthread_cond_t *cv); 140 | 成功返回0 141 | 142 | */ 143 | class condvar{ 144 | public: 145 | condvar(){ 146 | if(pthread_cond_init(&m_cond, NULL) != 0){ 147 | throw std::exception(); 148 | } 149 | } 150 | 151 | ~condvar(){ 152 | pthread_cond_destroy(&m_cond); 153 | } 154 | 155 | // 把调用线程放入条件变量请求队列,解锁阻塞,直到特定条件发生,唤醒后重新加锁 156 | // 开始加锁防止线程1没进入wait cond状态时,线程2调用cond singal导致信号丢失 157 | bool wait(pthread_mutex_t *m_mutex){ 158 | return pthread_cond_wait(&m_cond, m_mutex) == 0; 159 | } 160 | 161 | bool timewait(pthread_mutex_t *m_mutex, struct timespec t){ 162 | return pthread_cond_timedwait(&m_cond, m_mutex, &t) == 0; 163 | } 164 | 165 | bool signal(){ 166 | return pthread_cond_signal(&m_cond) == 0; 167 | } 168 | 169 | bool broadcast(){ 170 | return pthread_cond_broadcast(&m_cond) == 0; 171 | } 172 | 173 | private: 174 | pthread_cond_t m_cond; 175 | }; 176 | 177 | #endif -------------------------------------------------------------------------------- /code/log/README.md: -------------------------------------------------------------------------------- 1 | # 同步/异步日志系统 2 | 3 | 使用单例模式创建日志系统,对服务器运行状态、错误信息和访问数据进行记录,该系统可以实现按天分类,超行分类功能,可以根据实际情况分别使用同步和异步写入两种方式。 4 | 5 | + 同步日志:日志写入函数与工作线程串行执行,由于涉及到I/O操作,当单条日志比较大的时候,同步模式会阻塞整个处理流程,服务器所能处理的并发能力将有所下降,尤其是在峰值的时候,写日志可能成为系统的瓶颈。 6 | + 异步日志:将所写的日志内容先存入阻塞队列,写线程从阻塞队列中取出内容,写入日志。 -------------------------------------------------------------------------------- /code/log/block_queue.h: -------------------------------------------------------------------------------- 1 | #ifndef BLOCK_QUEUE_H 2 | #define BLOCK_QUEUE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "../locker/locker.h" 9 | 10 | using namespace std; 11 | 12 | // 阻塞队列模板类 13 | template 14 | class block_queue{ 15 | public: 16 | block_queue(int max_size = 1000){ 17 | if(max_size <= 0){ 18 | exit(-1); 19 | } 20 | m_max_size = max_size; 21 | m_array = new T[max_size]; 22 | m_size = 0; 23 | m_front = 1; 24 | m_back = -1; 25 | } 26 | 27 | void clear(){ 28 | m_mutex.lock(); 29 | m_size = 0; 30 | m_front = -1; 31 | m_back = -1; 32 | m_mutex.unlock(); 33 | } 34 | 35 | ~block_queue(){ 36 | m_mutex.lock(); 37 | if(m_array != nullptr) 38 | delete [] m_array; 39 | m_mutex.unlock(); 40 | } 41 | 42 | // 判断队列是否已满 43 | bool full(){ 44 | m_mutex.lock(); 45 | if(m_size >= m_max_size){ 46 | m_mutex.unlock(); 47 | return true; 48 | } 49 | m_mutex.unlock(); 50 | return false; 51 | } 52 | 53 | // 判断队列是否为空 54 | bool empty(){ 55 | m_mutex.lock(); 56 | if(m_size == 0){ 57 | m_mutex.lock(); 58 | return true; 59 | } 60 | m_mutex.unlock(); 61 | return false; 62 | } 63 | 64 | // 返回队首元素 65 | bool front(T &value){ 66 | m_mutex.lock(); 67 | if(m_size == 0){ 68 | m_mutex.unlock(); 69 | return false; 70 | } 71 | value = m_array[m_front]; 72 | m_mutex.unlock(); 73 | return true; 74 | } 75 | 76 | // 返回队尾元素 77 | bool back(T &value){ 78 | m_mutex.lock(); 79 | if(m_size == 0){ 80 | m_mutex.unlock(); 81 | return false; 82 | } 83 | value = m_array[m_back]; 84 | return true; 85 | } 86 | 87 | int size(){ 88 | int tmp = 0; 89 | m_mutex.lock(); 90 | tmp = m_size; 91 | m_mutex.unlock(); 92 | return tmp; 93 | } 94 | 95 | int max_size(){ 96 | int tmp = 0; 97 | m_mutex.lock(); 98 | tmp = m_max_size; 99 | m_mutex.unlock(); 100 | return tmp; 101 | } 102 | 103 | // 添加元素 104 | bool push(const T &item){ 105 | m_mutex.lock(); 106 | if(m_size >= m_max_size){ 107 | m_cond.broadcast(); 108 | m_mutex.unlock(); 109 | return false; 110 | } 111 | // 循环队列 112 | m_back = (m_back + 1) % m_max_size; 113 | m_array[m_back] = item; 114 | m_size++; 115 | 116 | // 广播唤醒,线程竞争 117 | m_cond.broadcast(); 118 | m_mutex.unlock(); 119 | return true; 120 | } 121 | 122 | // 取第一个元素 123 | bool pop(T &item){ 124 | m_mutex.lock(); 125 | // 多个线程竞争资源,wait成功返回不一定还有资源,所以使用while每次检查 126 | // 先加锁防止在条件判断以后、进入阻塞之前有其它线程释放信号而导致信号无效 127 | while(m_size <= 0){ 128 | if(!m_cond.wait(m_mutex.get())){ 129 | m_mutex.unlock(); 130 | return false; 131 | } 132 | } 133 | 134 | m_front = (m_front + 1) % m_max_size; 135 | item = m_array[m_front]; 136 | m_size--; 137 | m_mutex.unlock(); 138 | return true; 139 | } 140 | 141 | // 增加超时处理 142 | bool pop(T &item, int ms_timeout){ 143 | struct timespec t = {0, 0}; // s and ns 144 | struct timeval now = {0, 0}; // s and ms 145 | // 格林威治时间 146 | gettimeofday(&now, nullptr); 147 | 148 | m_mutex.lock(); 149 | // 把while拆成两个if,只等待一次 150 | if(m_size <= 0){ 151 | t.tv_sec = now.tv_sec + ms_timeout / 1000; 152 | t.tv_nsec = (ms_timeout % 1000) * 1000; 153 | if(!m_cond.timewait(m_mutex.get(), t)){ 154 | m_mutex.unlock(); 155 | return false; 156 | } 157 | } 158 | // 防止资源已经被使用 159 | if(m_size <= 0){ 160 | m_mutex.unlock(); 161 | return false; 162 | } 163 | 164 | m_front = (m_front + 1) % m_max_size; 165 | item = m_array[m_front]; 166 | m_size--; 167 | m_mutex.unlock(); 168 | return true; 169 | } 170 | 171 | private: 172 | mutexlocker m_mutex; 173 | condvar m_cond; 174 | 175 | T *m_array; 176 | int m_size; 177 | int m_max_size; 178 | int m_front; 179 | int m_back; 180 | }; 181 | 182 | #endif -------------------------------------------------------------------------------- /code/log/log.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "log.h" 7 | 8 | using namespace std; 9 | 10 | Log::Log(){ 11 | m_count = 0; 12 | m_is_async = false; 13 | } 14 | 15 | Log::~Log(){ 16 | if(m_fp != nullptr) 17 | fclose(m_fp); 18 | } 19 | 20 | // 初始化日志,异步需要设置阻塞队列的长度,同步不需要 21 | bool Log::init(const char *file_name, int log_buf_size, int split_lines, int max_queue_size){ 22 | // 如果设置了max_queue_size,则设置为异步 23 | if(max_queue_size >= 1){ 24 | m_is_async = true; 25 | m_log_queue = new block_queue(max_queue_size); 26 | // 创建线程异步写 27 | pthread_t tid; 28 | pthread_create(&tid, nullptr, flush_log_thread, nullptr); 29 | } 30 | 31 | // 初始化日志 32 | m_log_buf_size = log_buf_size; 33 | m_buf = new char[m_log_buf_size]; 34 | memset(m_buf, 0, m_log_buf_size); 35 | m_split_lines = split_lines; 36 | 37 | // 获取当前时间 38 | time_t t = time(nullptr); 39 | struct tm *sys_tm = localtime(&t); 40 | struct tm my_tm = *sys_tm; 41 | 42 | // 查找字符从右面开始第一次出现的位置,截断文件名 43 | const char *p = strrchr(file_name, '/'); 44 | char log_full_name[256] = {0}; 45 | 46 | // 构造日志文件名 47 | if(p == nullptr){ 48 | // 将可变参数格式化到字符串中 49 | // 没有/则直接在当前路径下 50 | snprintf_nowarn(log_full_name, 255, "%d_%02d_%02d_%s", my_tm.tm_year + 1900, my_tm.tm_mon + 1, my_tm.tm_mday, file_name); 51 | } 52 | else{ 53 | strcpy(log_name, p+1); // 日志文件名 54 | strncpy(dir_name, file_name, p-file_name+1); // 日志路径 55 | snprintf_nowarn(log_full_name, 255, "%s%d_%02d_%02d_%s", dir_name, my_tm.tm_year + 1900, my_tm.tm_mon + 1, my_tm.tm_mday, log_name); 56 | } 57 | 58 | m_today = my_tm.tm_mday; 59 | 60 | // 追加写 61 | m_fp = fopen(log_full_name, "a"); 62 | if(m_fp == nullptr) 63 | return false; 64 | return true; 65 | } 66 | 67 | // 写日志 68 | void Log::write_log(int level, const char *format, ...){ 69 | // 秒、微妙 70 | struct timeval now = {0, 0}; 71 | gettimeofday(&now, nullptr); 72 | // 获取时间结构体 73 | time_t t = now.tv_sec; 74 | struct tm *sys_tm = localtime(&t); 75 | struct tm my_tm = *sys_tm; 76 | 77 | // 临界区加锁 78 | m_mutex.lock(); 79 | 80 | m_count++; 81 | // 新的一天或者日志达到最大行数,需要更换日志文件 82 | if(m_today != my_tm.tm_mday || m_count % m_split_lines == 0){ 83 | // 刷新文件缓冲并关闭文件 84 | fflush(m_fp); 85 | fclose(m_fp); 86 | 87 | // 新日志路径 88 | char new_log[256] = {0}; 89 | char tail[16] = {0}; 90 | 91 | snprintf_nowarn(tail, 16, "%d_%02d_%02d_", my_tm.tm_year + 1900, my_tm.tm_mon + 1, my_tm.tm_mday); 92 | 93 | // 天数变化 94 | if(m_today != my_tm.tm_mday){ 95 | snprintf_nowarn(new_log, 255, "%s%s%s", dir_name, tail, log_name); 96 | m_today = my_tm.tm_mday; 97 | m_count = 0; 98 | } 99 | // 日志达到最大行数 100 | else{ 101 | snprintf_nowarn(new_log, 255, "%s%s%s.%lld", dir_name, tail, log_name, m_count / m_split_lines); 102 | } 103 | // 打开新日志 104 | m_fp = fopen(new_log, "a"); 105 | } 106 | 107 | m_mutex.unlock(); 108 | 109 | // 日志级别 110 | char s[16] = {0}; 111 | switch(level){ 112 | case 0: strcpy(s, "[debug]:"); break; 113 | case 1: strcpy(s, "[info]:"); break; 114 | case 2: strcpy(s, "[warn]:"); break; 115 | case 3: strcpy(s, "[erro]:"); break; 116 | default: strcpy(s, "[debug]:"); break; 117 | } 118 | 119 | // 临界区加锁 120 | m_mutex.lock(); 121 | 122 | // 构造日志内容 123 | // 时间、级别 124 | int n = snprintf(m_buf, 48, "%d-%02d-%02d %02d:%02d:%02d.%06ld %s ", 125 | my_tm.tm_year + 1900, my_tm.tm_mon + 1, my_tm.tm_mday, 126 | my_tm.tm_hour, my_tm.tm_min, my_tm.tm_sec, now.tv_usec, s); 127 | if(n < 0) abort(); 128 | // 正文 129 | va_list valst; 130 | va_start(valst, format); 131 | int m = vsnprintf(m_buf + n, m_log_buf_size - 1, format, valst); 132 | if(m < 0) abort(); 133 | va_end(valst); 134 | 135 | m_buf[n + m] = '\n'; 136 | m_buf[n + m + 1] = '\0'; 137 | 138 | // 异步写 139 | if(m_is_async && !m_log_queue->full()){ 140 | m_log_queue->push(m_buf); 141 | } 142 | // 同步写 143 | else{ 144 | fputs(m_buf, m_fp); 145 | } 146 | 147 | m_mutex.unlock(); 148 | } 149 | 150 | // 强制刷新文件缓冲 151 | void Log::flush(void){ 152 | m_mutex.lock(); 153 | fflush(m_fp); 154 | m_mutex.unlock(); 155 | } -------------------------------------------------------------------------------- /code/log/log.h: -------------------------------------------------------------------------------- 1 | #ifndef LOG_H 2 | #define LOG_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "block_queue.h" 10 | 11 | using namespace std; 12 | 13 | class Log{ 14 | public: 15 | // c++11后,局部静态变量懒汉式不用加锁 16 | static Log* get_instance(){ 17 | static Log instance; 18 | return &instance; 19 | } 20 | 21 | bool init(const char *file_name, int log_buf_size=8192, int split_lines=5000000, int max_queue_size=0); 22 | void write_log(int level, const char *format, ...); 23 | void flush(void); 24 | 25 | private: 26 | Log(); 27 | ~Log(); 28 | 29 | // 线程工作函数,静态函数防止产生this指针 30 | static void* flush_log_thread(void *args){ 31 | Log::get_instance()->async_write_log(); 32 | return nullptr; 33 | } 34 | 35 | // 异步写 36 | void async_write_log(){ 37 | string single_log; 38 | // 从阻塞队列取出一个日志 39 | while(m_log_queue->pop(single_log)){ 40 | m_mutex.lock(); 41 | // 写入后文件指针后移 42 | fputs(single_log.c_str(), m_fp); 43 | m_mutex.unlock(); 44 | } 45 | } 46 | 47 | private: 48 | char dir_name[128]; // 路径名 49 | char log_name[128]; // log文件名 50 | int m_split_lines; // 日志最大行数 51 | int m_log_buf_size; // 日志缓冲区大小 52 | long long m_count; // 日志记录行数 53 | int m_today; // 日志日期 54 | FILE *m_fp; // 日志文件指针 55 | char *m_buf; // 缓冲区 56 | block_queue *m_log_queue; //阻塞队列 57 | bool m_is_async; // 是否异步 58 | mutexlocker m_mutex; 59 | }; 60 | 61 | // 检查snprintf返回值,防止warning 62 | #define snprintf_nowarn(...) (snprintf(__VA_ARGS__) < 0 ? abort() : (void)0) 63 | // 可变参数宏__VA_ARGS__,当可变参数个数为0时,##__VA_ARGS__把前面多余的“,”去掉 64 | #define LOG_DEBUG(format, ...) if(m_close_log == 0) {Log::get_instance()->write_log(0, format, ##__VA_ARGS__); Log::get_instance()->flush();} 65 | #define LOG_INFO(format, ...) if(m_close_log == 0) {Log::get_instance()->write_log(1, format, ##__VA_ARGS__); Log::get_instance()->flush();} 66 | #define LOG_WARN(format, ...) if(m_close_log == 0) {Log::get_instance()->write_log(2, format, ##__VA_ARGS__); Log::get_instance()->flush();} 67 | #define LOG_ERROR(format, ...) if(m_close_log == 0) {Log::get_instance()->write_log(3, format, ##__VA_ARGS__); Log::get_instance()->flush();} 68 | 69 | #endif -------------------------------------------------------------------------------- /code/main.cpp: -------------------------------------------------------------------------------- 1 | #include "./config/config.h" 2 | #include "./server/webserver.h" 3 | 4 | int main(int argc, char *argv[]){ 5 | Config config; 6 | config.parse_arg(argc, argv); 7 | 8 | WebServer server(config.user, config.password, config.database_name, config.root, 9 | config.port, config.close_log, config.async_log, config.sql_num, 10 | config.thread_num, config.actor_model, config.trig_mode, config.opt_linger); 11 | server.eventListen(); 12 | server.eventLoop(); 13 | 14 | return 0; 15 | } -------------------------------------------------------------------------------- /code/server/webserver.cpp: -------------------------------------------------------------------------------- 1 | #include "webserver.h" 2 | 3 | WebServer::WebServer(string user, string password, string database_name, const char *root, 4 | int port, int close_log, int async_log, int sql_num, 5 | int thread_num, int actor_model, int trig_mode, int opt_linger){ 6 | m_user = user; 7 | m_passWord = password; 8 | m_databaseName = database_name; 9 | m_root = root; 10 | m_port = port; 11 | m_close_log = close_log; 12 | m_async_log = async_log; 13 | m_sql_num = sql_num; 14 | m_thread_num = thread_num; 15 | m_actormodel = actor_model; 16 | m_listen_trig_mode = trig_mode & 2; 17 | m_conn_trig_mode = trig_mode & 1; 18 | m_opt_linger = opt_linger; 19 | 20 | users = new http_conn[MAX_FD]; 21 | users_timer = new client_data[MAX_FD]; 22 | log_write(); 23 | sql_pool(); 24 | thread_pool(); 25 | } 26 | 27 | WebServer::~WebServer(){ 28 | close(m_epollfd); 29 | close(m_listenfd); 30 | close(m_pipefd[1]); 31 | close(m_pipefd[0]); 32 | delete[] users; 33 | delete[] users_timer; 34 | delete m_pool; 35 | delete m_connPool; 36 | } 37 | 38 | // 初始化日志 39 | void WebServer::log_write(){ 40 | if(m_close_log == 0){ 41 | // 获取单例并初始化 42 | if(m_async_log == 1) 43 | // 异步写,需要阻塞队列长度 44 | Log::get_instance()->init("./log/ServerLog", 2000, 800000, 800); 45 | else 46 | // 同步写 47 | Log::get_instance()->init("./log/ServerLog", 2000, 800000, 0); 48 | } 49 | } 50 | 51 | // 初始化数据库连接池 52 | void WebServer::sql_pool(){ 53 | m_connPool = connection_pool::get_instance(); 54 | m_connPool->init("localhost", m_user, m_passWord, m_databaseName, 3306, m_sql_num, m_close_log); 55 | 56 | // 读取用户表 57 | users->initmysql_result(m_connPool); 58 | } 59 | 60 | // 初始化线程池 61 | void WebServer::thread_pool(){ 62 | m_pool = new threadpool(m_actormodel, m_connPool, m_thread_num); 63 | } 64 | 65 | // 监听事件,网络编程基础步骤 66 | void WebServer::eventListen(){ 67 | // ipv4,面向连接 68 | m_listenfd = socket(PF_INET, SOCK_STREAM, 0); 69 | assert(m_listenfd >= 0); 70 | 71 | // 优雅关闭连接 72 | // 缺省为close()继续发送缓冲区残留数据,等待确认然后返回 73 | // 将修改为close()设置一个超时,发送缓冲区残留数据,全部确认则正常关闭,否则发送RST、丢弃数据并跳过time_wait直接关闭 74 | if(m_opt_linger){ 75 | struct linger tmp = {1, 1}; 76 | setsockopt(m_listenfd, SOL_SOCKET, SO_LINGER, &tmp, sizeof(tmp)); 77 | } 78 | 79 | // 重用端口 80 | int flag = 1; 81 | setsockopt(m_listenfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)); 82 | 83 | struct sockaddr_in address; 84 | bzero(&address, sizeof(address)); 85 | address.sin_family = AF_INET; 86 | // 转网络字节序 87 | address.sin_addr.s_addr = htonl(INADDR_ANY); 88 | address.sin_port = htons(m_port); 89 | 90 | // 绑定ip和端口 91 | int ret = 0; 92 | ret = bind(m_listenfd, (struct sockaddr *)&address, sizeof(address)); 93 | assert(ret >= 0); 94 | // 监听 95 | ret = listen(m_listenfd, 5); 96 | assert(ret >= 0); 97 | 98 | // 参数没有意义 99 | m_epollfd = epoll_create(5); 100 | assert(m_epollfd != -1); 101 | 102 | utils.init(TIMESLOT); 103 | // 注册事件并设置非阻塞 104 | utils.addfd(m_epollfd, m_listenfd, false, m_listen_trig_mode); 105 | http_conn::m_epollfd = m_epollfd; 106 | 107 | // 创建一对套接字,fd[1]写,fd[0]读 108 | ret = socketpair(PF_UNIX, SOCK_STREAM, 0, m_pipefd); 109 | assert(ret != -1); 110 | utils.setnonblocking(m_pipefd[1]); 111 | // 统一事件源 112 | utils.addfd(m_epollfd, m_pipefd[0], false, 0); 113 | 114 | // 两次向已关闭的连接发送数据导致SIGPIPE,避免进程退出,捕获SIGPIPE并忽略 115 | utils.addsig(SIGPIPE, SIG_IGN); 116 | // 时钟信号 117 | utils.addsig(SIGALRM, utils.sig_handler, false); 118 | // kill终止信号 119 | utils.addsig(SIGTERM, utils.sig_handler, false); 120 | // abort终止信号 121 | utils.addsig(SIGABRT, utils.sig_handler, false); 122 | 123 | // 定时器 124 | alarm(TIMESLOT); 125 | 126 | Utils::u_pipefd = m_pipefd; 127 | Utils::u_epollfd = m_epollfd; 128 | } 129 | 130 | // 设置客户和定时器 131 | void WebServer::timer(int connfd, struct sockaddr_in client_address){ 132 | // 初始化客户 133 | users[connfd].init(connfd, client_address, m_root, m_conn_trig_mode, m_close_log, m_user, m_passWord, m_databaseName); 134 | 135 | // 创建定时器,设置回调函数和超时时间,绑定用户数据,将定时器添加到链表中 136 | users_timer[connfd].address = client_address; 137 | users_timer[connfd].sockfd = connfd; 138 | util_timer *timer = new util_timer; 139 | timer->user_data = &users_timer[connfd]; 140 | timer->cb_func = cb_func; 141 | time_t cur = time(NULL); 142 | timer->expire = cur + 3 * TIMESLOT; 143 | users_timer[connfd].timer = timer; 144 | utils.m_timer_lst.add_timer(timer); 145 | } 146 | 147 | //若有数据传输,则将定时器往后延迟3个单位 148 | //并对新的定时器在链表上的位置进行调整 149 | void WebServer::adjust_timer(util_timer *timer){ 150 | time_t cur = time(NULL); 151 | timer->expire = cur + 3 * TIMESLOT; 152 | utils.m_timer_lst.adjust_timer(timer); 153 | 154 | LOG_INFO("%s", "adjust timer once"); 155 | } 156 | 157 | // 释放连接,删除计时器 158 | void WebServer::deal_timer(util_timer *timer, int sockfd){ 159 | timer->cb_func(&users_timer[sockfd]); 160 | if(timer){ 161 | utils.m_timer_lst.del_timer(timer); 162 | } 163 | 164 | LOG_INFO("close fd %d", users_timer[sockfd].sockfd); 165 | } 166 | 167 | // 接受新客户连接 168 | bool WebServer::dealclinetdata(){ 169 | struct sockaddr_in client_address; 170 | socklen_t client_addrlength = sizeof(client_address); 171 | if(m_listen_trig_mode == 0){ 172 | int connfd = accept(m_listenfd, (struct sockaddr *)&client_address, &client_addrlength); 173 | if(connfd < 0){ 174 | LOG_ERROR("%s:errno is:%d", "accept error", errno); 175 | return false; 176 | } 177 | if(http_conn::m_user_count >= MAX_FD){ 178 | utils.show_error(connfd, "Internal server busy"); 179 | LOG_ERROR("%s", "Internal server busy"); 180 | return false; 181 | } 182 | timer(connfd, client_address); 183 | } 184 | else{ 185 | while(1){ 186 | int connfd = accept(m_listenfd, (struct sockaddr *)&client_address, &client_addrlength); 187 | if(connfd < 0){ 188 | LOG_ERROR("%s:errno is:%d", "accept error", errno); 189 | break; 190 | } 191 | if(http_conn::m_user_count >= MAX_FD){ 192 | utils.show_error(connfd, "Internal server busy"); 193 | LOG_ERROR("%s", "Internal server busy"); 194 | break; 195 | } 196 | timer(connfd, client_address); 197 | } 198 | return false; 199 | } 200 | return true; 201 | } 202 | 203 | // 处理信号 204 | bool WebServer::dealwithsignal(bool &timeout, bool &stop_server){ 205 | int ret = 0; 206 | char signals[1024]; 207 | ret = recv(m_pipefd[0], signals, sizeof(signals), 0); 208 | if(ret == -1){ 209 | return false; 210 | } 211 | else if(ret == 0){ 212 | return false; 213 | } 214 | else{ 215 | for(int i = 0; i < ret; ++i){ 216 | switch(signals[i]){ 217 | case SIGALRM: timeout = true; break; 218 | case SIGTERM: stop_server = true; break; 219 | case SIGABRT: stop_server = true; break; 220 | } 221 | } 222 | } 223 | return true; 224 | } 225 | 226 | // 处理读 227 | void WebServer::dealwithread(int sockfd){ 228 | util_timer *timer = users_timer[sockfd].timer; 229 | 230 | // reactor 231 | if(m_actormodel == 1){ 232 | // 更新计时器 233 | if(timer){ 234 | adjust_timer(timer); 235 | } 236 | 237 | // 若监测到读事件,将该事件放入请求队列 238 | m_pool->append(users + sockfd, 0); 239 | 240 | // 等待工作线程读 241 | while(true){ 242 | if(users[sockfd].improv == 1){ 243 | // 读失败 244 | if(users[sockfd].timer_flag == 1){ 245 | deal_timer(timer, sockfd); 246 | users[sockfd].timer_flag = 0; 247 | } 248 | users[sockfd].improv = 0; 249 | break; 250 | } 251 | } 252 | } 253 | // proactor 254 | else{ 255 | if(users[sockfd].read_once()){ 256 | LOG_INFO("deal with the client(%s)", inet_ntoa(users[sockfd].get_address()->sin_addr)); 257 | 258 | // 更新计时器 259 | if(timer){ 260 | adjust_timer(timer); 261 | } 262 | 263 | // 若监测到读事件,将该事件放入请求队列 264 | m_pool->append_p(users + sockfd); 265 | } 266 | // 读失败 267 | else{ 268 | deal_timer(timer, sockfd); 269 | } 270 | } 271 | } 272 | 273 | // 处理写 274 | void WebServer::dealwithwrite(int sockfd){ 275 | util_timer *timer = users_timer[sockfd].timer; 276 | 277 | // reactor 278 | if(m_actormodel == 1){ 279 | if(timer){ 280 | adjust_timer(timer); 281 | } 282 | 283 | m_pool->append(users + sockfd, 1); 284 | 285 | while(true){ 286 | if(users[sockfd].improv == 1){ 287 | if(users[sockfd].timer_flag == 1){ 288 | deal_timer(timer, sockfd); 289 | users[sockfd].timer_flag = 0; 290 | } 291 | users[sockfd].improv = 0; 292 | break; 293 | } 294 | } 295 | } 296 | else{ 297 | // proactor 298 | if(users[sockfd].write()){ 299 | LOG_INFO("send data to the client(%s)", inet_ntoa(users[sockfd].get_address()->sin_addr)); 300 | 301 | if(timer){ 302 | adjust_timer(timer); 303 | } 304 | } 305 | else{ 306 | deal_timer(timer, sockfd); 307 | } 308 | } 309 | } 310 | 311 | // 循环处理事件 312 | void WebServer::eventLoop(){ 313 | bool timeout = false; 314 | bool stop_server = false; 315 | 316 | while(!stop_server){ 317 | int number = epoll_wait(m_epollfd, events, MAX_EVENT_NUMBER, -1); 318 | // epoll_wait会被信号打断,返回-1并设置errno=EINTR 319 | if(number < 0 && errno != EINTR){ 320 | LOG_ERROR("%s", "epoll failure"); 321 | break; 322 | } 323 | // 遍历事件 324 | for(int i = 0; i < number; i++){ 325 | int sockfd = events[i].data.fd; 326 | // 处理新到的客户连接 327 | if(sockfd == m_listenfd){ 328 | bool flag = dealclinetdata(); 329 | if(flag == false) 330 | continue; 331 | } 332 | // 对端关闭 333 | else if(events[i].events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)){ 334 | // 服务器端关闭连接,移除对应的定时器 335 | util_timer *timer = users_timer[sockfd].timer; 336 | deal_timer(timer, sockfd); 337 | } 338 | // 处理信号 339 | else if((sockfd == m_pipefd[0]) && (events[i].events & EPOLLIN)){ 340 | bool flag = dealwithsignal(timeout, stop_server); 341 | if(flag == false) 342 | LOG_ERROR("%s", "dealclientdata failure"); 343 | } 344 | // 处理客户连接上接收到的数据 345 | else if(events[i].events & EPOLLIN){ 346 | dealwithread(sockfd); 347 | } 348 | // 处理客户连接上要发送的数据 349 | else if(events[i].events & EPOLLOUT){ 350 | dealwithwrite(sockfd); 351 | } 352 | } 353 | // 时钟到时 354 | if(timeout){ 355 | utils.timer_handler(); 356 | LOG_INFO("%s", "timer tick"); 357 | timeout = false; 358 | } 359 | } 360 | } -------------------------------------------------------------------------------- /code/server/webserver.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBSERVER_H 2 | #define WEBSERVER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "../threadpool/threadpool.h" 16 | #include "../http/http_conn.h" 17 | 18 | const int MAX_FD = 65535; //最大文件描述符 19 | const int MAX_EVENT_NUMBER = 10000; //最大事件数 20 | const int TIMESLOT = 5; //最小超时单位 21 | 22 | class WebServer{ 23 | public: 24 | WebServer(string user, string password, string database_name, const char *root, 25 | int port, int close_log, int async_log, int sql_num, 26 | int thread_num, int actor_model, int trig_mode, int opt_linger); 27 | ~WebServer(); 28 | 29 | void log_write(); 30 | void sql_pool(); 31 | void thread_pool(); 32 | 33 | void eventListen(); 34 | void eventLoop(); 35 | 36 | void timer(int connfd, struct sockaddr_in client_address); 37 | void adjust_timer(util_timer *timer); 38 | void deal_timer(util_timer *timer, int sockfd); 39 | bool dealclinetdata(); 40 | bool dealwithsignal(bool& timeout, bool& stop_server); 41 | void dealwithread(int sockfd); 42 | void dealwithwrite(int sockfd); 43 | 44 | public: 45 | // 基本配置 46 | string m_user; // 登陆数据库用户名 47 | string m_passWord; // 登陆数据库密码 48 | string m_databaseName; // 数据库名 49 | const char *m_root; // 资源路径 50 | int m_port; // 端口号 51 | int m_close_log; // 关闭日志,0不关闭,1关闭 52 | int m_async_log; // 日志写入方式,0同步,1异步 53 | int m_sql_num; // 数据库池连接数 54 | int m_thread_num; // 线程池线程数 55 | int m_actormodel; // 并发模型,0 proactor,1 reactor 56 | int m_listen_trig_mode; // 监听触发模式,0 LT,1 ET 57 | int m_conn_trig_mode; // 连接触发模式,0 LT,1 ET 58 | int m_opt_linger; // 优雅关闭链接,0不使用,1使用 59 | 60 | connection_pool *m_connPool; // 数据库连接池 61 | threadpool *m_pool; // 线程池 62 | 63 | http_conn *users; // 客户请求结构 64 | client_data *users_timer; // 客户数据结构 65 | Utils utils; 66 | 67 | int m_listenfd; // 监听套接字 68 | int m_pipefd[2]; // 信号本地套接字 69 | 70 | int m_epollfd; // epoll句柄 71 | epoll_event events[MAX_EVENT_NUMBER]; // 事件列表 72 | }; 73 | 74 | #endif -------------------------------------------------------------------------------- /code/threadpool/README.md: -------------------------------------------------------------------------------- 1 | # 半同步/半反应堆线程池 2 | 3 | ## 五种I/O模型 4 | + 同步I/O:内核向应用程序通知的是就绪事件,比如只通知有客户端连接,要求用户代码自行执行I/O操作 5 | + 阻塞I/O:调用者调用了某个函数,等待这个函数返回,期间什么也不做,不停的去检查这个函数有没有返回,必须等这个函数返回才能进行下一步动作 6 | + 非阻塞I/O:非阻塞等待,每隔一段时间就去检测I/O事件是否就绪。没有就绪就可以做其他事。非阻塞I/O执行系统调用总是立即返回,不管时间是否已经发生,若时间没有发生,则返回-1,此时可以根据errno区分这两种情况,对于accept,recv和send,事件未发生时,errno通常被设置成eagain 7 | + 信号驱动I/O:linux用套接口进行信号驱动I/O,安装一个信号处理函数,进程继续运行并不阻塞,当IO时间就绪,进程收到SIGIO信号。然后处理IO事件 8 | + I/O复用:linux用select/poll函数实现I/O复用模型,这两个函数也会使进程阻塞,但是和阻塞IO所不同的是这两个函数可以同时阻塞多个I/O操作。而且可以同时对多个读操作、写操作的I/O函数进行检测。知道有数据可读或可写时,才真正调用I/O操作函数 9 | + 异步I/O:linux中,可以调用aio_read函数告诉内核描述字缓冲区指针和缓冲区的大小、文件偏移及通知的方式,然后立即返回,当内核将数据拷贝到缓冲区后,再通知应用程序 10 | 11 | ## 事件处理模式 12 | 13 | + reactor模式:主线程(I/O处理单元)只负责监听文件描述符上是否有事件发生,有的话立即通知工作线程(逻辑单元),读写数据、接受新连接及处理客户请求均在工作线程中完成。通常由同步I/O实现 14 | + proactor模式:主线程和内核负责处理读写数据、接受新连接等I/O操作,工作线程仅负责业务逻辑,如处理客户请求。通常由异步I/O实现 15 | 16 | ## 同步I/O模拟proactor模式 17 | 18 | 异步I/O不成熟,实际使用较少。 19 | 20 | 1. 主线程往epoll内核事件表注册socket上的读就绪事件 21 | 2. 主线程调用epoll_wait等待socket上有数据可读 22 | 3. 当socket上有数据可读,epoll_wait通知主线程,主线程从socket循环读取数据,直到没有更多数据可读,然后将读取到的数据封装成一个请求对象并插入请求队列 23 | 4. 睡眠在请求队列上某个工作线程被唤醒,它获得请求对象并处理客户请求,然后往epoll内核事件表中注册该socket上的写就绪事件 24 | 5. 主线程调用epoll_wait等待socket可写 25 | 6. 当socket上有数据可写,epoll_wait通知主线程。主线程往socket上写入服务器处理客户请求的结果 26 | 27 | ## 两种高效的并发编程模式 28 | 29 | 半同步/半异步:同步线程用于处理客户逻辑,异步线程用于处理I/O事件。异步线程监听到客户请求后,就将其封装成请求对象并插入请求队列中,请求队列将通知某个工作在同步模式的工作线程来读取并处理该请求对象 30 | 31 | + 半同步/半反应堆 32 | 1. 主线程充当异步线程,负责监听所有socket上的事件 33 | 2. 若有新请求到来,主线程接收之以得到新的连接socket,然后往epoll内核事件表中注册该socket上的读写事件 34 | 3. 如果连接socket上有读写事件发生,主线程从socket上接收数据,并将数据封装成请求对象插入到请求队列中 35 | 4. 所有工作线程睡眠在请求队列上,当有任务到来时,通过竞争(如互斥锁)获得任务的接管权 36 | 37 | + 缺点:1)线程获取任务时需要对请求队列加锁,耗费cpu时间。2)一个工作线程在同一时间只能处理一个客户请求,在客户请求数量多的情况下变慢,若增加工作线程,则线程切换也会耗费时间。 38 | 39 | 领导者/追随者模式 40 | 41 | ## 线程池 42 | 43 | 空间换时间,浪费服务器的硬件资源,换取运行效率 -------------------------------------------------------------------------------- /code/threadpool/threadpool.h: -------------------------------------------------------------------------------- 1 | #ifndef THREADPOOL_H 2 | #define THREADPOOL_H 3 | 4 | #include // 请求队列 5 | #include 6 | #include // std异常 7 | #include // 线程函数 8 | #include "../locker/locker.h" // 线程同步锁封装类 9 | #include "../CGImysql/sql_connection_pool.h" // 数据库 10 | 11 | // 线程池模板类 12 | template 13 | class threadpool{ 14 | public: 15 | // thread_number线程池中线程的数量 16 | // connPool是数据库连接池指针 17 | // max_request队列中最大请求数量 18 | threadpool(int actor_model, connection_pool *connPool, uint32_t thread_number, uint32_t max_request = 10000); 19 | ~threadpool(); 20 | // 添加请求 21 | bool append(T *request, int state); 22 | bool append_p(T *request); 23 | 24 | private: 25 | // 工作线程运行函数,需要是静态函数,因为pthread_create()第三个参数是(void *),而成员函数会编译为带有this指针参数,从而不能匹配 26 | // 静态成员函数没有this指针,但不能访问非静态成员变量,因此在内部调用run 27 | static void* worker(void *arg); 28 | void run(); 29 | 30 | private: 31 | int m_actor_model; // 处理模式,1 reactor,0 proactor 32 | connection_pool *m_connPool; // 数据库连接 33 | uint32_t m_thread_number; // 线程池中的最大线程数 34 | uint32_t m_max_request; // 请求队列中最大请求数 35 | pthread_t *m_threads; // 线程数组,大小为m_thread_number 36 | std::list m_workqueue; // 请求队列 37 | mutexlocker m_queuelocker; // 请求队列互斥锁 38 | semaphore m_queuestat; // 是否有任务需要处理信号量 39 | }; 40 | 41 | // 构造线程池,创建线程 42 | // 类成员函数参数默认值只在定义或声明其中一处对同一个参数设置 43 | template 44 | threadpool::threadpool(int actor_model, connection_pool *connPoll, uint32_t thread_number, uint32_t max_request) 45 | :m_actor_model(actor_model), m_connPool(connPoll), m_thread_number(thread_number), m_max_request(max_request), m_threads(nullptr){ 46 | if(thread_number <= 0 || max_request <= 0) 47 | throw std::exception(); 48 | m_threads = new pthread_t[m_thread_number]; 49 | if(!m_threads) 50 | throw std::exception(); 51 | for(uint32_t i = 0; i < thread_number; ++i){ 52 | // 内存单元、线程属性(默认NULL)、工作函数、传递参数(线程池) 53 | if(pthread_create(m_threads + i, NULL, worker, this) != 0){ 54 | delete[] m_threads; 55 | throw std::exception(); 56 | } 57 | // 分离主线程与子线程,子线程结束后资源自动回收 58 | if(pthread_detach(m_threads[i]) != 0){ 59 | delete[] m_threads; 60 | throw std::exception(); 61 | } 62 | } 63 | } 64 | 65 | // 析构,释放线程数组 66 | template 67 | threadpool::~threadpool(){ 68 | delete[] m_threads; 69 | } 70 | 71 | // 添加请求,并利用信号量通知工作线程 72 | template 73 | bool threadpool::append(T *request, int state){ 74 | // 任务队列是临界区,加互斥锁 75 | m_queuelocker.lock(); 76 | if(m_workqueue.size() >= m_max_request){ 77 | m_queuelocker.unlock(); 78 | return false; 79 | } 80 | // 加入请求队列,然后解锁 81 | request->m_state = state; 82 | m_workqueue.push_back(request); 83 | m_queuelocker.unlock(); 84 | // 增加信号量,表示有任务要处理 85 | m_queuestat.post(); 86 | return true; 87 | } 88 | 89 | // 添加请求,不带状态 90 | template 91 | bool threadpool::append_p(T *request){ 92 | m_queuelocker.lock(); 93 | if(m_workqueue.size() >= m_max_request){ 94 | m_queuelocker.unlock(); 95 | return false; 96 | } 97 | m_workqueue.push_back(request); 98 | m_queuelocker.unlock(); 99 | m_queuestat.post(); 100 | return true; 101 | } 102 | 103 | // 工作线程运行 104 | template 105 | void* threadpool::worker(void *arg){ 106 | // 转换为线程池类 107 | threadpool *pool = (threadpool *)arg; 108 | pool->run(); 109 | return nullptr; 110 | } 111 | 112 | // 工作函数 113 | template 114 | void threadpool::run(){ 115 | while(true){ 116 | // 信号量阻塞,等待任务 117 | m_queuestat.wait(); 118 | // 请求队列临界区,加互斥锁 119 | m_queuelocker.lock(); 120 | if(m_workqueue.empty()){ 121 | m_queuelocker.unlock(); 122 | continue; 123 | } 124 | // 从请求队列取第一个请求 125 | T *request = m_workqueue.front(); 126 | m_workqueue.pop_front(); 127 | m_queuelocker.unlock(); 128 | if(!request) 129 | continue; 130 | // reactor 131 | if(m_actor_model == 1){ 132 | // 读 133 | if(request->m_state == 0){ 134 | if(request->read_once()){ 135 | request->improv = 1; 136 | connectionRAII mysqlcon(&request->mysql, m_connPool); 137 | request->process(); 138 | } 139 | else{ 140 | request->improv = 1; 141 | request->timer_flag = 1; 142 | } 143 | } 144 | // 写 145 | else{ 146 | if(request->write()){ 147 | request->improv = 1; 148 | } 149 | else{ 150 | request->improv = 1; 151 | request->timer_flag = 1; 152 | } 153 | } 154 | } 155 | // proactor 156 | else{ 157 | // 从连接池中取出一个数据库连接 RAII 158 | connectionRAII mysqlcon(&request->mysql, m_connPool); 159 | // 处理请求 160 | request->process(); 161 | } 162 | } 163 | } 164 | 165 | #endif -------------------------------------------------------------------------------- /code/timer/README.md: -------------------------------------------------------------------------------- 1 | # 定时器 2 | 3 | ## 基础知识 4 | 5 | + 非活跃,是指客户端(这里是浏览器)与服务器端建立连接后,长时间不交换数据,一直占用服务器端的文件描述符,导致连接资源的浪费。 6 | 7 | + 定时事件,是指固定一段时间之后触发某段代码,由该段代码处理一个事件,如从内核事件表删除事件,并关闭文件描述符,释放连接资源。 8 | 9 | + 定时器,是指利用结构体或其他形式,将多种定时事件进行封装起来。具体的,这里只涉及一种定时事件,即定期检测非活跃连接,这里将该定时事件与连接资源封装为一个结构体定时器。 10 | 11 | + 定时器容器,是指使用某种容器类数据结构,将上述多个定时器组合起来,便于对定时事件统一管理。具体的,项目中使用升序链表将所有定时器串联组织起来。 12 | 13 | + Linux下三种定时方法:1)socket选项SO_RECVTIMEO和SO_SNDTIMEO。2)SIGALRM信号。3)I/O复用系统调用的超时参数 14 | 15 | ## 主要流程 16 | 17 | 利用alarm函数周期性地触发SIGALRM信号,信号处理函数利用管道通知主循环,主循环接收到该信号后对升序链表上所有定时器进行处理,若该段时间内没有交换数据,则将该连接关闭,释放所占用的资源。 18 | 19 | + 信号通知流程:进程收到信号时,操作系统中断进程进入信号处理函数。为避免信号竞态现象,信号处理函数中不会再次被信号打断,为防止信号被屏蔽过久,需要信号处理函数尽可能快地执行操作。对于处理逻辑复杂的情况,解决方案是信号处理函数仅发送信号通知程序主循环,由主循环执行对应的逻辑代码。 20 | 21 | + 统一事件源:将信号事件与其他事件一样被处理。信号处理函数使用管道将信号传递给主循环,信号处理函数往管道的写端写入信号值,主循环则从管道的读端读出信号值,使用I/O复用系统调用来监听管道读端的可读事件,这样信号事件与其他文件描述符都可以通过epoll来监测,从而实现统一处理。 22 | 23 | + 信号处理机制 24 | + 信号的接收:接收信号的任务是由内核代理的,当内核接收到信号后,会将其放到对应进程的信号队列中,同时向进程发送一个中断,使其陷入内核态。注意,此时信号还只是在队列中,对进程来说暂时是不知道有信号到来的。 25 | 26 | + 信号的检测:进程陷入内核态后,有两种场景会对信号进行检测。1)进程从内核态返回用户态前。2)进程在内核态中,从睡眠状态被唤醒时。 27 | 28 | + 信号的处理:1)信号处理函数是运行在用户态的,调用处理函数前,内核会将当前内核栈的内容备份拷贝到用户栈上,并且修改指令寄存器(eip)将其指向信号处理函数。2)接下来进程返回到用户态中,执行相应的信号处理函数。3)信号处理函数执行完成后,还需要返回内核态,检查是否还有其它信号未处理。如果所有信号都处理完成,就会将内核栈恢复(从用户栈的备份拷贝回来),同时恢复指令寄存器(eip)将其指向中断前的运行位置,最后回到用户态继续执行进程。 29 | 30 | ## 定时器的使用 31 | 32 | + 浏览器与服务器连接时,创建该连接对应的定时器,并将该定时器添加到链表上 33 | 34 | + 处理异常事件时,执行定时事件,服务器关闭连接,从链表上移除对应定时器 35 | 36 | + 处理定时信号时,将定时标志设置为true 37 | 38 | + 处理读事件时,若某连接上发生读事件,将对应定时器向后移动,否则,执行定时事件 39 | 40 | + 处理写事件时,若服务器通过某连接给浏览器发送数据,将对应定时器向后移动,否则,执行定时事件 -------------------------------------------------------------------------------- /code/timer/lst_timer.cpp: -------------------------------------------------------------------------------- 1 | #include "lst_timer.h" 2 | #include "../http/http_conn.h" 3 | 4 | // 定时器链表构造函数 5 | sort_timer_lst::sort_timer_lst(){ 6 | head = nullptr; 7 | tail = nullptr; 8 | } 9 | 10 | // 定时器链表析构函数,析构所有定时器 11 | sort_timer_lst::~sort_timer_lst(){ 12 | util_timer *tmp = head; 13 | while(tmp){ 14 | head = tmp->next; 15 | delete tmp; 16 | tmp = head; 17 | } 18 | } 19 | 20 | // 添加定时器,按过期时间升序插入 21 | void sort_timer_lst::add_timer(util_timer *timer){ 22 | if(!timer) 23 | return ; 24 | if(!head){ 25 | head = tail = timer; 26 | return ; 27 | } 28 | // 添加到链表头 29 | if(timer->expire < head->expire){ 30 | timer->next = head; 31 | head->prev = timer; 32 | head = timer; 33 | return ; 34 | } 35 | // 添加到链表中 36 | add_timer(timer, head); 37 | } 38 | 39 | // 调整定时器 40 | void sort_timer_lst::adjust_timer(util_timer *timer){ 41 | if(!timer) 42 | return ; 43 | util_timer *tmp = timer->next; 44 | if(!tmp || (timer->expire < tmp->expire)) 45 | return ; 46 | 47 | // 先从链表中删除,再重新按顺序插入 48 | if(timer == head){ 49 | head = head->next; 50 | head->prev = nullptr; 51 | timer->next = nullptr; 52 | add_timer(timer, head); 53 | } 54 | else{ 55 | timer->prev->next = timer->next; 56 | timer->next->prev = timer->prev; 57 | add_timer(timer, timer->next); 58 | } 59 | } 60 | 61 | // 删除定时器 62 | void sort_timer_lst::del_timer(util_timer *timer){ 63 | if(!timer) 64 | return ; 65 | if((timer == head) && (timer == tail)){ 66 | delete timer; 67 | head = nullptr; 68 | tail = nullptr; 69 | return ; 70 | } 71 | if(timer == head){ 72 | head = head->next; 73 | head->prev = nullptr; 74 | delete timer; 75 | return ; 76 | } 77 | if(timer == tail){ 78 | tail = tail->prev; 79 | tail->next = nullptr; 80 | delete timer; 81 | return ; 82 | } 83 | timer->prev->next = timer->next; 84 | timer->next->prev = timer->prev; 85 | delete timer; 86 | } 87 | 88 | // SIGALRM信号每次被触发,主循环中调用一次定时任务处理函数,处理链表容器中到期的定时器 89 | void sort_timer_lst::tick(){ 90 | if(!head) 91 | return ; 92 | time_t cur = time(nullptr); 93 | util_timer *tmp = head; 94 | while(tmp){ 95 | if(cur < tmp->expire) 96 | break; 97 | // 过期时间小于当前时间,释放连接 98 | tmp->cb_func(tmp->user_data); 99 | head = tmp->next; 100 | if(head) 101 | head->prev = nullptr; 102 | delete tmp; 103 | tmp = head; 104 | } 105 | } 106 | 107 | // 添加定时器,从给定head开始寻找可以插入的位置 108 | void sort_timer_lst::add_timer(util_timer *timer, util_timer *lst_head){ 109 | util_timer *prev = lst_head; 110 | util_timer *tmp = prev->next; 111 | while(tmp){ 112 | if(timer->expire < tmp->expire){ 113 | prev->next = timer; 114 | timer->next = tmp; 115 | tmp->prev = timer; 116 | timer->prev = prev; 117 | break; 118 | } 119 | prev = tmp; 120 | tmp = tmp->next; 121 | } 122 | if(!tmp){ 123 | prev->next = timer; 124 | timer->prev = prev; 125 | timer->next = nullptr; 126 | tail = timer; 127 | } 128 | } 129 | 130 | void Utils::init(int timeslot){ 131 | m_timeslot = timeslot; 132 | } 133 | 134 | // 设置非阻塞,读不到数据时返回-1,并且设置errno为EAGAIN 135 | int Utils::setnonblocking(int fd){ 136 | int old_option = fcntl(fd, F_GETFL); 137 | int new_option = old_option | O_NONBLOCK; 138 | fcntl(fd, F_SETFL, new_option); 139 | return old_option; 140 | } 141 | 142 | // 向epoll注册事件 143 | void Utils::addfd(int epollfd, int fd, bool one_shot, int TRIGMode){ 144 | epoll_event event; 145 | event.data.fd = fd; 146 | // ET模式 147 | if(TRIGMode == 1) 148 | // EPOLLRDHUP对端关闭 149 | event.events = EPOLLIN | EPOLLET | EPOLLRDHUP; 150 | // 默认LT模式 151 | else 152 | event.events = EPOLLIN | EPOLLRDHUP; 153 | // 只触发一次 154 | if(one_shot) 155 | event.events |= EPOLLONESHOT; 156 | epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event); 157 | // 设置非阻塞 158 | setnonblocking(fd); 159 | } 160 | 161 | // 信号处理函数 162 | void Utils::sig_handler(int sig){ 163 | // 为保证函数的可重入性,保留原来的errno 164 | // 可重入性表示中断后再次进入该函数,环境变量与之前相同,不会丢失数据 165 | int save_errno = errno; 166 | int msg = sig; 167 | 168 | // 将信号从管道写端写入,以字符类型传输 169 | // 回到主循环处理信号业务逻辑 170 | send(u_pipefd[1], (char *)&msg, 1, 0); 171 | 172 | // 恢复errno 173 | errno = save_errno; 174 | } 175 | 176 | // 设置信号 177 | void Utils::addsig(int sig, void (*handler)(int), bool restart){ 178 | // 创建sigaction结构体 179 | struct sigaction sa; 180 | memset(&sa, 0, sizeof(sa)); 181 | 182 | // 设置信号处理函数 183 | sa.sa_handler = handler; 184 | if(restart) 185 | // 使被信号打断的系统调用重新自动发起 186 | sa.sa_flags |= SA_RESTART; 187 | // 对信号集初始化,将所有信号添加到信号集中 188 | sigfillset(&sa.sa_mask); 189 | 190 | // 设置信号,-1表示有错误发生 191 | assert(sigaction(sig, &sa, NULL) != -1); 192 | } 193 | 194 | // 定时器触发 195 | void Utils::timer_handler(){ 196 | // 处理连接链表 197 | m_timer_lst.tick(); 198 | // 重新设置时钟 199 | alarm(m_timeslot); 200 | } 201 | 202 | void Utils::show_error(int connfd, const char *info){ 203 | send(connfd, info, strlen(info), 0); 204 | close(connfd); 205 | } 206 | 207 | int *Utils::u_pipefd = 0; 208 | int Utils::u_epollfd = 0; 209 | 210 | // 定时器回调函数,释放客户端连接 211 | void cb_func(client_data *user_data){ 212 | // 删除非活动连接在socket上的注册事件 213 | epoll_ctl(Utils::u_epollfd, EPOLL_CTL_DEL, user_data->sockfd, 0); 214 | assert(user_data); 215 | 216 | // 关闭socket 217 | close(user_data->sockfd); 218 | 219 | // 减少连接数 220 | http_conn::m_user_count--; 221 | } -------------------------------------------------------------------------------- /code/timer/lst_timer.h: -------------------------------------------------------------------------------- 1 | #ifndef LST_TIMER_H 2 | #define LST_TIMER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include "../log/log.h" 25 | 26 | // 前向声明 27 | class util_timer; 28 | 29 | // 客户端数据结构体 30 | struct client_data{ 31 | sockaddr_in address; // 客户端地址 32 | int sockfd; // 客户socket 33 | util_timer *timer; // 定时器 34 | }; 35 | 36 | // 定时器类 37 | class util_timer{ 38 | public: 39 | util_timer():prev(nullptr), next(nullptr){} 40 | 41 | public: 42 | time_t expire; // 超时时间 43 | void (*cb_func)(client_data*); // 回调函数 44 | client_data *user_data; // 连接资源 45 | util_timer *prev; // 前指针 46 | util_timer *next; // 后指针 47 | }; 48 | 49 | // 定时器双向链表,按过期时间升序排序 50 | class sort_timer_lst{ 51 | public: 52 | sort_timer_lst(); 53 | ~sort_timer_lst(); 54 | 55 | void add_timer(util_timer *timer); // 添加定时器 56 | void adjust_timer(util_timer *timer); // 调整定时器 57 | void del_timer(util_timer *timer); // 删除定时器 58 | void tick(); // 定时任务处理函数 59 | 60 | private: 61 | void add_timer(util_timer *timer, util_timer *lst_head); // 辅助添加函数 62 | 63 | util_timer *head; // 链表头 64 | util_timer *tail; // 链表尾 65 | }; 66 | 67 | // 通用类 68 | class Utils{ 69 | public: 70 | Utils(){} 71 | ~Utils(){} 72 | 73 | // 静态函数避免this指针 74 | static void sig_handler(int sig); 75 | 76 | void init(int timeslot); 77 | int setnonblocking(int fd); 78 | void addfd(int epollfd, int fd, bool one_shot, int TRIGMode); 79 | void addsig(int sig, void(*handler)(int), bool restart = true); 80 | void timer_handler(); 81 | void show_error(int connfd, const char *info); 82 | 83 | public: 84 | static int *u_pipefd; // 本地套接字 85 | static int u_epollfd; // epoll句柄 86 | sort_timer_lst m_timer_lst; // 定时器链表 87 | int m_timeslot; // 定时时间 88 | }; 89 | 90 | void cb_func(client_data *user_data); 91 | 92 | #endif -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | CXX ?= g++ 2 | CFLAGS = -std=c++11 -O2 -Wall -g 3 | 4 | TARGET = server 5 | OBJS = ./code/log/*.cpp ./code/timer/*.cpp \ 6 | ./code/http/*.cpp ./code/server/*.cpp ./code/CGImysql/*.cpp \ 7 | ./code/config/*.cpp ./code/main.cpp 8 | 9 | server: $(OBJS) 10 | $(CXX) $(CFLAGS) $(OBJS) -o ./bin/$(TARGET) -pthread -lmysqlclient 11 | 12 | clean: 13 | rm -r ./bin/server -------------------------------------------------------------------------------- /resource/README.md: -------------------------------------------------------------------------------- 1 | 界面跳转 2 | =============== 3 | 对html中action行为设置标志位,将method设置为POST 4 | > * 0 注册 5 | > * 1 登录 6 | > * 2 登录检测 7 | > * 3 注册检测 8 | > * 5 请求图片 9 | > * 6 请求视频 10 | > * 7 关注我 11 | -------------------------------------------------------------------------------- /resource/fans.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | awsl 6 | 7 |
8 |
9 |
嘿嘿,你来啦,更多资料,请关注 “两猿社” 喔.
10 |
11 | 12 |
13 |
14 |
15 | 16 | -------------------------------------------------------------------------------- /resource/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vatica/TinyWebSever-CPP/d8bacaee34a5eba32479a00457e3e0a928841179/resource/favicon.ico -------------------------------------------------------------------------------- /resource/frame.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vatica/TinyWebSever-CPP/d8bacaee34a5eba32479a00457e3e0a928841179/resource/frame.jpg -------------------------------------------------------------------------------- /resource/judge.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | WebServer 6 | 7 | 8 |
9 |
10 |
欢迎访问
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /resource/log.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sign in 6 | 7 | 8 |
9 |
10 |
登录
11 |
12 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /resource/logError.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sign in 6 | 7 | 8 |
9 |
10 |
登录
11 |
12 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /resource/login.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vatica/TinyWebSever-CPP/d8bacaee34a5eba32479a00457e3e0a928841179/resource/login.gif -------------------------------------------------------------------------------- /resource/loginnew.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vatica/TinyWebSever-CPP/d8bacaee34a5eba32479a00457e3e0a928841179/resource/loginnew.gif -------------------------------------------------------------------------------- /resource/picture.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vatica/TinyWebSever-CPP/d8bacaee34a5eba32479a00457e3e0a928841179/resource/picture.gif -------------------------------------------------------------------------------- /resource/picture.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | awsl 6 | 7 |
8 |
9 |
你居然想看图,不想关注我
10 |
11 | 12 |
13 |
14 |
15 | 16 | -------------------------------------------------------------------------------- /resource/register.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vatica/TinyWebSever-CPP/d8bacaee34a5eba32479a00457e3e0a928841179/resource/register.gif -------------------------------------------------------------------------------- /resource/register.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sign up 6 | 7 | 8 |
9 |
10 |
注册
11 |
12 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /resource/registerError.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sign up 6 | 7 | 8 |
9 |
10 |
注册
11 |
12 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /resource/registernew.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vatica/TinyWebSever-CPP/d8bacaee34a5eba32479a00457e3e0a928841179/resource/registernew.gif -------------------------------------------------------------------------------- /resource/test1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vatica/TinyWebSever-CPP/d8bacaee34a5eba32479a00457e3e0a928841179/resource/test1.jpg -------------------------------------------------------------------------------- /resource/video.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vatica/TinyWebSever-CPP/d8bacaee34a5eba32479a00457e3e0a928841179/resource/video.gif -------------------------------------------------------------------------------- /resource/video.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | awsl 6 | 7 |
8 |
9 |
你居然想看视频,不想关注我
10 |
11 | 12 |
13 |
14 |
17 | 18 | -------------------------------------------------------------------------------- /resource/welcome.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | WebServer 6 | 7 | 8 |
9 |
10 |
是时候做出选择了
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /resource/xxx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vatica/TinyWebSever-CPP/d8bacaee34a5eba32479a00457e3e0a928841179/resource/xxx.jpg -------------------------------------------------------------------------------- /resource/xxx.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vatica/TinyWebSever-CPP/d8bacaee34a5eba32479a00457e3e0a928841179/resource/xxx.mp4 --------------------------------------------------------------------------------