Repository: qinguoyi/TinyWebServer Branch: master Commit: 4bcf88762f85 Files: 51 Total size: 107.9 KB Directory structure: gitextract_1m5tla9o/ ├── CGImysql/ │ ├── README.md │ ├── sql_connection_pool.cpp │ └── sql_connection_pool.h ├── LICENSE ├── README.md ├── build.sh ├── config.cpp ├── config.h ├── http/ │ ├── README.md │ ├── http_conn.cpp │ └── http_conn.h ├── lock/ │ ├── README.md │ └── locker.h ├── log/ │ ├── README.md │ ├── block_queue.h │ ├── log.cpp │ └── log.h ├── main.cpp ├── makefile ├── root/ │ ├── README.md │ ├── fans.html │ ├── judge.html │ ├── log.html │ ├── logError.html │ ├── picture.html │ ├── register.html │ ├── registerError.html │ ├── video.html │ └── welcome.html ├── test_pressure/ │ ├── README.md │ └── webbench-1.5/ │ ├── COPYRIGHT │ ├── ChangeLog │ ├── Makefile │ ├── debian/ │ │ ├── changelog │ │ ├── control │ │ ├── copyright │ │ ├── dirs │ │ └── rules │ ├── socket.c │ ├── tags │ ├── webbench │ ├── webbench.1 │ ├── webbench.c │ └── webbench.o ├── threadpool/ │ ├── README.md │ └── threadpool.h ├── timer/ │ ├── README.md │ ├── lst_timer.cpp │ └── lst_timer.h ├── webserver.cpp └── webserver.h ================================================ FILE CONTENTS ================================================ ================================================ FILE: CGImysql/README.md ================================================ 校验 & 数据库连接池 =============== 数据库连接池 > * 单例模式,保证唯一 > * list实现连接池 > * 连接池为静态大小 > * 互斥锁实现线程安全 校验 > * HTTP请求采用POST方式 > * 登录用户名和密码校验 > * 用户注册及多线程注册安全 ================================================ FILE: CGImysql/sql_connection_pool.cpp ================================================ #include #include #include #include #include #include #include #include #include "sql_connection_pool.h" using namespace std; connection_pool::connection_pool() { m_CurConn = 0; m_FreeConn = 0; } connection_pool *connection_pool::GetInstance() { static connection_pool connPool; return &connPool; } //构造初始化 void connection_pool::init(string url, string User, string PassWord, string DBName, int Port, int MaxConn, int close_log) { m_url = url; m_Port = Port; m_User = User; m_PassWord = PassWord; m_DatabaseName = DBName; m_close_log = close_log; for (int i = 0; i < MaxConn; i++) { MYSQL *con = NULL; con = mysql_init(con); if (con == NULL) { LOG_ERROR("MySQL Error"); exit(1); } con = mysql_real_connect(con, url.c_str(), User.c_str(), PassWord.c_str(), DBName.c_str(), Port, NULL, 0); if (con == NULL) { LOG_ERROR("MySQL Error"); exit(1); } connList.push_back(con); ++m_FreeConn; } reserve = sem(m_FreeConn); m_MaxConn = m_FreeConn; } //当有请求时,从数据库连接池中返回一个可用连接,更新使用和空闲连接数 MYSQL *connection_pool::GetConnection() { MYSQL *con = NULL; if (0 == connList.size()) return NULL; reserve.wait(); lock.lock(); con = connList.front(); connList.pop_front(); --m_FreeConn; ++m_CurConn; lock.unlock(); return con; } //释放当前使用的连接 bool connection_pool::ReleaseConnection(MYSQL *con) { if (NULL == con) return false; lock.lock(); connList.push_back(con); ++m_FreeConn; --m_CurConn; lock.unlock(); reserve.post(); return true; } //销毁数据库连接池 void connection_pool::DestroyPool() { lock.lock(); if (connList.size() > 0) { list::iterator it; for (it = connList.begin(); it != connList.end(); ++it) { MYSQL *con = *it; mysql_close(con); } m_CurConn = 0; m_FreeConn = 0; connList.clear(); } lock.unlock(); } //当前空闲的连接数 int connection_pool::GetFreeConn() { return this->m_FreeConn; } connection_pool::~connection_pool() { DestroyPool(); } connectionRAII::connectionRAII(MYSQL **SQL, connection_pool *connPool){ *SQL = connPool->GetConnection(); conRAII = *SQL; poolRAII = connPool; } connectionRAII::~connectionRAII(){ poolRAII->ReleaseConnection(conRAII); } ================================================ FILE: CGImysql/sql_connection_pool.h ================================================ #ifndef _CONNECTION_POOL_ #define _CONNECTION_POOL_ #include #include #include #include #include #include #include #include "../lock/locker.h" #include "../log/log.h" using namespace std; class connection_pool { public: MYSQL *GetConnection(); //获取数据库连接 bool ReleaseConnection(MYSQL *conn); //释放连接 int GetFreeConn(); //获取连接 void DestroyPool(); //销毁所有连接 //单例模式 static connection_pool *GetInstance(); void init(string url, string User, string PassWord, string DataBaseName, int Port, int MaxConn, int close_log); private: connection_pool(); ~connection_pool(); int m_MaxConn; //最大连接数 int m_CurConn; //当前已使用的连接数 int m_FreeConn; //当前空闲的连接数 locker lock; list connList; //连接池 sem reserve; public: string m_url; //主机地址 string m_Port; //数据库端口号 string m_User; //登陆数据库用户名 string m_PassWord; //登陆数据库密码 string m_DatabaseName; //使用数据库名 int m_close_log; //日志开关 }; class connectionRAII{ public: connectionRAII(MYSQL **con, connection_pool *connPool); ~connectionRAII(); private: MYSQL *conRAII; connection_pool *poolRAII; }; #endif ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ TinyWebServer =============== Linux下C++轻量级Web服务器,助力初学者快速实践网络编程,搭建属于自己的服务器. * 使用 **线程池 + 非阻塞socket + epoll(ET和LT均实现) + 事件处理(Reactor和模拟Proactor均实现)** 的并发模型 * 使用**状态机**解析HTTP请求报文,支持解析**GET和POST**请求 * 访问服务器数据库实现web端用户**注册、登录**功能,可以请求服务器**图片和视频文件** * 实现**同步/异步日志系统**,记录服务器运行状态 * 经Webbench压力测试可以实现**上万的并发连接**数据交换 写在前面 ---- * 本项目开发维护过程中,很多童鞋曾发红包支持,我都一一谢绝。我现在不会,将来也不会将本项目包装成任何课程售卖,更不会开通任何支持通道。 * 目前网络上有人或对本项目,或对游双大佬的项目包装成课程售卖。请各位童鞋擦亮眼,辨识各大学习/求职网站的C++服务器项目,不要盲目付费。 * 有面试官大佬通过项目信息在公司内找到我,发现很多童鞋简历上都用了这个项目。但,在面试过程中发现`很多童鞋通过本项目入门了,但是对于一些东西还是属于知其然不知其所以然的状态,需要加强下基础知识的学习`,推荐认真阅读下 * 《unix环境高级编程》 * 《unix网络编程》 * 感谢各位大佬,各位朋友,各位童鞋的认可和支持。如果本项目能带你入门,将是我莫大的荣幸。 目录 ----- | [概述](#概述) | [框架](#框架) | [Demo演示](#Demo演示) | [压力测试](#压力测试) |[更新日志](#更新日志) |[源码下载](#源码下载) | [快速运行](#快速运行) | [个性化运行](#个性化运行) | [庖丁解牛](#庖丁解牛) | [CPP11实现](#CPP11实现) |[致谢](#致谢) | |:--------:|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:| 概述 ---------- > * C/C++ > * B/S模型 > * [线程同步机制包装类](https://github.com/qinguoyi/TinyWebServer/tree/master/lock) > * [http连接请求处理类](https://github.com/qinguoyi/TinyWebServer/tree/master/http) > * [半同步/半反应堆线程池](https://github.com/qinguoyi/TinyWebServer/tree/master/threadpool) > * [定时器处理非活动连接](https://github.com/qinguoyi/TinyWebServer/tree/master/timer) > * [同步/异步日志系统 ](https://github.com/qinguoyi/TinyWebServer/tree/master/log) > * [数据库连接池](https://github.com/qinguoyi/TinyWebServer/tree/master/CGImysql) > * [同步线程注册和登录校验](https://github.com/qinguoyi/TinyWebServer/tree/master/CGImysql) > * [简易服务器压力测试](https://github.com/qinguoyi/TinyWebServer/tree/master/test_presure) 框架 -------------
Demo演示 ---------- > * 注册演示
> * 登录演示
> * 请求图片文件演示(6M)
> * 请求视频文件演示(39M)
压力测试 ------------- 在关闭日志后,使用Webbench对服务器进行压力测试,对listenfd和connfd分别采用ET和LT模式,均可实现上万的并发连接,下面列出的是两者组合后的测试结果. > * Proactor,LT + LT,93251 QPS
> * Proactor,LT + ET,97459 QPS
> * Proactor,ET + LT,80498 QPS
> * Proactor,ET + ET,92167 QPS
> * Reactor,LT + ET,69175 QPS
> * 并发连接总数:10500 > * 访问服务器时间:5s > * 所有访问均成功 **注意:** 使用本项目的webbench进行压测时,若报错显示webbench命令找不到,将可执行文件webbench删除后,重新编译即可。 更新日志 ------- - [x] 解决请求服务器上大文件的Bug - [x] 增加请求视频文件的页面 - [x] 解决数据库同步校验内存泄漏 - [x] 实现非阻塞模式下的ET和LT触发,并完成压力测试 - [x] 完善`lock.h`中的封装类,统一使用该同步机制 - [x] 改进代码结构,更新局部变量懒汉单例模式 - [x] 优化数据库连接池信号量与代码结构 - [x] 使用RAII机制优化数据库连接的获取与释放 - [x] 优化代码结构,封装工具类以减少全局变量 - [x] 编译一次即可,命令行进行个性化测试更加友好 - [x] main函数封装重构 - [x] 新增命令行日志开关,关闭日志后更新压力测试结果 - [x] 改进编译方式,只配置一次SQL信息即可 - [x] 新增Reactor模式,并完成压力测试 源码下载 ------- 目前有两个版本,版本间的代码结构有较大改动,文档和代码运行方法也不一致。重构版本更简洁,原始版本(raw_version)更大保留游双代码的原汁原味,从原始版本更容易入手. 如果遇到github代码下载失败,或访问太慢,可以从以下链接下载,与Github最新提交同步. * 重构版本下载地址 : [BaiduYun](https://pan.baidu.com/s/1PozKji8Oop-1BYcfixZR0g) * 提取码 : vsqq * 原始版本(raw_version)下载地址 : [BaiduYun](https://pan.baidu.com/s/1asMNDW-zog92DZY1Oa4kaQ) * 提取码 : 9wye * 原始版本运行请参考[原始文档](https://github.com/qinguoyi/TinyWebServer/tree/raw_version) 快速运行 ------------ * 服务器测试环境 * Ubuntu版本16.04 * MySQL版本5.7.29 * 浏览器测试环境 * Windows、Linux均可 * Chrome * FireFox * 其他浏览器暂无测试 * 测试前确认已安装MySQL数据库 ```C++ // 建立yourdb库 create database yourdb; // 创建user表 USE yourdb; CREATE TABLE user( username char(50) NULL, passwd char(50) NULL )ENGINE=InnoDB; // 添加数据 INSERT INTO user(username, passwd) VALUES('name', 'passwd'); ``` * 修改main.cpp中的数据库初始化信息 ```C++ //数据库登录名,密码,库名 string user = "root"; string passwd = "root"; string databasename = "yourdb"; ``` * build ```C++ sh ./build.sh ``` * 启动server ```C++ ./server ``` * 浏览器端 ```C++ ip:9006 ``` 个性化运行 ------ ```C++ ./server [-p port] [-l LOGWrite] [-m TRIGMode] [-o OPT_LINGER] [-s sql_num] [-t thread_num] [-c close_log] [-a actor_model] ``` 温馨提示:以上参数不是非必须,不用全部使用,根据个人情况搭配选用即可. * -p,自定义端口号 * 默认9006 * -l,选择日志写入方式,默认同步写入 * 0,同步写入 * 1,异步写入 * -m,listenfd和connfd的模式组合,默认使用LT + LT * 0,表示使用LT + LT * 1,表示使用LT + ET * 2,表示使用ET + LT * 3,表示使用ET + ET * -o,优雅关闭连接,默认不使用 * 0,不使用 * 1,使用 * -s,数据库连接数量 * 默认为8 * -t,线程数量 * 默认为8 * -c,关闭日志,默认打开 * 0,打开日志 * 1,关闭日志 * -a,选择反应堆模型,默认Proactor * 0,Proactor模型 * 1,Reactor模型 测试示例命令与含义 ```C++ ./server -p 9007 -l 1 -m 0 -o 1 -s 10 -t 10 -c 1 -a 1 ``` - [x] 端口9007 - [x] 异步写入日志 - [x] 使用LT + LT组合 - [x] 使用优雅关闭连接 - [x] 数据库连接池内有10条连接 - [x] 线程池内有10条线程 - [x] 关闭日志 - [x] Reactor反应堆模型 庖丁解牛 ------------ 近期版本迭代较快,以下内容多以旧版本(raw_version)代码为蓝本进行详解. * [小白视角:一文读懂社长的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) * [最新版Web服务器项目详解 - 01 线程同步机制封装类](https://mp.weixin.qq.com/s?__biz=MzAxNzU2MzcwMw==&mid=2649274278&idx=3&sn=5840ff698e3f963c7855d702e842ec47&chksm=83ffbefeb48837e86fed9754986bca6db364a6fe2e2923549a378e8e5dec6e3cf732cdb198e2&scene=0&xtrack=1#rd) * [最新版Web服务器项目详解 - 02 半同步半反应堆线程池(上)](https://mp.weixin.qq.com/s?__biz=MzAxNzU2MzcwMw==&mid=2649274278&idx=4&sn=caa323faf0c51d882453c0e0c6a62282&chksm=83ffbefeb48837e841a6dbff292217475d9075e91cbe14042ad6e55b87437dcd01e6d9219e7d&scene=0&xtrack=1#rd) * [最新版Web服务器项目详解 - 03 半同步半反应堆线程池(下)](https://mp.weixin.qq.com/s/PB8vMwi8sB4Jw3WzAKpWOQ) * [最新版Web服务器项目详解 - 04 http连接处理(上)](https://mp.weixin.qq.com/s/BfnNl-3jc_x5WPrWEJGdzQ) * [最新版Web服务器项目详解 - 05 http连接处理(中)](https://mp.weixin.qq.com/s/wAQHU-QZiRt1VACMZZjNlw) * [最新版Web服务器项目详解 - 06 http连接处理(下)](https://mp.weixin.qq.com/s/451xNaSFHxcxfKlPBV3OCg) * [最新版Web服务器项目详解 - 07 定时器处理非活动连接(上)](https://mp.weixin.qq.com/s/mmXLqh_NywhBXJvI45hchA) * [最新版Web服务器项目详解 - 08 定时器处理非活动连接(下)](https://mp.weixin.qq.com/s/fb_OUnlV1SGuOUdrGrzVgg) * [最新版Web服务器项目详解 - 09 日志系统(上)](https://mp.weixin.qq.com/s/IWAlPzVDkR2ZRI5iirEfCg) * [最新版Web服务器项目详解 - 10 日志系统(下)](https://mp.weixin.qq.com/s/f-ujwFyCe1LZa3EB561ehA) * [最新版Web服务器项目详解 - 11 数据库连接池](https://mp.weixin.qq.com/s?__biz=MzAxNzU2MzcwMw==&mid=2649274326&idx=1&sn=5af78e2bf6552c46ae9ab2aa22faf839&chksm=83ffbe8eb4883798c3abb82ddd124c8100a39ef41ab8d04abe42d344067d5e1ac1b0cac9d9a3&token=1450918099&lang=zh_CN#rd) * [最新版Web服务器项目详解 - 12 注册登录](https://mp.weixin.qq.com/s?__biz=MzAxNzU2MzcwMw==&mid=2649274431&idx=4&sn=7595a70f06a79cb7abaebcd939e0cbee&chksm=83ffb167b4883871ce110aeb23e04acf835ef41016517247263a2c3ab6f8e615607858127ea6&token=1686112912&lang=zh_CN#rd) * [最新版Web服务器项目详解 - 13 踩坑与面试题](https://mp.weixin.qq.com/s?__biz=MzAxNzU2MzcwMw==&mid=2649274431&idx=1&sn=2dd28c92f5d9704a57c001a3d2630b69&chksm=83ffb167b48838715810b27b8f8b9a576023ee5c08a8e5d91df5baf396732de51268d1bf2a4e&token=1686112912&lang=zh_CN#rd) * 已更新完毕 Star History --------- [![Star History Chart](https://api.star-history.com/svg?repos=qinguoyi/TinyWebServer&type=Date)](https://star-history.com/#qinguoyi/TinyWebServer&Date) CPP11实现 ------------ 更简洁,更优雅的CPP11实现:[Webserver](https://github.com/markparticle/WebServer) 致谢 ------------ Linux高性能服务器编程,游双著. 感谢以下朋友的PR和帮助: [@RownH](https://github.com/RownH),[@mapleFU](https://github.com/mapleFU),[@ZWiley](https://github.com/ZWiley),[@zjuHong](https://github.com/zjuHong),[@mamil](https://github.com/mamil),[@byfate](https://github.com/byfate),[@MaJun827](https://github.com/MaJun827),[@BBLiu-coder](https://github.com/BBLiu-coder),[@smoky96](https://github.com/smoky96),[@yfBong](https://github.com/yfBong),[@liuwuyao](https://github.com/liuwuyao),[@Huixxi](https://github.com/Huixxi),[@markparticle](https://github.com/markparticle),[@blogg9ggg](https://github.com/Blogg9ggg). ================================================ FILE: build.sh ================================================ #!/bin/bash make server ================================================ FILE: config.cpp ================================================ #include "config.h" Config::Config(){ //端口号,默认9006 PORT = 9006; //日志写入方式,默认同步 LOGWrite = 0; //触发组合模式,默认listenfd LT + connfd LT TRIGMode = 0; //listenfd触发模式,默认LT LISTENTrigmode = 0; //connfd触发模式,默认LT CONNTrigmode = 0; //优雅关闭链接,默认不使用 OPT_LINGER = 0; //数据库连接池数量,默认8 sql_num = 8; //线程池内的线程数量,默认8 thread_num = 8; //关闭日志,默认不关闭 close_log = 0; //并发模型,默认是proactor actor_model = 0; } void Config::parse_arg(int argc, char*argv[]){ int opt; const char *str = "p:l:m:o:s:t:c:a:"; while ((opt = getopt(argc, argv, str)) != -1) { switch (opt) { case 'p': { PORT = atoi(optarg); break; } case 'l': { LOGWrite = atoi(optarg); break; } case 'm': { TRIGMode = atoi(optarg); break; } case 'o': { OPT_LINGER = atoi(optarg); break; } case 's': { sql_num = atoi(optarg); break; } case 't': { thread_num = atoi(optarg); break; } case 'c': { close_log = atoi(optarg); break; } case 'a': { actor_model = atoi(optarg); break; } default: break; } } } ================================================ FILE: config.h ================================================ #ifndef CONFIG_H #define CONFIG_H #include "webserver.h" using namespace std; class Config { public: Config(); ~Config(){}; void parse_arg(int argc, char*argv[]); //端口号 int PORT; //日志写入方式 int LOGWrite; //触发组合模式 int TRIGMode; //listenfd触发模式 int LISTENTrigmode; //connfd触发模式 int CONNTrigmode; //优雅关闭链接 int OPT_LINGER; //数据库连接池数量 int sql_num; //线程池内的线程数量 int thread_num; //是否关闭日志 int close_log; //并发模型选择 int actor_model; }; #endif ================================================ FILE: http/README.md ================================================ http连接处理类 =============== 根据状态转移,通过主从状态机封装了http连接类。其中,主状态机在内部调用从状态机,从状态机将处理状态和数据传给主状态机 > * 客户端发出http连接请求 > * 从状态机读取数据,更新自身状态和接收数据,传给主状态机 > * 主状态机根据从状态机状态,更新自身状态,决定响应请求还是继续读取 ================================================ FILE: http/http_conn.cpp ================================================ #include "http_conn.h" #include #include //定义http响应的一些状态信息 const char *ok_200_title = "OK"; const char *error_400_title = "Bad Request"; const char *error_400_form = "Your request has bad syntax or is inherently impossible to staisfy.\n"; const char *error_403_title = "Forbidden"; const char *error_403_form = "You do not have permission to get file form this server.\n"; const char *error_404_title = "Not Found"; const char *error_404_form = "The requested file was not found on this server.\n"; const char *error_500_title = "Internal Error"; const char *error_500_form = "There was an unusual problem serving the request file.\n"; locker m_lock; map users; void http_conn::initmysql_result(connection_pool *connPool) { //先从连接池中取一个连接 MYSQL *mysql = NULL; connectionRAII mysqlcon(&mysql, connPool); //在user表中检索username,passwd数据,浏览器端输入 if (mysql_query(mysql, "SELECT username,passwd FROM user")) { LOG_ERROR("SELECT error:%s\n", mysql_error(mysql)); } //从表中检索完整的结果集 MYSQL_RES *result = mysql_store_result(mysql); //返回结果集中的列数 int num_fields = mysql_num_fields(result); //返回所有字段结构的数组 MYSQL_FIELD *fields = mysql_fetch_fields(result); //从结果集中获取下一行,将对应的用户名和密码,存入map中 while (MYSQL_ROW row = mysql_fetch_row(result)) { string temp1(row[0]); string temp2(row[1]); users[temp1] = temp2; } } //对文件描述符设置非阻塞 int setnonblocking(int fd) { int old_option = fcntl(fd, F_GETFL); int new_option = old_option | O_NONBLOCK; fcntl(fd, F_SETFL, new_option); return old_option; } //将内核事件表注册读事件,ET模式,选择开启EPOLLONESHOT void addfd(int epollfd, int fd, bool one_shot, int TRIGMode) { epoll_event event; event.data.fd = fd; if (1 == TRIGMode) event.events = EPOLLIN | EPOLLET | EPOLLRDHUP; else event.events = EPOLLIN | EPOLLRDHUP; if (one_shot) event.events |= EPOLLONESHOT; epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event); setnonblocking(fd); } //从内核时间表删除描述符 void removefd(int epollfd, int fd) { epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, 0); close(fd); } //将事件重置为EPOLLONESHOT void modfd(int epollfd, int fd, int ev, int TRIGMode) { epoll_event event; event.data.fd = fd; if (1 == TRIGMode) event.events = ev | EPOLLET | EPOLLONESHOT | EPOLLRDHUP; else event.events = ev | EPOLLONESHOT | EPOLLRDHUP; epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &event); } int http_conn::m_user_count = 0; int http_conn::m_epollfd = -1; //关闭连接,关闭一个连接,客户总量减一 void http_conn::close_conn(bool real_close) { if (real_close && (m_sockfd != -1)) { printf("close %d\n", m_sockfd); removefd(m_epollfd, m_sockfd); m_sockfd = -1; m_user_count--; } } //初始化连接,外部调用初始化套接字地址 void http_conn::init(int sockfd, const sockaddr_in &addr, char *root, int TRIGMode, int close_log, string user, string passwd, string sqlname) { m_sockfd = sockfd; m_address = addr; addfd(m_epollfd, sockfd, true, m_TRIGMode); m_user_count++; //当浏览器出现连接重置时,可能是网站根目录出错或http响应格式出错或者访问的文件中内容完全为空 doc_root = root; m_TRIGMode = TRIGMode; m_close_log = close_log; strcpy(sql_user, user.c_str()); strcpy(sql_passwd, passwd.c_str()); strcpy(sql_name, sqlname.c_str()); init(); } //初始化新接受的连接 //check_state默认为分析请求行状态 void http_conn::init() { mysql = NULL; bytes_to_send = 0; bytes_have_send = 0; m_check_state = CHECK_STATE_REQUESTLINE; m_linger = false; m_method = GET; m_url = 0; m_version = 0; m_content_length = 0; m_host = 0; m_start_line = 0; m_checked_idx = 0; m_read_idx = 0; m_write_idx = 0; cgi = 0; m_state = 0; timer_flag = 0; improv = 0; memset(m_read_buf, '\0', READ_BUFFER_SIZE); memset(m_write_buf, '\0', WRITE_BUFFER_SIZE); memset(m_real_file, '\0', FILENAME_LEN); } //从状态机,用于分析出一行内容 //返回值为行的读取状态,有LINE_OK,LINE_BAD,LINE_OPEN http_conn::LINE_STATUS http_conn::parse_line() { char temp; for (; m_checked_idx < m_read_idx; ++m_checked_idx) { temp = m_read_buf[m_checked_idx]; if (temp == '\r') { if ((m_checked_idx + 1) == m_read_idx) return LINE_OPEN; else if (m_read_buf[m_checked_idx + 1] == '\n') { m_read_buf[m_checked_idx++] = '\0'; m_read_buf[m_checked_idx++] = '\0'; return LINE_OK; } return LINE_BAD; } else if (temp == '\n') { if (m_checked_idx > 1 && m_read_buf[m_checked_idx - 1] == '\r') { m_read_buf[m_checked_idx - 1] = '\0'; m_read_buf[m_checked_idx++] = '\0'; return LINE_OK; } return LINE_BAD; } } return LINE_OPEN; } //循环读取客户数据,直到无数据可读或对方关闭连接 //非阻塞ET工作模式下,需要一次性将数据读完 bool http_conn::read_once() { if (m_read_idx >= READ_BUFFER_SIZE) { return false; } int bytes_read = 0; //LT读取数据 if (0 == m_TRIGMode) { bytes_read = recv(m_sockfd, m_read_buf + m_read_idx, READ_BUFFER_SIZE - m_read_idx, 0); m_read_idx += bytes_read; if (bytes_read <= 0) { return false; } return true; } //ET读数据 else { while (true) { bytes_read = recv(m_sockfd, m_read_buf + m_read_idx, READ_BUFFER_SIZE - m_read_idx, 0); if (bytes_read == -1) { if (errno == EAGAIN || errno == EWOULDBLOCK) break; return false; } else if (bytes_read == 0) { return false; } m_read_idx += bytes_read; } return true; } } //解析http请求行,获得请求方法,目标url及http版本号 http_conn::HTTP_CODE http_conn::parse_request_line(char *text) { m_url = strpbrk(text, " \t"); if (!m_url) { return BAD_REQUEST; } *m_url++ = '\0'; char *method = text; if (strcasecmp(method, "GET") == 0) m_method = GET; else if (strcasecmp(method, "POST") == 0) { m_method = POST; cgi = 1; } else return BAD_REQUEST; m_url += strspn(m_url, " \t"); m_version = strpbrk(m_url, " \t"); if (!m_version) return BAD_REQUEST; *m_version++ = '\0'; m_version += strspn(m_version, " \t"); if (strcasecmp(m_version, "HTTP/1.1") != 0) return BAD_REQUEST; if (strncasecmp(m_url, "http://", 7) == 0) { m_url += 7; m_url = strchr(m_url, '/'); } if (strncasecmp(m_url, "https://", 8) == 0) { m_url += 8; m_url = strchr(m_url, '/'); } if (!m_url || m_url[0] != '/') return BAD_REQUEST; //当url为/时,显示判断界面 if (strlen(m_url) == 1) strcat(m_url, "judge.html"); m_check_state = CHECK_STATE_HEADER; return NO_REQUEST; } //解析http请求的一个头部信息 http_conn::HTTP_CODE http_conn::parse_headers(char *text) { if (text[0] == '\0') { if (m_content_length != 0) { m_check_state = CHECK_STATE_CONTENT; return NO_REQUEST; } return GET_REQUEST; } else if (strncasecmp(text, "Connection:", 11) == 0) { text += 11; text += strspn(text, " \t"); if (strcasecmp(text, "keep-alive") == 0) { m_linger = true; } } else if (strncasecmp(text, "Content-length:", 15) == 0) { text += 15; text += strspn(text, " \t"); m_content_length = atol(text); } else if (strncasecmp(text, "Host:", 5) == 0) { text += 5; text += strspn(text, " \t"); m_host = text; } else { LOG_INFO("oop!unknow header: %s", text); } return NO_REQUEST; } //判断http请求是否被完整读入 http_conn::HTTP_CODE http_conn::parse_content(char *text) { if (m_read_idx >= (m_content_length + m_checked_idx)) { text[m_content_length] = '\0'; //POST请求中最后为输入的用户名和密码 m_string = text; return GET_REQUEST; } return NO_REQUEST; } http_conn::HTTP_CODE http_conn::process_read() { LINE_STATUS line_status = LINE_OK; HTTP_CODE ret = NO_REQUEST; char *text = 0; while ((m_check_state == CHECK_STATE_CONTENT && line_status == LINE_OK) || ((line_status = parse_line()) == LINE_OK)) { text = get_line(); m_start_line = m_checked_idx; LOG_INFO("%s", text); switch (m_check_state) { case CHECK_STATE_REQUESTLINE: { ret = parse_request_line(text); if (ret == BAD_REQUEST) return BAD_REQUEST; break; } case CHECK_STATE_HEADER: { ret = parse_headers(text); if (ret == BAD_REQUEST) return BAD_REQUEST; else if (ret == GET_REQUEST) { return do_request(); } break; } case CHECK_STATE_CONTENT: { ret = parse_content(text); if (ret == GET_REQUEST) return do_request(); line_status = LINE_OPEN; break; } default: return INTERNAL_ERROR; } } return NO_REQUEST; } http_conn::HTTP_CODE http_conn::do_request() { strcpy(m_real_file, doc_root); int len = strlen(doc_root); //printf("m_url:%s\n", m_url); const char *p = strrchr(m_url, '/'); //处理cgi if (cgi == 1 && (*(p + 1) == '2' || *(p + 1) == '3')) { //根据标志判断是登录检测还是注册检测 char flag = m_url[1]; char *m_url_real = (char *)malloc(sizeof(char) * 200); strcpy(m_url_real, "/"); strcat(m_url_real, m_url + 2); strncpy(m_real_file + len, m_url_real, FILENAME_LEN - len - 1); free(m_url_real); //将用户名和密码提取出来 //user=123&passwd=123 char name[100], password[100]; int i; for (i = 5; m_string[i] != '&'; ++i) name[i - 5] = m_string[i]; name[i - 5] = '\0'; int j = 0; for (i = i + 10; m_string[i] != '\0'; ++i, ++j) password[j] = m_string[i]; password[j] = '\0'; if (*(p + 1) == '3') { //如果是注册,先检测数据库中是否有重名的 //没有重名的,进行增加数据 char *sql_insert = (char *)malloc(sizeof(char) * 200); strcpy(sql_insert, "INSERT INTO user(username, passwd) VALUES("); strcat(sql_insert, "'"); strcat(sql_insert, name); strcat(sql_insert, "', '"); strcat(sql_insert, password); strcat(sql_insert, "')"); if (users.find(name) == users.end()) { m_lock.lock(); int res = mysql_query(mysql, sql_insert); users.insert(pair(name, password)); m_lock.unlock(); if (!res) strcpy(m_url, "/log.html"); else strcpy(m_url, "/registerError.html"); } else strcpy(m_url, "/registerError.html"); } //如果是登录,直接判断 //若浏览器端输入的用户名和密码在表中可以查找到,返回1,否则返回0 else if (*(p + 1) == '2') { if (users.find(name) != users.end() && users[name] == password) strcpy(m_url, "/welcome.html"); else strcpy(m_url, "/logError.html"); } } if (*(p + 1) == '0') { char *m_url_real = (char *)malloc(sizeof(char) * 200); strcpy(m_url_real, "/register.html"); strncpy(m_real_file + len, m_url_real, strlen(m_url_real)); free(m_url_real); } else if (*(p + 1) == '1') { char *m_url_real = (char *)malloc(sizeof(char) * 200); strcpy(m_url_real, "/log.html"); strncpy(m_real_file + len, m_url_real, strlen(m_url_real)); free(m_url_real); } else if (*(p + 1) == '5') { char *m_url_real = (char *)malloc(sizeof(char) * 200); strcpy(m_url_real, "/picture.html"); strncpy(m_real_file + len, m_url_real, strlen(m_url_real)); free(m_url_real); } else if (*(p + 1) == '6') { char *m_url_real = (char *)malloc(sizeof(char) * 200); strcpy(m_url_real, "/video.html"); strncpy(m_real_file + len, m_url_real, strlen(m_url_real)); free(m_url_real); } else if (*(p + 1) == '7') { char *m_url_real = (char *)malloc(sizeof(char) * 200); strcpy(m_url_real, "/fans.html"); strncpy(m_real_file + len, m_url_real, strlen(m_url_real)); free(m_url_real); } else strncpy(m_real_file + len, m_url, FILENAME_LEN - len - 1); if (stat(m_real_file, &m_file_stat) < 0) return NO_RESOURCE; if (!(m_file_stat.st_mode & S_IROTH)) return FORBIDDEN_REQUEST; if (S_ISDIR(m_file_stat.st_mode)) return BAD_REQUEST; int fd = open(m_real_file, O_RDONLY); m_file_address = (char *)mmap(0, m_file_stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0); close(fd); return FILE_REQUEST; } void http_conn::unmap() { if (m_file_address) { munmap(m_file_address, m_file_stat.st_size); m_file_address = 0; } } bool http_conn::write() { int temp = 0; if (bytes_to_send == 0) { modfd(m_epollfd, m_sockfd, EPOLLIN, m_TRIGMode); init(); return true; } while (1) { temp = writev(m_sockfd, m_iv, m_iv_count); if (temp < 0) { if (errno == EAGAIN) { modfd(m_epollfd, m_sockfd, EPOLLOUT, m_TRIGMode); return true; } unmap(); return false; } bytes_have_send += temp; bytes_to_send -= temp; if (bytes_have_send >= m_iv[0].iov_len) { m_iv[0].iov_len = 0; m_iv[1].iov_base = m_file_address + (bytes_have_send - m_write_idx); m_iv[1].iov_len = bytes_to_send; } else { m_iv[0].iov_base = m_write_buf + bytes_have_send; m_iv[0].iov_len = m_iv[0].iov_len - bytes_have_send; } if (bytes_to_send <= 0) { unmap(); modfd(m_epollfd, m_sockfd, EPOLLIN, m_TRIGMode); if (m_linger) { init(); return true; } else { return false; } } } } bool http_conn::add_response(const char *format, ...) { if (m_write_idx >= WRITE_BUFFER_SIZE) return false; va_list arg_list; va_start(arg_list, format); int len = vsnprintf(m_write_buf + m_write_idx, WRITE_BUFFER_SIZE - 1 - m_write_idx, format, arg_list); if (len >= (WRITE_BUFFER_SIZE - 1 - m_write_idx)) { va_end(arg_list); return false; } m_write_idx += len; va_end(arg_list); LOG_INFO("request:%s", m_write_buf); return true; } bool http_conn::add_status_line(int status, const char *title) { return add_response("%s %d %s\r\n", "HTTP/1.1", status, title); } bool http_conn::add_headers(int content_len) { return add_content_length(content_len) && add_linger() && add_blank_line(); } bool http_conn::add_content_length(int content_len) { return add_response("Content-Length:%d\r\n", content_len); } bool http_conn::add_content_type() { return add_response("Content-Type:%s\r\n", "text/html"); } bool http_conn::add_linger() { return add_response("Connection:%s\r\n", (m_linger == true) ? "keep-alive" : "close"); } bool http_conn::add_blank_line() { return add_response("%s", "\r\n"); } bool http_conn::add_content(const char *content) { return add_response("%s", content); } bool http_conn::process_write(HTTP_CODE ret) { switch (ret) { case INTERNAL_ERROR: { add_status_line(500, error_500_title); add_headers(strlen(error_500_form)); if (!add_content(error_500_form)) return false; break; } case BAD_REQUEST: { add_status_line(404, error_404_title); add_headers(strlen(error_404_form)); if (!add_content(error_404_form)) return false; break; } case FORBIDDEN_REQUEST: { add_status_line(403, error_403_title); add_headers(strlen(error_403_form)); if (!add_content(error_403_form)) return false; break; } case FILE_REQUEST: { add_status_line(200, ok_200_title); if (m_file_stat.st_size != 0) { add_headers(m_file_stat.st_size); m_iv[0].iov_base = m_write_buf; m_iv[0].iov_len = m_write_idx; m_iv[1].iov_base = m_file_address; m_iv[1].iov_len = m_file_stat.st_size; m_iv_count = 2; bytes_to_send = m_write_idx + m_file_stat.st_size; return true; } else { const char *ok_string = ""; add_headers(strlen(ok_string)); if (!add_content(ok_string)) return false; } } default: return false; } m_iv[0].iov_base = m_write_buf; m_iv[0].iov_len = m_write_idx; m_iv_count = 1; bytes_to_send = m_write_idx; return true; } void http_conn::process() { HTTP_CODE read_ret = process_read(); if (read_ret == NO_REQUEST) { modfd(m_epollfd, m_sockfd, EPOLLIN, m_TRIGMode); return; } bool write_ret = process_write(read_ret); if (!write_ret) { close_conn(); } modfd(m_epollfd, m_sockfd, EPOLLOUT, m_TRIGMode); } ================================================ FILE: http/http_conn.h ================================================ #ifndef HTTPCONNECTION_H #define HTTPCONNECTION_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../lock/locker.h" #include "../CGImysql/sql_connection_pool.h" #include "../timer/lst_timer.h" #include "../log/log.h" class http_conn { public: static const int FILENAME_LEN = 200; static const int READ_BUFFER_SIZE = 2048; static const int WRITE_BUFFER_SIZE = 1024; enum METHOD { GET = 0, POST, HEAD, PUT, DELETE, TRACE, OPTIONS, CONNECT, PATH }; enum CHECK_STATE { CHECK_STATE_REQUESTLINE = 0, CHECK_STATE_HEADER, CHECK_STATE_CONTENT }; enum HTTP_CODE { NO_REQUEST, GET_REQUEST, BAD_REQUEST, NO_RESOURCE, FORBIDDEN_REQUEST, FILE_REQUEST, INTERNAL_ERROR, CLOSED_CONNECTION }; enum LINE_STATUS { LINE_OK = 0, LINE_BAD, LINE_OPEN }; public: http_conn() {} ~http_conn() {} public: void init(int sockfd, const sockaddr_in &addr, char *, int, int, string user, string passwd, string sqlname); void close_conn(bool real_close = true); void process(); bool read_once(); bool write(); sockaddr_in *get_address() { return &m_address; } void initmysql_result(connection_pool *connPool); int timer_flag; int improv; private: void init(); HTTP_CODE process_read(); bool process_write(HTTP_CODE ret); HTTP_CODE parse_request_line(char *text); HTTP_CODE parse_headers(char *text); HTTP_CODE parse_content(char *text); HTTP_CODE do_request(); char *get_line() { return m_read_buf + m_start_line; }; LINE_STATUS parse_line(); void unmap(); bool add_response(const char *format, ...); bool add_content(const char *content); bool add_status_line(int status, const char *title); bool add_headers(int content_length); bool add_content_type(); bool add_content_length(int content_length); bool add_linger(); bool add_blank_line(); public: static int m_epollfd; static int m_user_count; MYSQL *mysql; int m_state; //读为0, 写为1 private: int m_sockfd; sockaddr_in m_address; char m_read_buf[READ_BUFFER_SIZE]; long m_read_idx; long m_checked_idx; int m_start_line; char m_write_buf[WRITE_BUFFER_SIZE]; int m_write_idx; CHECK_STATE m_check_state; METHOD m_method; char m_real_file[FILENAME_LEN]; char *m_url; char *m_version; char *m_host; long m_content_length; bool m_linger; char *m_file_address; struct stat m_file_stat; struct iovec m_iv[2]; int m_iv_count; int cgi; //是否启用的POST char *m_string; //存储请求头数据 int bytes_to_send; int bytes_have_send; char *doc_root; map m_users; int m_TRIGMode; int m_close_log; char sql_user[100]; char sql_passwd[100]; char sql_name[100]; }; #endif ================================================ FILE: lock/README.md ================================================ 线程同步机制包装类 =============== 多线程同步,确保任一时刻只能有一个线程能进入关键代码段. > * 信号量 > * 互斥锁 > * 条件变量 ================================================ FILE: lock/locker.h ================================================ #ifndef LOCKER_H #define LOCKER_H #include #include #include class sem { public: sem() { if (sem_init(&m_sem, 0, 0) != 0) { throw std::exception(); } } sem(int num) { if (sem_init(&m_sem, 0, num) != 0) { throw std::exception(); } } ~sem() { sem_destroy(&m_sem); } bool wait() { return sem_wait(&m_sem) == 0; } bool post() { return sem_post(&m_sem) == 0; } private: sem_t m_sem; }; class locker { public: locker() { if (pthread_mutex_init(&m_mutex, NULL) != 0) { throw std::exception(); } } ~locker() { pthread_mutex_destroy(&m_mutex); } bool lock() { return pthread_mutex_lock(&m_mutex) == 0; } bool unlock() { return pthread_mutex_unlock(&m_mutex) == 0; } pthread_mutex_t *get() { return &m_mutex; } private: pthread_mutex_t m_mutex; }; class cond { public: cond() { if (pthread_cond_init(&m_cond, NULL) != 0) { //pthread_mutex_destroy(&m_mutex); throw std::exception(); } } ~cond() { pthread_cond_destroy(&m_cond); } bool wait(pthread_mutex_t *m_mutex) { int ret = 0; //pthread_mutex_lock(&m_mutex); ret = pthread_cond_wait(&m_cond, m_mutex); //pthread_mutex_unlock(&m_mutex); return ret == 0; } bool timewait(pthread_mutex_t *m_mutex, struct timespec t) { int ret = 0; //pthread_mutex_lock(&m_mutex); ret = pthread_cond_timedwait(&m_cond, m_mutex, &t); //pthread_mutex_unlock(&m_mutex); return ret == 0; } bool signal() { return pthread_cond_signal(&m_cond) == 0; } bool broadcast() { return pthread_cond_broadcast(&m_cond) == 0; } private: //static pthread_mutex_t m_mutex; pthread_cond_t m_cond; }; #endif ================================================ FILE: log/README.md ================================================ 同步/异步日志系统 =============== 同步/异步日志系统主要涉及了两个模块,一个是日志模块,一个是阻塞队列模块,其中加入阻塞队列模块主要是解决异步写入日志做准备. > * 自定义阻塞队列 > * 单例模式创建日志 > * 同步日志 > * 异步日志 > * 实现按天、超行分类 ================================================ FILE: log/block_queue.h ================================================ /************************************************************* *循环数组实现的阻塞队列,m_back = (m_back + 1) % m_max_size; *线程安全,每个操作前都要先加互斥锁,操作完后,再解锁 **************************************************************/ #ifndef BLOCK_QUEUE_H #define BLOCK_QUEUE_H #include #include #include #include #include "../lock/locker.h" using namespace std; template class block_queue { public: block_queue(int max_size = 1000) { if (max_size <= 0) { exit(-1); } m_max_size = max_size; m_array = new T[max_size]; m_size = 0; m_front = -1; m_back = -1; } void clear() { m_mutex.lock(); m_size = 0; m_front = -1; m_back = -1; m_mutex.unlock(); } ~block_queue() { m_mutex.lock(); if (m_array != NULL) delete [] m_array; m_mutex.unlock(); } //判断队列是否满了 bool full() { m_mutex.lock(); if (m_size >= m_max_size) { m_mutex.unlock(); return true; } m_mutex.unlock(); return false; } //判断队列是否为空 bool empty() { m_mutex.lock(); if (0 == m_size) { m_mutex.unlock(); return true; } m_mutex.unlock(); return false; } //返回队首元素 bool front(T &value) { m_mutex.lock(); if (0 == m_size) { m_mutex.unlock(); return false; } value = m_array[m_front]; m_mutex.unlock(); return true; } //返回队尾元素 bool back(T &value) { m_mutex.lock(); if (0 == m_size) { m_mutex.unlock(); return false; } value = m_array[m_back]; m_mutex.unlock(); return true; } int size() { int tmp = 0; m_mutex.lock(); tmp = m_size; m_mutex.unlock(); return tmp; } int max_size() { int tmp = 0; m_mutex.lock(); tmp = m_max_size; m_mutex.unlock(); return tmp; } //往队列添加元素,需要将所有使用队列的线程先唤醒 //当有元素push进队列,相当于生产者生产了一个元素 //若当前没有线程等待条件变量,则唤醒无意义 bool push(const T &item) { m_mutex.lock(); if (m_size >= m_max_size) { m_cond.broadcast(); m_mutex.unlock(); return false; } m_back = (m_back + 1) % m_max_size; m_array[m_back] = item; m_size++; m_cond.broadcast(); m_mutex.unlock(); return true; } //pop时,如果当前队列没有元素,将会等待条件变量 bool pop(T &item) { m_mutex.lock(); while (m_size <= 0) { if (!m_cond.wait(m_mutex.get())) { m_mutex.unlock(); return false; } } m_front = (m_front + 1) % m_max_size; item = m_array[m_front]; m_size--; m_mutex.unlock(); return true; } //增加了超时处理 bool pop(T &item, int ms_timeout) { struct timespec t = {0, 0}; struct timeval now = {0, 0}; gettimeofday(&now, NULL); m_mutex.lock(); if (m_size <= 0) { t.tv_sec = now.tv_sec + ms_timeout / 1000; t.tv_nsec = (ms_timeout % 1000) * 1000; if (!m_cond.timewait(m_mutex.get(), t)) { m_mutex.unlock(); return false; } } if (m_size <= 0) { m_mutex.unlock(); return false; } m_front = (m_front + 1) % m_max_size; item = m_array[m_front]; m_size--; m_mutex.unlock(); return true; } private: locker m_mutex; cond m_cond; T *m_array; int m_size; int m_max_size; int m_front; int m_back; }; #endif ================================================ FILE: log/log.cpp ================================================ #include #include #include #include #include "log.h" #include using namespace std; Log::Log() { m_count = 0; m_is_async = false; } Log::~Log() { if (m_fp != NULL) { fclose(m_fp); } } //异步需要设置阻塞队列的长度,同步不需要设置 bool Log::init(const char *file_name, int close_log, int log_buf_size, int split_lines, int max_queue_size) { //如果设置了max_queue_size,则设置为异步 if (max_queue_size >= 1) { m_is_async = true; m_log_queue = new block_queue(max_queue_size); pthread_t tid; //flush_log_thread为回调函数,这里表示创建线程异步写日志 pthread_create(&tid, NULL, flush_log_thread, NULL); } m_close_log = close_log; m_log_buf_size = log_buf_size; m_buf = new char[m_log_buf_size]; memset(m_buf, '\0', m_log_buf_size); m_split_lines = split_lines; time_t t = time(NULL); struct tm *sys_tm = localtime(&t); struct tm my_tm = *sys_tm; const char *p = strrchr(file_name, '/'); char log_full_name[256] = {0}; if (p == NULL) { snprintf(log_full_name, 255, "%d_%02d_%02d_%s", my_tm.tm_year + 1900, my_tm.tm_mon + 1, my_tm.tm_mday, file_name); } else { strcpy(log_name, p + 1); strncpy(dir_name, file_name, p - file_name + 1); snprintf(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); } m_today = my_tm.tm_mday; m_fp = fopen(log_full_name, "a"); if (m_fp == NULL) { return false; } return true; } void Log::write_log(int level, const char *format, ...) { struct timeval now = {0, 0}; gettimeofday(&now, NULL); time_t t = now.tv_sec; struct tm *sys_tm = localtime(&t); struct tm my_tm = *sys_tm; char s[16] = {0}; switch (level) { case 0: strcpy(s, "[debug]:"); break; case 1: strcpy(s, "[info]:"); break; case 2: strcpy(s, "[warn]:"); break; case 3: strcpy(s, "[erro]:"); break; default: strcpy(s, "[info]:"); break; } //写入一个log,对m_count++, m_split_lines最大行数 m_mutex.lock(); m_count++; if (m_today != my_tm.tm_mday || m_count % m_split_lines == 0) //everyday log { char new_log[256] = {0}; fflush(m_fp); fclose(m_fp); char tail[16] = {0}; snprintf(tail, 16, "%d_%02d_%02d_", my_tm.tm_year + 1900, my_tm.tm_mon + 1, my_tm.tm_mday); if (m_today != my_tm.tm_mday) { snprintf(new_log, 255, "%s%s%s", dir_name, tail, log_name); m_today = my_tm.tm_mday; m_count = 0; } else { snprintf(new_log, 255, "%s%s%s.%lld", dir_name, tail, log_name, m_count / m_split_lines); } m_fp = fopen(new_log, "a"); } m_mutex.unlock(); va_list valst; va_start(valst, format); string log_str; m_mutex.lock(); //写入的具体时间内容格式 int n = snprintf(m_buf, 48, "%d-%02d-%02d %02d:%02d:%02d.%06ld %s ", my_tm.tm_year + 1900, my_tm.tm_mon + 1, my_tm.tm_mday, my_tm.tm_hour, my_tm.tm_min, my_tm.tm_sec, now.tv_usec, s); int m = vsnprintf(m_buf + n, m_log_buf_size - n - 1, format, valst); m_buf[n + m] = '\n'; m_buf[n + m + 1] = '\0'; log_str = m_buf; m_mutex.unlock(); if (m_is_async && !m_log_queue->full()) { m_log_queue->push(log_str); } else { m_mutex.lock(); fputs(log_str.c_str(), m_fp); m_mutex.unlock(); } va_end(valst); } void Log::flush(void) { m_mutex.lock(); //强制刷新写入流缓冲区 fflush(m_fp); m_mutex.unlock(); } ================================================ FILE: log/log.h ================================================ #ifndef LOG_H #define LOG_H #include #include #include #include #include #include "block_queue.h" using namespace std; class Log { public: //C++11以后,使用局部变量懒汉不用加锁 static Log *get_instance() { static Log instance; return &instance; } static void *flush_log_thread(void *args) { Log::get_instance()->async_write_log(); } //可选择的参数有日志文件、日志缓冲区大小、最大行数以及最长日志条队列 bool init(const char *file_name, int close_log, int log_buf_size = 8192, int split_lines = 5000000, int max_queue_size = 0); void write_log(int level, const char *format, ...); void flush(void); private: Log(); virtual ~Log(); void *async_write_log() { string single_log; //从阻塞队列中取出一个日志string,写入文件 while (m_log_queue->pop(single_log)) { m_mutex.lock(); fputs(single_log.c_str(), m_fp); m_mutex.unlock(); } } private: char dir_name[128]; //路径名 char log_name[128]; //log文件名 int m_split_lines; //日志最大行数 int m_log_buf_size; //日志缓冲区大小 long long m_count; //日志行数记录 int m_today; //因为按天分类,记录当前时间是那一天 FILE *m_fp; //打开log的文件指针 char *m_buf; block_queue *m_log_queue; //阻塞队列 bool m_is_async; //是否同步标志位 locker m_mutex; int m_close_log; //关闭日志 }; #define LOG_DEBUG(format, ...) if(0 == m_close_log) {Log::get_instance()->write_log(0, format, ##__VA_ARGS__); Log::get_instance()->flush();} #define LOG_INFO(format, ...) if(0 == m_close_log) {Log::get_instance()->write_log(1, format, ##__VA_ARGS__); Log::get_instance()->flush();} #define LOG_WARN(format, ...) if(0 == m_close_log) {Log::get_instance()->write_log(2, format, ##__VA_ARGS__); Log::get_instance()->flush();} #define LOG_ERROR(format, ...) if(0 == m_close_log) {Log::get_instance()->write_log(3, format, ##__VA_ARGS__); Log::get_instance()->flush();} #endif ================================================ FILE: main.cpp ================================================ #include "config.h" int main(int argc, char *argv[]) { //需要修改的数据库信息,登录名,密码,库名 string user = "root"; string passwd = "root"; string databasename = "qgydb"; //命令行解析 Config config; config.parse_arg(argc, argv); WebServer server; //初始化 server.init(config.PORT, user, passwd, databasename, config.LOGWrite, config.OPT_LINGER, config.TRIGMode, config.sql_num, config.thread_num, config.close_log, config.actor_model); //日志 server.log_write(); //数据库 server.sql_pool(); //线程池 server.thread_pool(); //触发模式 server.trig_mode(); //监听 server.eventListen(); //运行 server.eventLoop(); return 0; } ================================================ FILE: makefile ================================================ CXX ?= g++ DEBUG ?= 1 ifeq ($(DEBUG), 1) CXXFLAGS += -g else CXXFLAGS += -O2 endif server: main.cpp ./timer/lst_timer.cpp ./http/http_conn.cpp ./log/log.cpp ./CGImysql/sql_connection_pool.cpp webserver.cpp config.cpp $(CXX) -o server $^ $(CXXFLAGS) -lpthread -lmysqlclient clean: rm -r server ================================================ FILE: root/README.md ================================================ 界面跳转 =============== 对html中action行为设置标志位,将method设置为POST > * 0 注册 > * 1 登录 > * 2 登录检测 > * 3 注册检测 > * 5 请求图片 > * 6 请求视频 > * 7 关注我 ================================================ FILE: root/fans.html ================================================ awsl

嘿嘿,你来啦,更多资料,请关注 “两猿社” 喔.



================================================ FILE: root/judge.html ================================================ WebServer

欢迎访问



================================================ FILE: root/log.html ================================================ Sign in

登录

================================================ FILE: root/logError.html ================================================ Sign in

登录

================================================ FILE: root/picture.html ================================================ awsl

你居然想看图,不想关注我



================================================ FILE: root/register.html ================================================ Sign up

注册

================================================ FILE: root/registerError.html ================================================ Sign up

注册

================================================ FILE: root/video.html ================================================ awsl

你居然想看视频,不想关注我



================================================ FILE: root/welcome.html ================================================ WebServer

是时候做出选择了




================================================ FILE: test_pressure/README.md ================================================ 服务器压力测试 =============== Webbench是有名的网站压力测试工具,它是由[Lionbridge](http://www.lionbridge.com)公司开发。 > * 测试处在相同硬件上,不同服务的性能以及不同硬件上同一个服务的运行状况。 > * 展示服务器的两项内容:每秒钟响应请求数和每秒钟传输数据量。 测试规则 ------------ * 测试示例 ```C++ webbench -c 500 -t 30 http://127.0.0.1/phpionfo.php ``` * 参数 > * `-c` 表示客户端数 > * `-t` 表示时间 测试结果 --------- Webbench对服务器进行压力测试,经压力测试可以实现上万的并发连接. > * 并发连接总数:10500 > * 访问服务器时间:5s > * 每秒钟响应请求数:552852 pages/min > * 每秒钟传输数据量:1031990 bytes/sec > * 所有访问均成功
================================================ FILE: test_pressure/webbench-1.5/COPYRIGHT ================================================ debian/copyright ================================================ FILE: test_pressure/webbench-1.5/ChangeLog ================================================ debian/changelog ================================================ FILE: test_pressure/webbench-1.5/Makefile ================================================ CFLAGS?= -Wall -Wno-unused-parameter -ggdb -W -O CC?= gcc LIBS?= LDFLAGS?= PREFIX?= /usr/local VERSION=1.5 TMPDIR=/tmp/webbench-$(VERSION) all: webbench tags tags: *.c -ctags *.c install: webbench install -s webbench $(DESTDIR)$(PREFIX)/bin install -m 644 webbench.1 $(DESTDIR)$(PREFIX)/man/man1 install -d $(DESTDIR)$(PREFIX)/share/doc/webbench install -m 644 debian/copyright $(DESTDIR)$(PREFIX)/share/doc/webbench install -m 644 debian/changelog $(DESTDIR)$(PREFIX)/share/doc/webbench webbench: webbench.o Makefile $(CC) $(CFLAGS) $(LDFLAGS) -o webbench webbench.o $(LIBS) clean: -rm -f *.o webbench *~ core *.core tags tar: clean -debian/rules clean rm -rf $(TMPDIR) install -d $(TMPDIR) cp -p Makefile webbench.c socket.c webbench.1 $(TMPDIR) install -d $(TMPDIR)/debian -cp -p debian/* $(TMPDIR)/debian ln -sf debian/copyright $(TMPDIR)/COPYRIGHT ln -sf debian/changelog $(TMPDIR)/ChangeLog -cd $(TMPDIR) && cd .. && tar cozf webbench-$(VERSION).tar.gz webbench-$(VERSION) webbench.o: webbench.c socket.c Makefile .PHONY: clean install all tar ================================================ FILE: test_pressure/webbench-1.5/debian/changelog ================================================ webbench (1.5) unstable; urgency=low * allow building with both Gnu and BSD make -- Radim Kolar Fri, Jun 25 12:00:20 CEST 2004 webbench (1.4) unstable; urgency=low * check if url is not too long * report correct program version number * use yield() when waiting for test start * corrected error codes * check availability of test server first * do not abort test if first request failed * report when some childrens are dead. * use alarm, not time() for lower syscal use by bench * use mode 644 for installed doc * makefile cleaned for better freebsd ports integration -- Radim Kolar Thu, 15 Jan 2004 11:15:52 +0100 webbench (1.3) unstable; urgency=low * Build fixes for freeBSD * Default benchmark time 60 -> 30 * generate tar with subdirectory * added to freeBSD ports collection -- Radim Kolar Mon, 12 Jan 2004 17:00:24 +0100 webbench (1.2) unstable; urgency=low * Only debian-related bugfixes * Updated Debian/rules * Adapted to fit new directory system * moved from debstd to dh_* -- Radim Kolar Fri, 18 Jan 2002 12:33:04 +0100 webbench (1.1) unstable; urgency=medium * Program debianized * added support for multiple methods (GET, HEAD, OPTIONS, TRACE) * added support for multiple HTTP versions (0.9 -- 1.1) * added long options * added multiple clients * wait for start of second before test * test time can be specified * better error checking when reading reply from server * FIX: tests was one second longer than expected -- Radim Kolar Thu, 16 Sep 1999 18:48:00 +0200 Local variables: mode: debian-changelog End: ================================================ FILE: test_pressure/webbench-1.5/debian/control ================================================ Source: webbench Section: web Priority: extra Maintainer: Radim Kolar Build-Depends: debhelper (>> 3.0.0) Standards-Version: 3.5.2 Package: webbench Architecture: any Depends: ${shlibs:Depends} Description: Simple forking Web benchmark webbench is very simple program for benchmarking WWW or Proxy servers. Uses fork() for simulating multiple clients load. Can use HTTP 0.9 - 1.1 requests, but Keep-Alive connections are not supported. ================================================ FILE: test_pressure/webbench-1.5/debian/copyright ================================================ Webbench was written by Radim Kolar 1997-2004 (hsn@netmag.cz). UNIX sockets code (socket.c) taken from popclient 1.5 4/1/94 public domain code, created by Virginia Tech Computing Center. Copyright: GPL (see /usr/share/common-licenses/GPL) ================================================ FILE: test_pressure/webbench-1.5/debian/dirs ================================================ usr/bin ================================================ FILE: test_pressure/webbench-1.5/debian/rules ================================================ #!/usr/bin/make -f # Sample debian/rules that uses debhelper. # GNU copyright 1997 to 1999 by Joey Hess. # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 # This is the debhelper compatability version to use. export DH_COMPAT=3 configure: configure-stamp configure-stamp: dh_testdir touch configure-stamp build: configure-stamp build-stamp build-stamp: dh_testdir $(MAKE) touch build-stamp clean: dh_testdir rm -f build-stamp configure-stamp # Add here commands to clean up after the build process. -$(MAKE) clean dh_clean install: build dh_testdir dh_testroot dh_clean -k dh_installdirs # Add here commands to install the package into debian/webbench. $(MAKE) install DESTDIR=$(CURDIR)/debian/webbench # Build architecture-independent files here. binary-indep: build install # We have nothing to do by default. # Build architecture-dependent files here. binary-arch: build install dh_testdir dh_testroot dh_installdocs dh_installman webbench.1 dh_installchangelogs dh_link dh_strip dh_compress dh_fixperms # dh_makeshlibs dh_installdeb dh_shlibdeps dh_gencontrol dh_md5sums dh_builddeb binary: binary-indep binary-arch .PHONY: build clean binary-indep binary-arch binary install configure ================================================ FILE: test_pressure/webbench-1.5/socket.c ================================================ /* $Id: socket.c 1.1 1995/01/01 07:11:14 cthuang Exp $ * * This module has been modified by Radim Kolar for OS/2 emx */ /*********************************************************************** module: socket.c program: popclient SCCS ID: @(#)socket.c 1.5 4/1/94 programmer: Virginia Tech Computing Center compiler: DEC RISC C compiler (Ultrix 4.1) environment: DEC Ultrix 4.3 description: UNIX sockets code. ***********************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include int Socket(const char *host, int clientPort) { int sock; unsigned long inaddr; struct sockaddr_in ad; struct hostent *hp; memset(&ad, 0, sizeof(ad)); ad.sin_family = AF_INET; inaddr = inet_addr(host); if (inaddr != INADDR_NONE) memcpy(&ad.sin_addr, &inaddr, sizeof(inaddr)); else { hp = gethostbyname(host); if (hp == NULL) return -1; memcpy(&ad.sin_addr, hp->h_addr, hp->h_length); } ad.sin_port = htons(clientPort); sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) return sock; if (connect(sock, (struct sockaddr *)&ad, sizeof(ad)) < 0) return -1; return sock; } ================================================ FILE: test_pressure/webbench-1.5/tags ================================================ !_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/ !_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/ !_TAG_PROGRAM_AUTHOR Darren Hiebert /dhiebert@users.sourceforge.net/ !_TAG_PROGRAM_NAME Exuberant Ctags // !_TAG_PROGRAM_URL http://ctags.sourceforge.net /official site/ !_TAG_PROGRAM_VERSION 5.9~svn20110310 // METHOD_GET webbench.c 35;" d file: METHOD_HEAD webbench.c 36;" d file: METHOD_OPTIONS webbench.c 37;" d file: METHOD_TRACE webbench.c 38;" d file: PROGRAM_VERSION webbench.c 39;" d file: REQUEST_SIZE webbench.c 50;" d file: Socket socket.c /^int Socket(const char *host, int clientPort)$/;" f alarm_handler webbench.c /^static void alarm_handler(int signal)$/;" f file: bench webbench.c /^static int bench(void)$/;" f file: benchcore webbench.c /^void benchcore(const char *host,const int port,const char *req)$/;" f benchtime webbench.c /^int benchtime=30;$/;" v build_request webbench.c /^void build_request(const char *url)$/;" f bytes webbench.c /^int bytes=0;$/;" v clients webbench.c /^int clients=1;$/;" v failed webbench.c /^int failed=0;$/;" v force webbench.c /^int force=0;$/;" v force_reload webbench.c /^int force_reload=0;$/;" v host webbench.c /^char host[MAXHOSTNAMELEN];$/;" v http10 webbench.c /^int http10=1; \/* 0 - http\/0.9, 1 - http\/1.0, 2 - http\/1.1 *\/$/;" v long_options webbench.c /^static const struct option long_options[]=$/;" v typeref:struct:option file: main webbench.c /^int main(int argc, char *argv[])$/;" f method webbench.c /^int method=METHOD_GET;$/;" v mypipe webbench.c /^int mypipe[2];$/;" v proxyhost webbench.c /^char *proxyhost=NULL;$/;" v proxyport webbench.c /^int proxyport=80;$/;" v request webbench.c /^char request[REQUEST_SIZE];$/;" v speed webbench.c /^int speed=0;$/;" v timerexpired webbench.c /^volatile int timerexpired=0;$/;" v usage webbench.c /^static void usage(void)$/;" f file: ================================================ FILE: test_pressure/webbench-1.5/webbench.1 ================================================ .TH WEBBENCH 1 "14 Jan 2004" .\" NAME should be all caps, SECTION should be 1-8, maybe w/ subsection .\" other parms are allowed: see man(7), man(1) .SH NAME webbench \- simple forking web benchmark .SH SYNOPSIS .B webbench .I "[options] URL" .br .SH "AUTHOR" This program and manual page was written by Radim Kolar, for the .B Supreme Personality of Godhead (but may be used by others). .SH "DESCRIPTION" .B webbench is simple program for benchmarking HTTP servers or any other servers, which can be accessed via HTTP proxy. Unlike others benchmarks, .B webbench uses multiple processes for simulating traffic generated by multiple users. This allows better operating on SMP systems and on systems with slow or buggy implementation of select(). .SH OPTIONS The programs follow the usual GNU command line syntax, with long options starting with two dashes (`-'). A summary of options are included below. .TP .B \-?, \-h, \-\-help Show summary of options. .TP .B \-v, \-\-version Show version of program. .TP .B \-f, \-\-force Do not wait for any response from server. Close connection after request is send. This option produce quite a good denial of service attack. .TP .B \-9, \-\-http09 Use HTTP/0.9 protocol, if possible. .TP .B \-1, \-\-http10 Use HTTP/1.0 protocol, if possible. .TP .B \-2, \-\-http11 Use HTTP/1.1 protocol (without .I Keep-Alive ), if possible. .TP .B \-r, \-\-reload Forces proxy to reload document. If proxy is not set, option has no effect. .TP .B \-t, \-\-time Run benchmark for .I seconds. Default value is 30. .TP .B \-p, \-\-proxy Send request via proxy server. Needed for supporting others protocols than HTTP. .TP .B \-\-get Use GET request method. .TP .B \-\-head Use HEAD request method. .TP .B \-\-options Use OPTIONS request method. .TP .B \-\-trace Use TRACE request method. .TP .B \-c, \-\-clients Use .I multiple clients for benchmark. Default value is 1. .SH "EXIT STATUS" .TP 0 - sucess .TP 1 - benchmark failed, can not connect to server .TP 2 - bad command line argument(s) .TP 3 - internal error, i.e. fork failed .SH "TODO" Include support for using .I Keep-Alive HTTP/1.1 connections. .SH "COPYING" Webbench is distributed under GPL. Copyright 1997-2004 Radim Kolar (hsn@netmag.cz). UNIX sockets code taken from popclient 1.5 4/1/94 public domain code, created by Virginia Tech Computing Center. .BR This man page is public domain. ================================================ FILE: test_pressure/webbench-1.5/webbench.c ================================================ /* * (C) Radim Kolar 1997-2004 * This is free software, see GNU Public License version 2 for * details. * * Simple forking WWW Server benchmark: * * Usage: * webbench --help * * Return codes: * 0 - sucess * 1 - benchmark failed (server is not on-line) * 2 - bad param * 3 - internal error, fork failed * */ #include "socket.c" #include #include #include #include #include #include #include /* values */ volatile int timerexpired=0; int speed=0; int failed=0; int bytes=0; /* globals */ int http10=1; /* 0 - http/0.9, 1 - http/1.0, 2 - http/1.1 */ /* Allow: GET, HEAD, OPTIONS, TRACE */ #define METHOD_GET 0 #define METHOD_HEAD 1 #define METHOD_OPTIONS 2 #define METHOD_TRACE 3 #define PROGRAM_VERSION "1.5" int method=METHOD_GET; int clients=1; int force=0; int force_reload=0; int proxyport=80; char *proxyhost=NULL; int benchtime=30; /* internal */ int mypipe[2]; char host[MAXHOSTNAMELEN]; #define REQUEST_SIZE 2048 char request[REQUEST_SIZE]; static const struct option long_options[]= { {"force",no_argument,&force,1}, {"reload",no_argument,&force_reload,1}, {"time",required_argument,NULL,'t'}, {"help",no_argument,NULL,'?'}, {"http09",no_argument,NULL,'9'}, {"http10",no_argument,NULL,'1'}, {"http11",no_argument,NULL,'2'}, {"get",no_argument,&method,METHOD_GET}, {"head",no_argument,&method,METHOD_HEAD}, {"options",no_argument,&method,METHOD_OPTIONS}, {"trace",no_argument,&method,METHOD_TRACE}, {"version",no_argument,NULL,'V'}, {"proxy",required_argument,NULL,'p'}, {"clients",required_argument,NULL,'c'}, {NULL,0,NULL,0} }; /* prototypes */ static void benchcore(const char* host,const int port, const char *request); static int bench(void); static void build_request(const char *url); static void alarm_handler(int signal) { timerexpired=1; } static void usage(void) { fprintf(stderr, "webbench [option]... URL\n" " -f|--force Don't wait for reply from server.\n" " -r|--reload Send reload request - Pragma: no-cache.\n" " -t|--time Run benchmark for seconds. Default 30.\n" " -p|--proxy Use proxy server for request.\n" " -c|--clients Run HTTP clients at once. Default one.\n" " -9|--http09 Use HTTP/0.9 style requests.\n" " -1|--http10 Use HTTP/1.0 protocol.\n" " -2|--http11 Use HTTP/1.1 protocol.\n" " --get Use GET request method.\n" " --head Use HEAD request method.\n" " --options Use OPTIONS request method.\n" " --trace Use TRACE request method.\n" " -?|-h|--help This information.\n" " -V|--version Display program version.\n" ); }; int main(int argc, char *argv[]) { int opt=0; int options_index=0; char *tmp=NULL; if(argc==1) { usage(); return 2; } while((opt=getopt_long(argc,argv,"912Vfrt:p:c:?h",long_options,&options_index))!=EOF ) { switch(opt) { case 0 : break; case 'f': force=1;break; case 'r': force_reload=1;break; case '9': http10=0;break; case '1': http10=1;break; case '2': http10=2;break; case 'V': printf(PROGRAM_VERSION"\n");exit(0); case 't': benchtime=atoi(optarg);break; case 'p': /* proxy server parsing server:port */ tmp=strrchr(optarg,':'); proxyhost=optarg; if(tmp==NULL) { break; } if(tmp==optarg) { fprintf(stderr,"Error in option --proxy %s: Missing hostname.\n",optarg); return 2; } if(tmp==optarg+strlen(optarg)-1) { fprintf(stderr,"Error in option --proxy %s Port number is missing.\n",optarg); return 2; } *tmp='\0'; proxyport=atoi(tmp+1);break; case ':': case 'h': case '?': usage();return 2;break; case 'c': clients=atoi(optarg);break; } } if(optind==argc) { fprintf(stderr,"webbench: Missing URL!\n"); usage(); return 2; } if(clients==0) clients=1; if(benchtime==0) benchtime=60; /* Copyright */ fprintf(stderr,"Webbench - Simple Web Benchmark "PROGRAM_VERSION"\n" "Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.\n" ); build_request(argv[optind]); /* print bench info */ printf("\nBenchmarking: "); switch(method) { case METHOD_GET: default: printf("GET");break; case METHOD_OPTIONS: printf("OPTIONS");break; case METHOD_HEAD: printf("HEAD");break; case METHOD_TRACE: printf("TRACE");break; } printf(" %s",argv[optind]); switch(http10) { case 0: printf(" (using HTTP/0.9)");break; case 2: printf(" (using HTTP/1.1)");break; } printf("\n"); if(clients==1) printf("1 client"); else printf("%d clients",clients); printf(", running %d sec", benchtime); if(force) printf(", early socket close"); if(proxyhost!=NULL) printf(", via proxy server %s:%d",proxyhost,proxyport); if(force_reload) printf(", forcing reload"); printf(".\n"); return bench(); } void build_request(const char *url) { char tmp[10]; int i; bzero(host,MAXHOSTNAMELEN); bzero(request,REQUEST_SIZE); if(force_reload && proxyhost!=NULL && http10<1) http10=1; if(method==METHOD_HEAD && http10<1) http10=1; if(method==METHOD_OPTIONS && http10<2) http10=2; if(method==METHOD_TRACE && http10<2) http10=2; switch(method) { default: case METHOD_GET: strcpy(request,"GET");break; case METHOD_HEAD: strcpy(request,"HEAD");break; case METHOD_OPTIONS: strcpy(request,"OPTIONS");break; case METHOD_TRACE: strcpy(request,"TRACE");break; } strcat(request," "); if(NULL==strstr(url,"://")) { fprintf(stderr, "\n%s: is not a valid URL.\n",url); exit(2); } if(strlen(url)>1500) { fprintf(stderr,"URL is too long.\n"); exit(2); } if(proxyhost==NULL) if (0!=strncasecmp("http://",url,7)) { fprintf(stderr,"\nOnly HTTP protocol is directly supported, set --proxy for others.\n"); exit(2); } /* protocol/host delimiter */ i=strstr(url,"://")-url+3; /* printf("%d\n",i); */ if(strchr(url+i,'/')==NULL) { fprintf(stderr,"\nInvalid URL syntax - hostname don't ends with '/'.\n"); exit(2); } if(proxyhost==NULL) { /* get port from hostname */ if(index(url+i,':')!=NULL && index(url+i,':')0) strcat(request,"User-Agent: WebBench "PROGRAM_VERSION"\r\n"); if(proxyhost==NULL && http10>0) { strcat(request,"Host: "); strcat(request,host); strcat(request,"\r\n"); } if(force_reload && proxyhost!=NULL) { strcat(request,"Pragma: no-cache\r\n"); } if(http10>1) strcat(request,"Connection: close\r\n"); /* add empty line at end */ if(http10>0) strcat(request,"\r\n"); // printf("Req=%s\n",request); } /* vraci system rc error kod */ static int bench(void) { int i,j,k; pid_t pid=0; FILE *f; /* check avaibility of target server */ i=Socket(proxyhost==NULL?host:proxyhost,proxyport); if(i<0) { fprintf(stderr,"\nConnect to server failed. Aborting benchmark.\n"); return 1; } close(i); /* create pipe */ if(pipe(mypipe)) { perror("pipe failed."); return 3; } /* not needed, since we have alarm() in childrens */ /* wait 4 next system clock tick */ /* cas=time(NULL); while(time(NULL)==cas) sched_yield(); */ /* fork childs */ for(i=0;i0) { /* fprintf(stderr,"Correcting failed by signal\n"); */ failed--; } return; } s=Socket(host,port); if(s<0) { failed++;continue;} if(rlen!=write(s,req,rlen)) {failed++;close(s);continue;} if(http10==0) if(shutdown(s,1)) { failed++;close(s);continue;} if(force==0) { /* read all available data from socket */ while(1) { if(timerexpired) break; i=read(s,buf,1500); /* fprintf(stderr,"%d\n",i); */ if(i<0) { failed++; close(s); goto nexttry; } else if(i==0) break; else bytes+=i; } } if(close(s)) {failed++;continue;} speed++; } } ================================================ FILE: threadpool/README.md ================================================ 半同步/半反应堆线程池 =============== 使用一个工作队列完全解除了主线程和工作线程的耦合关系:主线程往工作队列中插入任务,工作线程通过竞争来取得任务并执行它。 > * 同步I/O模拟proactor模式 > * 半同步/半反应堆 > * 线程池 ================================================ FILE: threadpool/threadpool.h ================================================ #ifndef THREADPOOL_H #define THREADPOOL_H #include #include #include #include #include "../lock/locker.h" #include "../CGImysql/sql_connection_pool.h" template class threadpool { public: /*thread_number是线程池中线程的数量,max_requests是请求队列中最多允许的、等待处理的请求的数量*/ threadpool(int actor_model, connection_pool *connPool, int thread_number = 8, int max_request = 10000); ~threadpool(); bool append(T *request, int state); bool append_p(T *request); private: /*工作线程运行的函数,它不断从工作队列中取出任务并执行之*/ static void *worker(void *arg); void run(); private: int m_thread_number; //线程池中的线程数 int m_max_requests; //请求队列中允许的最大请求数 pthread_t *m_threads; //描述线程池的数组,其大小为m_thread_number std::list m_workqueue; //请求队列 locker m_queuelocker; //保护请求队列的互斥锁 sem m_queuestat; //是否有任务需要处理 connection_pool *m_connPool; //数据库 int m_actor_model; //模型切换 }; template threadpool::threadpool( int actor_model, connection_pool *connPool, int thread_number, int max_requests) : m_actor_model(actor_model),m_thread_number(thread_number), m_max_requests(max_requests), m_threads(NULL),m_connPool(connPool) { if (thread_number <= 0 || max_requests <= 0) throw std::exception(); m_threads = new pthread_t[m_thread_number]; if (!m_threads) throw std::exception(); for (int i = 0; i < thread_number; ++i) { if (pthread_create(m_threads + i, NULL, worker, this) != 0) { delete[] m_threads; throw std::exception(); } if (pthread_detach(m_threads[i])) { delete[] m_threads; throw std::exception(); } } } template threadpool::~threadpool() { delete[] m_threads; } template bool threadpool::append(T *request, int state) { m_queuelocker.lock(); if (m_workqueue.size() >= m_max_requests) { m_queuelocker.unlock(); return false; } request->m_state = state; m_workqueue.push_back(request); m_queuelocker.unlock(); m_queuestat.post(); return true; } template bool threadpool::append_p(T *request) { m_queuelocker.lock(); if (m_workqueue.size() >= m_max_requests) { m_queuelocker.unlock(); return false; } m_workqueue.push_back(request); m_queuelocker.unlock(); m_queuestat.post(); return true; } template void *threadpool::worker(void *arg) { threadpool *pool = (threadpool *)arg; pool->run(); return pool; } template void threadpool::run() { while (true) { m_queuestat.wait(); m_queuelocker.lock(); if (m_workqueue.empty()) { m_queuelocker.unlock(); continue; } T *request = m_workqueue.front(); m_workqueue.pop_front(); m_queuelocker.unlock(); if (!request) continue; if (1 == m_actor_model) { if (0 == request->m_state) { if (request->read_once()) { request->improv = 1; connectionRAII mysqlcon(&request->mysql, m_connPool); request->process(); } else { request->improv = 1; request->timer_flag = 1; } } else { if (request->write()) { request->improv = 1; } else { request->improv = 1; request->timer_flag = 1; } } } else { connectionRAII mysqlcon(&request->mysql, m_connPool); request->process(); } } } #endif ================================================ FILE: timer/README.md ================================================ 定时器处理非活动连接 =============== 由于非活跃连接占用了连接资源,严重影响服务器的性能,通过实现一个服务器定时器,处理这种非活跃连接,释放连接资源。利用alarm函数周期性地触发SIGALRM信号,该信号的信号处理函数利用管道通知主循环执行定时器链表上的定时任务. > * 统一事件源 > * 基于升序链表的定时器 > * 处理非活动连接 ================================================ FILE: timer/lst_timer.cpp ================================================ #include "lst_timer.h" #include "../http/http_conn.h" sort_timer_lst::sort_timer_lst() { head = NULL; tail = NULL; } sort_timer_lst::~sort_timer_lst() { util_timer *tmp = head; while (tmp) { head = tmp->next; delete tmp; tmp = head; } } void sort_timer_lst::add_timer(util_timer *timer) { if (!timer) { return; } if (!head) { head = tail = timer; return; } if (timer->expire < head->expire) { timer->next = head; head->prev = timer; head = timer; return; } add_timer(timer, head); } void sort_timer_lst::adjust_timer(util_timer *timer) { if (!timer) { return; } util_timer *tmp = timer->next; if (!tmp || (timer->expire < tmp->expire)) { return; } if (timer == head) { head = head->next; head->prev = NULL; timer->next = NULL; add_timer(timer, head); } else { timer->prev->next = timer->next; timer->next->prev = timer->prev; add_timer(timer, timer->next); } } void sort_timer_lst::del_timer(util_timer *timer) { if (!timer) { return; } if ((timer == head) && (timer == tail)) { delete timer; head = NULL; tail = NULL; return; } if (timer == head) { head = head->next; head->prev = NULL; delete timer; return; } if (timer == tail) { tail = tail->prev; tail->next = NULL; delete timer; return; } timer->prev->next = timer->next; timer->next->prev = timer->prev; delete timer; } void sort_timer_lst::tick() { if (!head) { return; } time_t cur = time(NULL); util_timer *tmp = head; while (tmp) { if (cur < tmp->expire) { break; } tmp->cb_func(tmp->user_data); head = tmp->next; if (head) { head->prev = NULL; } delete tmp; tmp = head; } } void sort_timer_lst::add_timer(util_timer *timer, util_timer *lst_head) { util_timer *prev = lst_head; util_timer *tmp = prev->next; while (tmp) { if (timer->expire < tmp->expire) { prev->next = timer; timer->next = tmp; tmp->prev = timer; timer->prev = prev; break; } prev = tmp; tmp = tmp->next; } if (!tmp) { prev->next = timer; timer->prev = prev; timer->next = NULL; tail = timer; } } void Utils::init(int timeslot) { m_TIMESLOT = timeslot; } //对文件描述符设置非阻塞 int Utils::setnonblocking(int fd) { int old_option = fcntl(fd, F_GETFL); int new_option = old_option | O_NONBLOCK; fcntl(fd, F_SETFL, new_option); return old_option; } //将内核事件表注册读事件,ET模式,选择开启EPOLLONESHOT void Utils::addfd(int epollfd, int fd, bool one_shot, int TRIGMode) { epoll_event event; event.data.fd = fd; if (1 == TRIGMode) event.events = EPOLLIN | EPOLLET | EPOLLRDHUP; else event.events = EPOLLIN | EPOLLRDHUP; if (one_shot) event.events |= EPOLLONESHOT; epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event); setnonblocking(fd); } //信号处理函数 void Utils::sig_handler(int sig) { //为保证函数的可重入性,保留原来的errno int save_errno = errno; int msg = sig; send(u_pipefd[1], (char *)&msg, 1, 0); errno = save_errno; } //设置信号函数 void Utils::addsig(int sig, void(handler)(int), bool restart) { struct sigaction sa; memset(&sa, '\0', sizeof(sa)); sa.sa_handler = handler; if (restart) sa.sa_flags |= SA_RESTART; sigfillset(&sa.sa_mask); assert(sigaction(sig, &sa, NULL) != -1); } //定时处理任务,重新定时以不断触发SIGALRM信号 void Utils::timer_handler() { m_timer_lst.tick(); alarm(m_TIMESLOT); } void Utils::show_error(int connfd, const char *info) { send(connfd, info, strlen(info), 0); close(connfd); } int *Utils::u_pipefd = 0; int Utils::u_epollfd = 0; class Utils; void cb_func(client_data *user_data) { epoll_ctl(Utils::u_epollfd, EPOLL_CTL_DEL, user_data->sockfd, 0); assert(user_data); close(user_data->sockfd); http_conn::m_user_count--; } ================================================ FILE: timer/lst_timer.h ================================================ #ifndef LST_TIMER #define LST_TIMER #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../log/log.h" class util_timer; struct client_data { sockaddr_in address; int sockfd; util_timer *timer; }; class util_timer { public: util_timer() : prev(NULL), next(NULL) {} public: time_t expire; void (* cb_func)(client_data *); client_data *user_data; util_timer *prev; util_timer *next; }; class sort_timer_lst { public: sort_timer_lst(); ~sort_timer_lst(); void add_timer(util_timer *timer); void adjust_timer(util_timer *timer); void del_timer(util_timer *timer); void tick(); private: void add_timer(util_timer *timer, util_timer *lst_head); util_timer *head; util_timer *tail; }; class Utils { public: Utils() {} ~Utils() {} void init(int timeslot); //对文件描述符设置非阻塞 int setnonblocking(int fd); //将内核事件表注册读事件,ET模式,选择开启EPOLLONESHOT void addfd(int epollfd, int fd, bool one_shot, int TRIGMode); //信号处理函数 static void sig_handler(int sig); //设置信号函数 void addsig(int sig, void(handler)(int), bool restart = true); //定时处理任务,重新定时以不断触发SIGALRM信号 void timer_handler(); void show_error(int connfd, const char *info); public: static int *u_pipefd; sort_timer_lst m_timer_lst; static int u_epollfd; int m_TIMESLOT; }; void cb_func(client_data *user_data); #endif ================================================ FILE: webserver.cpp ================================================ #include "webserver.h" WebServer::WebServer() { //http_conn类对象 users = new http_conn[MAX_FD]; //root文件夹路径 char server_path[200]; getcwd(server_path, 200); char root[6] = "/root"; m_root = (char *)malloc(strlen(server_path) + strlen(root) + 1); strcpy(m_root, server_path); strcat(m_root, root); //定时器 users_timer = new client_data[MAX_FD]; } WebServer::~WebServer() { close(m_epollfd); close(m_listenfd); close(m_pipefd[1]); close(m_pipefd[0]); delete[] users; delete[] users_timer; delete m_pool; } void WebServer::init(int port, string user, string passWord, string databaseName, int log_write, int opt_linger, int trigmode, int sql_num, int thread_num, int close_log, int actor_model) { m_port = port; m_user = user; m_passWord = passWord; m_databaseName = databaseName; m_sql_num = sql_num; m_thread_num = thread_num; m_log_write = log_write; m_OPT_LINGER = opt_linger; m_TRIGMode = trigmode; m_close_log = close_log; m_actormodel = actor_model; } void WebServer::trig_mode() { //LT + LT if (0 == m_TRIGMode) { m_LISTENTrigmode = 0; m_CONNTrigmode = 0; } //LT + ET else if (1 == m_TRIGMode) { m_LISTENTrigmode = 0; m_CONNTrigmode = 1; } //ET + LT else if (2 == m_TRIGMode) { m_LISTENTrigmode = 1; m_CONNTrigmode = 0; } //ET + ET else if (3 == m_TRIGMode) { m_LISTENTrigmode = 1; m_CONNTrigmode = 1; } } void WebServer::log_write() { if (0 == m_close_log) { //初始化日志 if (1 == m_log_write) Log::get_instance()->init("./ServerLog", m_close_log, 2000, 800000, 800); else Log::get_instance()->init("./ServerLog", m_close_log, 2000, 800000, 0); } } void WebServer::sql_pool() { //初始化数据库连接池 m_connPool = connection_pool::GetInstance(); m_connPool->init("localhost", m_user, m_passWord, m_databaseName, 3306, m_sql_num, m_close_log); //初始化数据库读取表 users->initmysql_result(m_connPool); } void WebServer::thread_pool() { //线程池 m_pool = new threadpool(m_actormodel, m_connPool, m_thread_num); } void WebServer::eventListen() { //网络编程基础步骤 m_listenfd = socket(PF_INET, SOCK_STREAM, 0); assert(m_listenfd >= 0); //优雅关闭连接 if (0 == m_OPT_LINGER) { struct linger tmp = {0, 1}; setsockopt(m_listenfd, SOL_SOCKET, SO_LINGER, &tmp, sizeof(tmp)); } else if (1 == m_OPT_LINGER) { struct linger tmp = {1, 1}; setsockopt(m_listenfd, SOL_SOCKET, SO_LINGER, &tmp, sizeof(tmp)); } int ret = 0; struct sockaddr_in address; bzero(&address, sizeof(address)); address.sin_family = AF_INET; address.sin_addr.s_addr = htonl(INADDR_ANY); address.sin_port = htons(m_port); int flag = 1; setsockopt(m_listenfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)); ret = bind(m_listenfd, (struct sockaddr *)&address, sizeof(address)); assert(ret >= 0); ret = listen(m_listenfd, 5); assert(ret >= 0); utils.init(TIMESLOT); //epoll创建内核事件表 epoll_event events[MAX_EVENT_NUMBER]; m_epollfd = epoll_create(5); assert(m_epollfd != -1); utils.addfd(m_epollfd, m_listenfd, false, m_LISTENTrigmode); http_conn::m_epollfd = m_epollfd; ret = socketpair(PF_UNIX, SOCK_STREAM, 0, m_pipefd); assert(ret != -1); utils.setnonblocking(m_pipefd[1]); utils.addfd(m_epollfd, m_pipefd[0], false, 0); utils.addsig(SIGPIPE, SIG_IGN); utils.addsig(SIGALRM, utils.sig_handler, false); utils.addsig(SIGTERM, utils.sig_handler, false); alarm(TIMESLOT); //工具类,信号和描述符基础操作 Utils::u_pipefd = m_pipefd; Utils::u_epollfd = m_epollfd; } void WebServer::timer(int connfd, struct sockaddr_in client_address) { users[connfd].init(connfd, client_address, m_root, m_CONNTrigmode, m_close_log, m_user, m_passWord, m_databaseName); //初始化client_data数据 //创建定时器,设置回调函数和超时时间,绑定用户数据,将定时器添加到链表中 users_timer[connfd].address = client_address; users_timer[connfd].sockfd = connfd; util_timer *timer = new util_timer; timer->user_data = &users_timer[connfd]; timer->cb_func = cb_func; time_t cur = time(NULL); timer->expire = cur + 3 * TIMESLOT; users_timer[connfd].timer = timer; utils.m_timer_lst.add_timer(timer); } //若有数据传输,则将定时器往后延迟3个单位 //并对新的定时器在链表上的位置进行调整 void WebServer::adjust_timer(util_timer *timer) { time_t cur = time(NULL); timer->expire = cur + 3 * TIMESLOT; utils.m_timer_lst.adjust_timer(timer); LOG_INFO("%s", "adjust timer once"); } void WebServer::deal_timer(util_timer *timer, int sockfd) { timer->cb_func(&users_timer[sockfd]); if (timer) { utils.m_timer_lst.del_timer(timer); } LOG_INFO("close fd %d", users_timer[sockfd].sockfd); } bool WebServer::dealclientdata() { struct sockaddr_in client_address; socklen_t client_addrlength = sizeof(client_address); if (0 == m_LISTENTrigmode) { int connfd = accept(m_listenfd, (struct sockaddr *)&client_address, &client_addrlength); if (connfd < 0) { LOG_ERROR("%s:errno is:%d", "accept error", errno); return false; } if (http_conn::m_user_count >= MAX_FD) { utils.show_error(connfd, "Internal server busy"); LOG_ERROR("%s", "Internal server busy"); return false; } timer(connfd, client_address); } else { while (1) { int connfd = accept(m_listenfd, (struct sockaddr *)&client_address, &client_addrlength); if (connfd < 0) { LOG_ERROR("%s:errno is:%d", "accept error", errno); break; } if (http_conn::m_user_count >= MAX_FD) { utils.show_error(connfd, "Internal server busy"); LOG_ERROR("%s", "Internal server busy"); break; } timer(connfd, client_address); } return false; } return true; } bool WebServer::dealwithsignal(bool &timeout, bool &stop_server) { int ret = 0; int sig; char signals[1024]; ret = recv(m_pipefd[0], signals, sizeof(signals), 0); if (ret == -1) { return false; } else if (ret == 0) { return false; } else { for (int i = 0; i < ret; ++i) { switch (signals[i]) { case SIGALRM: { timeout = true; break; } case SIGTERM: { stop_server = true; break; } } } } return true; } void WebServer::dealwithread(int sockfd) { util_timer *timer = users_timer[sockfd].timer; //reactor if (1 == m_actormodel) { if (timer) { adjust_timer(timer); } //若监测到读事件,将该事件放入请求队列 m_pool->append(users + sockfd, 0); while (true) { if (1 == users[sockfd].improv) { if (1 == users[sockfd].timer_flag) { deal_timer(timer, sockfd); users[sockfd].timer_flag = 0; } users[sockfd].improv = 0; break; } } } else { //proactor if (users[sockfd].read_once()) { LOG_INFO("deal with the client(%s)", inet_ntoa(users[sockfd].get_address()->sin_addr)); //若监测到读事件,将该事件放入请求队列 m_pool->append_p(users + sockfd); if (timer) { adjust_timer(timer); } } else { deal_timer(timer, sockfd); } } } void WebServer::dealwithwrite(int sockfd) { util_timer *timer = users_timer[sockfd].timer; //reactor if (1 == m_actormodel) { if (timer) { adjust_timer(timer); } m_pool->append(users + sockfd, 1); while (true) { if (1 == users[sockfd].improv) { if (1 == users[sockfd].timer_flag) { deal_timer(timer, sockfd); users[sockfd].timer_flag = 0; } users[sockfd].improv = 0; break; } } } else { //proactor if (users[sockfd].write()) { LOG_INFO("send data to the client(%s)", inet_ntoa(users[sockfd].get_address()->sin_addr)); if (timer) { adjust_timer(timer); } } else { deal_timer(timer, sockfd); } } } void WebServer::eventLoop() { bool timeout = false; bool stop_server = false; while (!stop_server) { int number = epoll_wait(m_epollfd, events, MAX_EVENT_NUMBER, -1); if (number < 0 && errno != EINTR) { LOG_ERROR("%s", "epoll failure"); break; } for (int i = 0; i < number; i++) { int sockfd = events[i].data.fd; //处理新到的客户连接 if (sockfd == m_listenfd) { bool flag = dealclientdata(); if (false == flag) continue; } else if (events[i].events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)) { //服务器端关闭连接,移除对应的定时器 util_timer *timer = users_timer[sockfd].timer; deal_timer(timer, sockfd); } //处理信号 else if ((sockfd == m_pipefd[0]) && (events[i].events & EPOLLIN)) { bool flag = dealwithsignal(timeout, stop_server); if (false == flag) LOG_ERROR("%s", "dealclientdata failure"); } //处理客户连接上接收到的数据 else if (events[i].events & EPOLLIN) { dealwithread(sockfd); } else if (events[i].events & EPOLLOUT) { dealwithwrite(sockfd); } } if (timeout) { utils.timer_handler(); LOG_INFO("%s", "timer tick"); timeout = false; } } } ================================================ FILE: webserver.h ================================================ #ifndef WEBSERVER_H #define WEBSERVER_H #include #include #include #include #include #include #include #include #include #include #include "./threadpool/threadpool.h" #include "./http/http_conn.h" const int MAX_FD = 65536; //最大文件描述符 const int MAX_EVENT_NUMBER = 10000; //最大事件数 const int TIMESLOT = 5; //最小超时单位 class WebServer { public: WebServer(); ~WebServer(); void init(int port , string user, string passWord, string databaseName, int log_write , int opt_linger, int trigmode, int sql_num, int thread_num, int close_log, int actor_model); void thread_pool(); void sql_pool(); void log_write(); void trig_mode(); void eventListen(); void eventLoop(); void timer(int connfd, struct sockaddr_in client_address); void adjust_timer(util_timer *timer); void deal_timer(util_timer *timer, int sockfd); bool dealclientdata(); bool dealwithsignal(bool& timeout, bool& stop_server); void dealwithread(int sockfd); void dealwithwrite(int sockfd); public: //基础 int m_port; char *m_root; int m_log_write; int m_close_log; int m_actormodel; int m_pipefd[2]; int m_epollfd; http_conn *users; //数据库相关 connection_pool *m_connPool; string m_user; //登陆数据库用户名 string m_passWord; //登陆数据库密码 string m_databaseName; //使用数据库名 int m_sql_num; //线程池相关 threadpool *m_pool; int m_thread_num; //epoll_event相关 epoll_event events[MAX_EVENT_NUMBER]; int m_listenfd; int m_OPT_LINGER; int m_TRIGMode; int m_LISTENTrigmode; int m_CONNTrigmode; //定时器相关 client_data *users_timer; Utils utils; }; #endif