├── CGImysql ├── README.md ├── sql_connection_pool.cpp └── sql_connection_pool.h ├── README.md ├── doc ├── (1)如何接收请求.md ├── (2)如何处理请求.md ├── (3)高效响应HTTP请求.md ├── (4)数据库连接池与注册登录.md ├── (5)同步异步日志系统.md └── (6)压力测试.md ├── http ├── README.md ├── http_conn.cpp └── http_conn.h ├── lock ├── README.md ├── locker.h └── locker_new.h ├── log ├── README.md ├── block_queue.h ├── log.cpp └── log.h ├── main.c ├── makefile ├── root ├── carousel │ ├── 01.jpeg │ ├── 01.jpg │ ├── 02.jpeg │ ├── 02.jpg │ ├── 03.jpg │ ├── 04.jpg │ ├── LinuxWebService.jpg │ └── xxx11.jpg ├── css │ ├── layuimini.css │ ├── public.css │ └── themes │ │ └── default.css ├── images │ ├── bg.jpg │ ├── bigVideo.mp4 │ ├── captcha.jpg │ ├── donate_qrcode.png │ ├── favicon.ico │ ├── home.png │ ├── icon-login.png │ ├── loginbg.png │ └── logo.png ├── js │ ├── lay-config.js │ └── lay-module │ │ ├── echarts │ │ ├── echarts.js │ │ └── echartsTheme.js │ │ ├── iconPicker │ │ └── iconPickerFa.js │ │ ├── layarea │ │ └── layarea.js │ │ ├── layuimini │ │ ├── miniAdmin.js │ │ ├── miniMenu.js │ │ ├── miniTab.js │ │ ├── miniTheme.js │ │ └── miniTongji.js │ │ ├── step-lay │ │ ├── step.css │ │ └── step.js │ │ ├── tableSelect │ │ └── tableSelect.js │ │ ├── treetable-lay │ │ ├── treetable.css │ │ └── treetable.js │ │ └── wangEditor │ │ ├── fonts │ │ └── w-e-icon.woff │ │ ├── wangEditor.css │ │ ├── wangEditor.js │ │ ├── wangEditor.min.css │ │ ├── wangEditor.min.js │ │ └── wangEditor.min.js.map ├── lib │ └── layui-v2.6.3 │ │ ├── css │ │ ├── layui.css │ │ └── modules │ │ │ ├── code.css │ │ │ ├── laydate │ │ │ └── default │ │ │ │ └── laydate.css │ │ │ └── layer │ │ │ └── default │ │ │ ├── icon-ext.png │ │ │ ├── icon.png │ │ │ ├── layer.css │ │ │ ├── loading-0.gif │ │ │ ├── loading-1.gif │ │ │ └── loading-2.gif │ │ ├── font │ │ ├── iconfont.eot │ │ ├── iconfont.svg │ │ ├── iconfont.ttf │ │ ├── iconfont.woff │ │ └── iconfont.woff2 │ │ └── layui.js ├── page │ ├── fans.html │ ├── judge.html │ ├── logError.html │ ├── login.html │ ├── picture.html │ ├── register.html │ ├── registerError.html │ ├── video.html │ └── welcome.html └── xxx.jpg ├── server ├── test_presure ├── 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 └── threadpool_new.h └── timer ├── README.md └── lst_timer.h /CGImysql/README.md: -------------------------------------------------------------------------------- 1 | 2 | CGI & 数据库连接池 3 | =============== 4 | 数据库连接池 5 | > * 单例模式,保证唯一 6 | > * list实现连接池 7 | > * 连接池为静态大小 8 | > * 互斥锁实现线程安全 9 | 10 | CGI 11 | > * HTTP请求采用POST方式 12 | > * 登录用户名和密码校验 13 | > * 用户注册及多线程注册安全 14 | -------------------------------------------------------------------------------- /CGImysql/sql_connection_pool.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "sql_connection_pool.h" 10 | 11 | using namespace std; 12 | 13 | connection_pool::connection_pool() 14 | { 15 | this->CurConn = 0; 16 | this->FreeConn = 0; 17 | } 18 | 19 | connection_pool *connection_pool::GetInstance() 20 | { 21 | static connection_pool connPool; 22 | return &connPool; 23 | } 24 | 25 | //构造初始化 26 | void connection_pool::init(string url, string User, string PassWord, string DBName, int Port, unsigned int MaxConn) 27 | { 28 | this->url = url; 29 | this->Port = Port; 30 | this->User = User; 31 | this->PassWord = PassWord; 32 | this->DatabaseName = DBName; 33 | 34 | lock.lock(); 35 | for (int i = 0; i < MaxConn; i++) 36 | { 37 | MYSQL *con = NULL; 38 | con = mysql_init(con); 39 | 40 | if (con == NULL) 41 | { 42 | cout << "Error:" << mysql_error(con); 43 | exit(1); 44 | } 45 | con = mysql_real_connect(con, url.c_str(), User.c_str(), PassWord.c_str(), DBName.c_str(), Port, NULL, 0); 46 | 47 | if (con == NULL) 48 | { 49 | cout << "Error: " << mysql_error(con); 50 | exit(1); 51 | } 52 | connList.push_back(con); 53 | ++FreeConn; 54 | } 55 | 56 | reserve = sem(FreeConn); 57 | 58 | this->MaxConn = FreeConn; 59 | 60 | lock.unlock(); 61 | } 62 | 63 | 64 | //当有请求时,从数据库连接池中返回一个可用连接,更新使用和空闲连接数 65 | MYSQL *connection_pool::GetConnection() 66 | { 67 | MYSQL *con = NULL; 68 | 69 | if (0 == connList.size()) 70 | return NULL; 71 | 72 | reserve.wait(); 73 | 74 | lock.lock(); 75 | 76 | con = connList.front(); 77 | connList.pop_front(); 78 | 79 | --FreeConn; 80 | ++CurConn; 81 | 82 | lock.unlock(); 83 | return con; 84 | } 85 | 86 | //释放当前使用的连接 87 | bool connection_pool::ReleaseConnection(MYSQL *con) 88 | { 89 | if (NULL == con) 90 | return false; 91 | 92 | lock.lock(); 93 | 94 | connList.push_back(con); 95 | ++FreeConn; 96 | --CurConn; 97 | 98 | lock.unlock(); 99 | 100 | reserve.post(); 101 | return true; 102 | } 103 | 104 | //销毁数据库连接池 105 | void connection_pool::DestroyPool() 106 | { 107 | 108 | lock.lock(); 109 | if (connList.size() > 0) 110 | { 111 | list::iterator it; 112 | for (it = connList.begin(); it != connList.end(); ++it) 113 | { 114 | MYSQL *con = *it; 115 | mysql_close(con); 116 | } 117 | CurConn = 0; 118 | FreeConn = 0; 119 | connList.clear(); 120 | 121 | lock.unlock(); 122 | } 123 | 124 | lock.unlock(); 125 | } 126 | 127 | //当前空闲的连接数 128 | int connection_pool::GetFreeConn() 129 | { 130 | return this->FreeConn; 131 | } 132 | 133 | connection_pool::~connection_pool() 134 | { 135 | DestroyPool(); 136 | } 137 | 138 | connectionRAII::connectionRAII(MYSQL **SQL, connection_pool *connPool){ 139 | *SQL = connPool->GetConnection(); 140 | 141 | conRAII = *SQL; 142 | poolRAII = connPool; 143 | } 144 | 145 | connectionRAII::~connectionRAII(){ 146 | poolRAII->ReleaseConnection(conRAII); 147 | } -------------------------------------------------------------------------------- /CGImysql/sql_connection_pool.h: -------------------------------------------------------------------------------- 1 | #ifndef _CONNECTION_POOL_ 2 | #define _CONNECTION_POOL_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "../lock/locker.h" 12 | 13 | using namespace std; 14 | 15 | class connection_pool 16 | { 17 | public: 18 | MYSQL *GetConnection(); //获取数据库连接 19 | bool ReleaseConnection(MYSQL *conn); //释放连接 20 | int GetFreeConn(); //获取连接 21 | void DestroyPool(); //销毁所有连接 22 | 23 | //单例模式 24 | static connection_pool *GetInstance(); 25 | 26 | void init(string url, string User, string PassWord, string DataBaseName, int Port, unsigned int MaxConn); 27 | 28 | connection_pool(); 29 | ~connection_pool(); 30 | 31 | private: 32 | unsigned int MaxConn; //最大连接数 33 | unsigned int CurConn; //当前已使用的连接数 34 | unsigned int FreeConn; //当前空闲的连接数 35 | 36 | private: 37 | locker lock; 38 | list connList; //连接池 39 | sem reserve; 40 | 41 | private: 42 | string url; //主机地址 43 | string Port; //数据库端口号 44 | string User; //登陆数据库用户名 45 | string PassWord; //登陆数据库密码 46 | string DatabaseName; //使用数据库名 47 | }; 48 | 49 | class connectionRAII{ 50 | 51 | public: 52 | connectionRAII(MYSQL **con, connection_pool *connPool); 53 | ~connectionRAII(); 54 | 55 | private: 56 | MYSQL *conRAII; 57 | connection_pool *poolRAII; 58 | }; 59 | 60 | #endif 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | LinuxWebSever 2 | =============== 3 | 4 | ![](https://img.shields.io/badge/build-passing-brightgreen) ![](https://img.shields.io/badge/ubuntu-16.04-blue) ![](https://img.shields.io/badge/MySQL-5.7.29-blue) ![](https://img.shields.io/badge/cmake-3.21-blue) 5 | 6 | **开源地址**:https://github.com/YDLinStars/LinuxWebServer 7 | 8 | **技术栈** 9 | 10 | - `Ubuntu `操作系统常见命令 11 | - `MySQL`数据库的使用 12 | - `gcc`,`vim`,`makefile`指令 13 | - `多路IO复用(epoll)`知识 14 | - `线程同步机制` 15 | - `线程池`、`数据库连接池` 16 | - [`layui前端框架`] 可选 非必要 17 | 18 | # 1 概述 19 | 20 | ## (1)设计思路 21 | 22 | ![设计思路.drawio](https://ydlin.oss-cn-guangzhou.aliyuncs.com/blog-img/%E6%9C%AA%E5%91%BD%E5%90%8D%E7%BB%98%E5%9B%BE.drawio.svg) 23 | 24 | 模块介绍: 25 | 26 | | 模块 | 单个服务器程序 | 27 | | ------------ | -------------------------- | 28 | | I/O处理单元 | 处理客户连接,读写网络数据 | 29 | | 逻辑单元 | 业务进程或线程 | 30 | | 网络存储单元 | 本地数据库、文件或缓存 | 31 | | 请求队列 | 各单元之间的通信方式 | 32 | 33 | ## (2)设计的实现 34 | 35 | Linux下C++轻量级Web服务器,助力初学者快速实践网络编程,搭建属于自己的服务器. 36 | 37 | * 使用**线程池 + epoll(ET和LT均实现) + 模拟Proactor模式**的并发模型 38 | * 使用**状态机**解析HTTP请求报文,支持解析**GET和POST**请求 39 | * 通过访问服务器数据库实现web端用户**注册、登录**功能,可以请求服务器**图片和视频文件** 40 | * 实现**同步/异步日志系统**,记录服务器运行状态 41 | * 经Webbench压力测试可以实现**上万的并发连接**数据交换 42 | 43 | ![LinuxWebService.drawio](https://ydlin.oss-cn-guangzhou.aliyuncs.com/blog-img/LinuxWebService.drawio.svg) 44 | 45 | 主要有四大功能模块:半同步/半反应堆线程池,同步异步日志系统,定时器,请求逻辑处理 46 | 47 | # 2 安装与配置 48 | 49 | ## 开发环境 50 | 51 | VSCode 52 | 53 | ## 环境搭建 54 | 55 | * 服务器测试环境 56 | 57 | * `Ubuntu版本16.04` 58 | * `MySQL版本5.7.29` 59 | 60 | > sudo apt-get install mysql-server 61 | > 62 | > sudo apt-get install libmysqlclient-dev 63 | > 64 | > 否则报如下的错: 65 | > 66 | > **fatal error: mysql/mysql.h: No such file or directory** 67 | 68 | * 浏览器测试环境 69 | 70 | * Windows、Linux均可 71 | * Chrome 72 | * FireFox 73 | * 其他浏览器暂无测试 74 | 75 | * 测试前确认已安装MySQL数据库 76 | 77 | > mysql -h localhost -u root 78 | 79 | ```C++ 80 | // 建立yourdb库 81 | create database webdb; 82 | 83 | // 创建user表 84 | USE webdb; 85 | CREATE TABLE user( 86 | username char(50) NULL, 87 | passwd char(50) NULL 88 | )ENGINE=InnoDB; 89 | 90 | // 添加数据 91 | INSERT INTO user(username, passwd) VALUES('ydlin', '123456'); 92 | ``` 93 | 94 | * 修改main.c中的数据库初始化信息 95 | 96 | ```C++ 97 | // root root修改为服务器数据库的登录名和密码 98 | // yourdb修改为上述创建的yourdb库名 99 | connPool->init("localhost", "root", "root", "yourdb", 3306, 8); 100 | ``` 101 | 102 | * 修改http_conn.cpp中的root路径 103 | 104 | ```C++ 105 | // 修改为root文件夹所在路径 106 | const char *doc_root = "/home/ydlin/Desktop/LinuxWebServer/root"; 107 | ``` 108 | 109 | # 3 使用 110 | 111 | ## (1)基础测试 112 | 113 | * 生成server 114 | 115 | ```C++ 116 | make server 117 | ``` 118 | 119 | * 启动server 120 | 121 | ```C++ 122 | ./server port 123 | ``` 124 | 125 | * 浏览器端 126 | 127 | ```C++ 128 | ip:port 129 | ``` 130 | 131 | ## (2)个性化测试 132 | 133 | > * I/O复用方式,listenfd和connfd可以使用不同的触发模式,代码中使用LT + LT模式,可以自由修改与搭配. 134 | 135 | > 这部分的话需要手动调整一下代码 136 | 137 | - [x] LT + LT模式 138 | * listenfd触发模式,关闭main.c中listenfdET,打开listenfdLT 139 | 140 | ```C++ 141 | 26 //#define listenfdET //边缘触发非阻塞 142 | 27 #define listenfdLT //水平触发阻塞 143 | ``` 144 | * listenfd触发模式,关闭http_conn.cpp中listenfdET,打开listenfdLT 145 | 146 | ```C++ 147 | 10 //#define listenfdET //边缘触发非阻塞 148 | 11 #define listenfdLT //水平触发阻塞 149 | ``` 150 | 151 | * connfd触发模式,关闭http_conn.cpp中connfdET,打开connfdLT 152 | 153 | ```C++ 154 | 7 //#define connfdET //边缘触发非阻塞 155 | 8 #define connfdLT //水平触发阻塞 156 | ``` 157 | 158 | - [ ] LT + ET模式 159 | * listenfd触发模式,关闭main.c中listenfdET,打开listenfdLT 160 | 161 | ```C++ 162 | 26 //#define listenfdET //边缘触发非阻塞 163 | 27 #define listenfdLT //水平触发阻塞 164 | ``` 165 | 166 | * listenfd触发模式,关闭http_conn.cpp中listenfdET,打开listenfdLT 167 | 168 | ```C++ 169 | 10 //#define listenfdET //边缘触发非阻塞 170 | 11 #define listenfdLT //水平触发阻塞 171 | ``` 172 | 173 | * connfd触发模式,打开http_conn.cpp中connfdET,关闭connfdLT 174 | 175 | ```C++ 176 | 7 #define connfdET //边缘触发非阻塞 177 | 8 //#define connfdLT //水平触发阻塞 178 | ``` 179 | 180 | > * 日志写入方式,代码中使用同步日志,可以修改为异步写入. 181 | 182 | - [x] 同步写入日志 183 | * 关闭main.c中ASYNLOG,打开同步写入SYNLOG 184 | 185 | ```C++ 186 | 25 #define SYNLOG //同步写日志 187 | 26 //#define ASYNLOG /异步写日志 188 | ``` 189 | 190 | - [ ] 异步写入日志 191 | * 关闭main.c中SYNLOG,打开异步写入ASYNLOG 192 | 193 | ```C++ 194 | 25 //#define SYNLOG //同步写日志 195 | 26 #define ASYNLOG /异步写日志 196 | ``` 197 | * 选择I/O复用方式或日志写入方式后,按照前述生成server,启动server,即可进行测试. 198 | 199 | ## (3) 压力测试 200 | 201 | 压力测试的安装: 202 | 203 | > sudo apt-get install exuberant-ctags 204 | > 205 | > cd webbench.1.5 206 | > 207 | > sudo make && make install 208 | 209 | 安装成功: 210 | 211 | > root@ubuntu:/home/ydlin/Desktop/LinuxWebServer/test_presure/webbench-1.5# make && make install 212 | > make: Nothing to be done for 'all'. 213 | > install -s webbench /usr/local/bin 214 | > install -m 644 webbench.1 /usr/local/man/man1 215 | > install -d /usr/local/share/doc/webbench 216 | > install -m 644 debian/copyright /usr/local/share/doc/webbench 217 | > install -m 644 debian/changelog /usr/local/share/doc/webbench 218 | > root@ubuntu:/home/ydlin/Desktop/LinuxWebServer/test_presure/webbench-1.5# which webbench 219 | 220 | 压力测试的参数: 221 | 222 | ```bash 223 | webbench -c 10500 -t 5 http://127.0.0.1:9906/ 224 | ``` 225 | 226 | 客户端数量10500, `t`运行测试的时间。 227 | 228 | Benchmarking: GET http://127.0.0.1:9906/ 229 | 10500 clients, running 5 sec. 230 | 231 | Speed=790884 pages/min, 1476294 bytes/sec. 232 | Requests: 65907 susceed, 0 failed. 233 | 234 | ![image-20220602001514823](https://ydlin.oss-cn-guangzhou.aliyuncs.com/blog-img/image-20220602001514823.png) 235 | 236 | LT + LT,93251 QPS 237 | 238 | > - 并发连接总数:10500 239 | > - 访问服务器时间:5s 240 | > - 所有访问均成功 241 | 242 | 243 | 244 | # 4 项目演示 245 | 246 | ## 项目界面展示 247 | 248 | ### (1) 欢迎界面 249 | 250 | ![image-20220601233125805](https://ydlin.oss-cn-guangzhou.aliyuncs.com/blog-img/image-20220601233125805.png) 251 | 252 | ### (2) 注册界面 253 | 254 | ![image-20220601233159752](https://ydlin.oss-cn-guangzhou.aliyuncs.com/blog-img/image-20220601233159752.png) 255 | 256 | ### (3)登录界面 257 | 258 | ![image-20220601233250141](https://ydlin.oss-cn-guangzhou.aliyuncs.com/blog-img/image-20220601233250141.png) 259 | 260 | ### (4)主界面 261 | 262 | ![image-20220601233323555](https://ydlin.oss-cn-guangzhou.aliyuncs.com/blog-img/image-20220601233323555.png) 263 | 264 | ### (5)轮播图 265 | 266 | 做了一个轮播图 267 | 268 | ![image-20220601235327449](https://ydlin.oss-cn-guangzhou.aliyuncs.com/blog-img/image-20220601235327449.png) 269 | 270 | ### (6) **播放视频** 271 | 272 | ![image-20220601233429685](https://ydlin.oss-cn-guangzhou.aliyuncs.com/blog-img/image-20220601233429685.png) 273 | 274 | # 5 关键技术 275 | 276 | 关键的技术实现主要有一下几个方面,同时将响应的笔记整理到了对应的博客地址里面,有需要的小伙伴可以点开来看看欧,有惊喜! 277 | 278 | ## (1)如何接收请求 279 | 280 | https://ydlin.blog.csdn.net/article/details/125090338 281 | 282 | ## (2)如何处理请求报文 283 | 284 | https://ydlin.blog.csdn.net/article/details/125090379 285 | 286 | ## (3)如何响应请求 287 | 288 | https://ydlin.blog.csdn.net/article/details/125090441 289 | 290 | ## (4)数据库连接池以及登录注册 291 | 292 | https://ydlin.blog.csdn.net/article/details/125090469 293 | 294 | ## (5)同步异步日志系统设计 295 | 296 | https://ydlin.blog.csdn.net/article/details/125090506 297 | 298 | ## (6)压力测试与服务器优化思考 299 | 300 | https://ydlin.blog.csdn.net/article/details/125090546 301 | 302 | # 6 学习资料 303 | 304 | - TCP/IP网络编程 305 | - Linux高性能服务器编程---游双 306 | - 社长的[WebServer](https://github.com/qinguoyi/TinyWebServer) -------------------------------------------------------------------------------- /doc/(1)如何接收请求.md: -------------------------------------------------------------------------------- 1 | # 一、前言 2 | 3 | 作为一个轻量级的Web服务器,高效地处理和接受客户端发送请求并进行处理是非常关键的。 4 | 5 | 本章节主要围绕两部分来讲: 6 | 7 | - 如何接收客户端发来的HTTP请求报文呢? 8 | - 如何高效处理HTTP请求报文? 9 | 10 | # 二、如何接收客户端发来的HTTP请求报文 11 | 12 | 13 | 14 | ![img](https://ydlin.oss-cn-guangzhou.aliyuncs.com/blog-img/tcp.jpg) 15 | 16 | Web服务器端通过`socket监听`来自用户的请求。 17 | 18 | 远端的很多用户会尝试去`connect()`这个Web Server正在`listen`的这个`port`,而监听到的这些连接会排队等待被`accept()`.由于用户连接请求是随机到达的异步时间,所以监听`socket(lisenfs)` lisen到的新的客户连接并且加入监听队列,当`accept`这个连接时候,会分配一个逻辑单元来处理这个用户请求。 19 | 20 | ## 2.1 多路IO复用技术(Select、Poll与Epoll的区别) 21 | 22 | - 调用函数 23 | 24 | - select和poll都是一个函数,epoll是一组函数 25 | 26 | - **文件描述符数量** 27 | 28 | - select通过**线性表**描述文件描述符集合,文件描述符有上限,一般是1024,但可以修改源码,重新编译内核,不推荐 29 | - poll是链表描述,突破了文件描述符上限,最大可以打开文件的数目 30 | - epoll通过红黑树描述,最大可以打开文件的数目,可以通过命令ulimit -n number修改,仅对当前终端有效 31 | 32 | - **将文件描述符从用户传给内核** 33 | 34 | - select和poll通过将所有文件描述符拷贝到内核态,每次调用都需要拷贝 35 | - epoll通过epoll_create建立一棵红黑树,通过epoll_ctl将要监听的文件描述符注册到红黑树上 36 | 37 | - **内核判断就绪的文件描述符** 38 | 39 | - select 和 poll 每次调用都会对连接进行**线性遍历**,所以随着FD的增加会造成遍历速度慢的“线性下降性能问题” 40 | - epoll 因为epoll内核种实现是根据每个**fd上的callback函数来实现的**,只有活跃的socket才会主动调用callback,所以在活跃socket较少的情况下,使用epoll没有前面两者的线性下降的性能问题,但是所有socket都很活跃,可能有性能问题。 41 | 42 | - **应用程序索引就绪文件描述符** 43 | 44 | - select/poll只返回发生了事件的文件描述符的个数,若知道是哪个发生了事件,同样需要遍历 45 | - epoll返回的发生了事件的个数和结构体数组,结构体包含socket的信息,因此直接处理返回的数组即可 46 | 47 | - **工作模式** 48 | 49 | - select和poll都只能工作在相对低效的LT模式下 50 | - epoll则可以工作在ET高效模式,并且epoll还支持EPOLLONESHOT事件,该事件能进一步减少可读、可写和异常事件被触发的次数。 51 | 52 | > 一个socket连接在任一时刻都只被一个线程处理,可以使用 epoll 的 EPOLLONESHOT 事件实现。 53 | 54 | - **应用场景** 55 | 56 | - 当所有的`fd`都是活跃连接,epoll需要建立文件系统,红黑树和链表对于此来说,效率反而不高,不如selece和poll 57 | - 当监测的fd数目较小,且各个fd都比较活跃,建议使用select或者poll 58 | - 当监测的fd数目非常大,成千上万,且单位时间只有其中的一部分fd处于就绪状态,这个时候使用epoll能够明显提升性能 59 | 60 | 61 | ## 2.2 事件接收处理模式 62 | 63 | 并发:在处理这个请求的同时,还需要继续监听其他客户的请求并分配其另一逻辑单元来处理。 64 | 65 | - 通过epoll 这种I/O复用技术来实现对监听socket(`listenfd`)和连接socket(客户请求)的同时监听。 66 | 67 | > 虽然I/O复用可以同时**监听多个文件描述符**,但是它本身是`阻塞`的,并且`多个文件描述符同时就绪`的时候,如果不采用额外措施,程序只能按照顺序处理其中就绪的每个文件描述符。 68 | 69 | 因此可以通过多线程并发,用线程池来实现并发,为每个就绪的文件描述符分配一个逻辑单元(线程)来处理。 70 | 71 | **服务器程序通常需要处理三类事件**:I/O事件、信号、定时事件 72 | 73 | **有两种事件并发处理模式** 74 | 75 | - `Reactor模式`:要求主线程(I/O处理单元)只负责监听文件描述符上是否有事件发生(可读、可写),若有,则立即通知工作线程(逻辑单元),将socket可读可写事件放入请求队列,交给工作线程处理。 76 | - `Proactor模式`:将所有的I/O操作都交给主线程和内核来处理(进行读、写),工作线程仅负责处理逻辑,如主线程读完成后`users[sockfd].read()`,选择一个工作线程来处理客户请求`pool->append(users + sockfd)` 77 | 78 | **模拟Proactor模式** 79 | 80 | 使用同步I/O方式模拟出Proactor模式的原理是:主线程执行数据读写操作,读写完成之后,主线程向工作线程通知这一“完成事件”。那么从工作线程的角度来看,它们就直接获得了数据读写的结果,接下来要做的只是对读写的结果进行逻辑处理。 81 | 82 | 通常使用同步I/O模型(如`epoll_wait`)实现Reactor,使用异步I/O(如`aio_read`和`aio_write`)实现Proactor。但在此项目中,我们使用的是**同步I/O模拟的Proactor**事件处理模式。 83 | 84 | > 使用模拟`proactor`模式,主线程负责监听,监听有事件之后,从socket中循环读取数据,然后将读取到的数据封装成一个请求对象观察入队列 85 | 86 | 以epoll_wait为例子 87 | 88 | > - 主线程往epoll内核事件表注册socket上的读就绪事件。 89 | > - 主线程调用epoll_wait等待socket上有数据可读 90 | > - 当socket上有数据可读,epoll_wait通知主线程,主线程从socket循环读取数据,直到没有更多数据可读,然后将读取到的数据封装成一个请求对象并插入请求队列。 91 | > - 睡眠在请求队列上某个工作线程被唤醒,它获得请求对象并处理客户请求,然后往epoll内核事件表中注册该socket上的写就绪事件 92 | > - 主线程调用epoll_wait等待socket可写。 93 | > - 当socket上有数据可写,epoll_wait通知主线程。主线程往socket上写入服务器处理客户请求的结果。 94 | 95 | **Linux下有三种IO复用方式**:`epoll`,`select`、`poll`,为什么使用`epoll`,他和其他两个有什么区别(见上) 96 | 97 | ## 2.3 项目细节 98 | 99 | ### (1)主线程调用不同函数进行注册,两次注册是否没有必要,直接主线程循环读取然后封装放请求队列不就行了么? 100 | 101 | 不对,如果数据一直没来,直接进行循环读取就会持续在这里发生阻塞,这就是同步IO的特点,所以一定要注册一下然后等通知,这样就可以避免长期阻塞等候数据。同步:它主线程使用epoll向内核注册读事件。但是这里内核不会负责将数据从内核读到用户缓冲区,最后还是要靠主线程也就是用户程序read()函数等负责将内核数据循环读到用户缓冲区。 102 | 103 | Epoll对文件操作符的操作有两种模式: `LT`(电平触发)、`ET`(边缘触发),二者的区别在于当你调用`epoll_wait`的时候内核里面发生了什么: 104 | 105 | > 助理解 106 | > 107 | > **LT条件触发的特性**: 108 | > 109 | > > 条件触发方式中,只要输入缓冲有数据就会一直通知该事件 110 | > 111 | > 例如,服务器端输入缓冲收到 50 字节数据时,服务器端操作系统将通知该事件(注册到发生变化的文件描述符)。但是服务器端读取 20 字节后还剩下 30 字节的情况下,仍会注册事件。也就是说,条件触发方式中,只要输入缓冲中还剩有数据,就将以事件方式再次注册。 112 | > 113 | > **ET边缘触发特性**: 114 | > 115 | > 边缘触发中输入缓冲收到数据时仅注册 1 次该事件。即使输入缓冲中还留有数据,也不会再进行注册。 116 | > 117 | > 默认是LT的方式 118 | 119 | - LT(水平触发):类似`select`,LT会去遍历在epoll事件表中每个文件描述符,来观察是否有我们感兴趣的事件发生,如果有(触发了该文件描述符上的回调函数),`epoll_wait`就会以非阻塞的方式返回。**若该epoll事件没有被处理完(没有返回`EWOULDBLOCK`),该事件还会被后续的`epoll_wait`再次触发。** 120 | - ET(边缘触发):ET在发现有我们感兴趣的事件发生后,立即返回,并且`sleep`这一事件的`epoll_wait`,不管该事件有没有结束。 121 | 122 | > sleep和wait的区别: 123 | > 1、sleep是Thread的静态方法,wait是Object的方法,任何对象实例都能调用。 124 | > 2、**sleep不会释放锁,它也不需要占用锁。wait会释放锁**,但调用它的前提是当前线程占有锁(即代码要在synchronized中)。当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备。 125 | > 3、它们都可以被interrupted方法中断。 126 | 127 | ET模式 128 | 129 | 缺点:应用层业务逻辑复杂,容易遗漏事件,很难用好。 130 | 131 | 优点:相对LT模式效率比较高。一触发立即处理事件。 132 | 133 | LT模式: 134 | 135 | 优点:编程更符合用户直觉,业务层逻辑更简单。 136 | 137 | 缺点:效率比ET低。 138 | 139 | ### **(2)什么时候用ET,什么时候用LT?** 140 | 141 | LT适用于并发量小的情况,ET适用于并发量大的情况。 142 | 143 | **为什么?** 144 | 145 | ET在通知用户之后,就会将fd从就绪链表中删除,而LT不会,它会一直保留,这就会导致随着fd增多,就绪链表越大,每次都要从头开始遍历找到对应的fd,所以并发量越大效率越低。ET因为会删除所以效率比较高。 146 | 147 | ### (3)**怎么解决LT的缺点?** 148 | 149 | LT模式下,可写状态的fd会一直触发事件,该怎么处理这个问题 150 | 151 | 方法1:每次要写数据时,将fd绑定EPOLLOUT事件,写完后将fd同EPOLLOUT从epoll中移除。 152 | 153 | 方法2:方法一中每次写数据都要操作epoll。如果数据量很少,socket很容易将数据发送出去。可以考虑改成:数据量很少时直接send,数据量很多时在采用方法1. 154 | 155 | ### (4)**触发LT模式后,读一次还是循环读?** 156 | 157 | 读一次。 158 | 159 | ### (5)为什么ET模式下一定要设置非阻塞? 160 | 161 | 因为ET模式下是无限循环读,直到出现错误为EAGAIN或者EWOULDBLOCK,这两个错误表示socket为空,不用再读了,然后就停止循环了,如果是非阻塞,循环读在socket为空的时候就会阻塞到那里,主线程的read()函数一旦阻塞住,当再有其他监听事件过来就没办法读了,给其他事情造成了影响,所以必须要设置为非阻塞。 162 | 163 | ### (6)epoll 和 阻塞IO 还是非阻塞IO 搭配使用 164 | 165 | > http://t.zoukankan.com/lawliet12-p-13508057.html 166 | 167 | ### (7)在读取数据的时候怎么直到读取完了呢 168 | 169 | > 非阻塞socket而言,EAGAIN不是一种错误。在VxWorks和Windows上,EAGAIN的名字叫做EWOULDBLOCK。 170 | 171 | 172 | 173 | - LT水平触发模式 174 | 175 | - - epoll_wait检测到文件描述符有事件发生,则将其通知给应用程序,应用程序可以不立即处理该事件。 176 | - 当下一次调用epoll_wait时,epoll_wait还会再次向应用程序报告此事件,直至被处理 177 | 178 | - ET边缘触发模式 179 | 180 | - - epoll_wait检测到文件描述符有事件发生,则将其通知给应用程序,应用程序必须立即处理该事件 181 | - **必须要一次性将数据读取完,使用非阻塞I/O,读取到出现eagain** 182 | 183 | # 三、小结 184 | 185 | 在`Linux`下文件描述符不活跃但是大量存在的时候,优先采用`epoll`这种多路复用技术,多路复用技术也是线程阻塞的,但是他可以同时阻塞多个I/O请求,这样可以极大的提高服务器的性能。 186 | 187 | 下一章 重点讲解一下服务器是如何处理HTTP请求报文的! -------------------------------------------------------------------------------- /doc/(2)如何处理请求.md: -------------------------------------------------------------------------------- 1 | # 一、前言 2 | 3 | 在解决多接受的问题后,我们还需要考虑如何提高处理请求,请求的方式有很多种一种是`HTTP`请求,`数据库访问`等逻辑处理请求。 4 | 5 | 因此我们可以借助线程池,**主线程负责读写,工作线程(线程池中的线程)负责处理逻辑(HTTP请求报文的解析等等)**。 6 | 7 | 8 | 9 | # 二、设计架构图 10 | 11 | **半同步/半反应堆线程池** 12 | 13 | ![image-20220601193827012](https://ydlin.oss-cn-guangzhou.aliyuncs.com/blog-img/image-20220601193827012.png) 14 | 15 | # 三、如何处理HTTP请求报文的? 16 | 17 | ## 3.0 线程池中的并发处理模式 18 | 19 | 半异步:异步处理I/O事件,就是客户端向服务器端的请求的接收,是通过异步线程进行处理的,来请求触发处理,没有来的时候处理其他事情。 20 | 21 | 半同步:是指同步处理请求数据,异步线程接收完请求之后会封装一下插入队列,工作线程就依次同步从队列中取出请求对象进行处理。 22 | 23 | 半同步/半反应堆:它是半同步/半异步模式的变体,它核心在于,主线程充当异步线程,只负责监听客户端请求以及向内核注册读写事件,这和前面的rector(反应堆)事件处理模型类似,所以这样称呼。 24 | 25 | 并发编程方法的实现有多线程和多进程两种,但这里涉及的并发模式指I/O处理单元与逻辑单元的协同完成任务的方法。 26 | 27 | - 半同步/半异步模式 28 | - 领导者/追随者模式 29 | 30 | 并发模式中的同步和异步 31 | 32 | > - 同步指的是程序完全按照代码序列的顺序执行 33 | > - 异步指的是程序的执行需要由系统事件驱动 34 | 35 | 半同步/半异步模式工作流程 36 | 37 | > - 同步线程用于处理客户逻辑 38 | > - 异步线程用于处理I/O事件 39 | > - 异步线程监听到客户请求后,就将其封装成请求对象并插入请求队列中 40 | > - 请求队列将通知某个工作在**同步模式的工作线程**来读取并处理该请求对象 41 | 42 | 半同步/半反应堆工作流程(以Proactor模式为例) 43 | 44 | > - **主线程充当异步线程**,负责监听所有socket上的事件 45 | > - 若有新请求到来,**主线程接收之以得到新的连接socket,然后往epoll内核事件表中注册**该socket上的读写事件 46 | > - 如果连接socket上**有读写事件发生**,主线程从socket上接收数据,并**将数据封装成请求对象插入到请求队列**中 47 | > - 所有工作线程睡眠在请求队列上,当有任务到来时,通过竞争(如互斥锁)获得任务的接管权 48 | 49 | 该项目使用线程池(半同步半反应堆模式)并发处理用户请求,**主线程负责读写,工作线程(线程池中的线程)负责处理逻辑(HTTP请求报文的解析等等)**。 50 | 51 | > 具体的,主线程为异步线程,负责监听文件描述符,接收socket新连接,若当前监听的socket发生了读写事件,然后将任务插入到请求队列。工作线程从请求队列中取出任务,完成读写数据的处理。 52 | 53 | 通过之前的代码,我们将`listenfd`上到达的`connection`通过 `accept()`接收,并返回一个新的socket文件描述符`connfd`用于和用户通信,并对用户请求返回响应,同时将这个`connfd`注册到内核事件表中,等用户发来请求报文。这个过程是:通过`epoll_wait`发现这个`connfd`上有可读事件了(`EPOLLIN`),主线程就将这个HTTP的请求报文读进这个连接socket的读缓存中`users[sockfd].read()`,然后将该任务对象(指针)插入线程池的请求队列中`pool->append(users + sockfd);`,线程池的实现还需要依靠**锁机制**以及**信号量**机制来实现线程同步,保证操作的原子性。 54 | 55 | > 前提是保证所有客户请求都是无状态的,因为同一个连接上的不同请求可能会由不同的线程处理。 56 | 57 | 58 | 59 | ## 3.1 为什么使用线程池 60 | 61 | 当你需要**限制你应用程序中同时运行的线程数**时,线程池非常有用。因为启动**一个新线程会带来性能开销**,每个线程也会为其堆栈分配一些内存等。为了任务的并发执行,我们可以将这些任务传递到线程池,而不是为每个任务动态开启一个新的线程。 62 | 63 | ## 3.2 **处理过程中,线程池线程的选择有哪几种方式** 64 | 65 | 主线程选择哪个子线程来为新任务服务方式: 66 | 67 | 1. 随机算法和轮流选取算法。 68 | 2. 主进程和所有地子进程通过一个共享的工作队列(list 单链表)来同步,子进程都睡眠在该工作队列上。 69 | 70 | ## 3.3 线程池实现细节 71 | 72 | - 所谓线程池,就是一个`pthread_t`类型的普通数组,通过`pthread_create()`函数创建`m_thread_number`个**线程**,用来执行`worker()`函数以执行每个请求处理函数(HTTP请求的`process`函数),通过`pthread_detach()`将线程设置成脱离态(detached)后,当这一线程运行结束时,它的资源会被系统自动回收,而不再需要在其它线程中对其进行 `pthread_join()` 操作。 消息队列的大小由机器硬件来决定,本实验环境选取`max_request = 10000` 73 | 74 | 两种高效的并发模式:并发其实适合于**``I/O`密集型**而不适合于计算密集型,比如经常读写文件,访问数据库等,由于I/O操作的速度远没有CPU计算速度快,所以让程序阻塞于I/O操作将浪费大量的CPU时间。 75 | 76 | - 操作工作队列一定**要加锁**(`locker`),因为它被所有线程共享(与最大请求数做个判断,允许)。 77 | - 我们用**信号量来标识请求队列中的请求数**,通过`m_queuestat.wait();`来等待一个请求队列中待处理的HTTP请求,然后交给线程池中的空闲线程来处理。 78 | 79 | > 设置成脱离态的目的:为了在使用线程的时候,避免线程的资源得不到正确的释放,从而导致了内存泄漏的问题。所以要确保进程为可分离的的状态,否则要进行线程等待已回收他的资源。 80 | 81 | ## 3.4 线程的同步机制有哪些? 82 | 83 | 临界区,互斥对象,信号量,事件对象(条件变量的应用)。 84 | 85 | 其中**临界区和互斥对象**用于互斥控制;**信号量和事件对象**主要用于同步控制。 86 | 87 | > 事件对象:通过通知操作的方式来保持线程的同步,还可以实现对多个线程的优先级比较的操作。 88 | 89 | - POSIX信号量:可用于进程同步,也可用于线程同步。 90 | - POSIX互斥锁 + 条件变量:只能用于线程同步。 91 | 92 | > ps: 信号量、共享内存,以及消息队列等System V IPC三剑客主要关注进程间通信; 93 | > 94 | > 而条件变量、互斥锁,主要关注线程间通信。 95 | 96 | ## 3.5 **线程池**具体做法 97 | 98 | 通过epoll_wait 发现这个connfd上有可读事件了(EPOLLIN),主线程就将这个HTTP请求报文读进这个连接socket的读缓存中users.[sockfd].read(),讲后将任务对象(指针)插入线程池的请求队列中pool->append(users + sockfd); 99 | 100 | > 线程池的实现还需要依靠锁机制以及信号量机制来实现线程同步,保证操作的原子性 101 | 102 | ## 3.6 介绍一下几种典型的锁? 103 | 104 | #### 读写锁 105 | 106 | - 多个读者可以同时进行读 107 | - 写者必须互斥(只允许一个写者写,也不能读者写者同时进行) 108 | - 写者优先于读者(一旦有写者,则后续读者必须等待,唤醒时优先考虑写者) 109 | 110 | #### 互斥锁 111 | 112 | 一次只能一个线程拥有互斥锁,其他线程只有等待 113 | 114 | 互斥锁是在抢锁失败的情况下主动放弃CPU进入睡眠状态直到锁的状态改变时再唤醒,而操作系统负责线程调度,为了实现锁的状态发生改变时唤醒阻塞的线程或者进程,需要把锁交给操作系统管理,所以互斥锁在加锁操作时涉及上下文的切换。互斥锁实际的效率还是可以让人接受的,加锁的时间大概100ns左右,而实际上互斥锁的一种可能的实现是先自旋一段时间,当自旋的时间超过阀值之后再将线程投入睡眠中,因此在并发运算中使用互斥锁(每次占用锁的时间很短)的效果可能不亚于使用自旋锁 115 | 116 | #### 条件变量 117 | 118 | 互斥锁一个明显的缺点是他只有两种状态:锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,他常和互斥锁一起使用,以免出现竞态条件。当条件不满足时,线程往往解开相应的互斥锁并阻塞线程然后等待条件发生变化。一旦其他的某个线程改变了条件变量,他将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。总的来说**互斥锁是线程间互斥的机制,条件变量则是同步机制。** 119 | 120 | > 图解操作系统里面 妈妈叫孩子吃饭的例子 121 | 122 | #### 自旋锁 123 | 124 | 如果进线程无法取得锁,进线程不会立刻放弃CPU时间片,而是一直循环尝试获取锁,直到获取为止。如果别的线程长时期占有锁,那么自旋就是在浪费CPU做无用功,但是自旋锁一般应用于加锁时间很短的场景,这个时候效率比较高。 125 | 126 | ## 3.7 线程数量的选择 127 | 128 | 最佳线程数 = CPU当前可使用的Cores数 * 当前CPU的利用率 * (1 + CPU等待时间 / CPU处理时间) 129 | 130 | 线程池中线程的数量如何确定: 131 | 132 | > 针对不同的任务性质而言:CPU密集型任务应配置尽可能小的线程,如配置CPU个数+1的线程数,IO密集型任务应配置尽可能多的线程,因为IO操作不占用CPU,不要让CPU闲下来,应加大线程数量,如配置两倍CPU个数+1,而对于混合型的任务,如果可以拆分,拆分成IO密集型和CPU密集型分别处理,前提是两者运行的时间是差不多的,如果处理时间相差很大,则没必要拆分了。 133 | > 134 | > 任务对其他系统资源有依赖:如某个任务依赖数据库的连接返回的结果,这时候等待的时间越长,则CPU空闲的时间越长,那么线程数量应设置得越大,才能更好的利用CPU。 135 | 136 | # 四、实现细节 137 | 138 | ## 1. 线程池中的工作线程是一直等待吗? 139 | 140 | 线程池中的工作线程是处于一直阻塞等待的模式下的。在run函数中,我们为了能够处理高并发的问题,将线程池中的工作线程都设置为阻塞等待在请求队列是否不为空的条件上,因此项目中线程池中的工作线程是处于一直阻塞等待的模式下的。 141 | 142 | ## 2. 线程池工作线程处理完一个任务后的状态是什么? 143 | 144 | 这里要分**两种**情况考虑 145 | 146 | (1) 当处理完任务后如果请求队列为空时,则这个线程重新回到阻塞等待的状态 147 | 148 | (2) 当处理完任务后如果请求队列不为空时,那么这个线程将处于与其他线程竞争资源的状态,谁获得锁谁就获得了处理事件的资格。 149 | 150 | 151 | 152 | ## 3. 如果同时有1000个客户端进行访问请求,线程数不多,怎么能及时响应处理每一个呢? 153 | 154 | 本项目是通过对子线程循环调用来解决高并发的问题的。 155 | 156 | 首先在创建线程的同时就调用了`pthread_detach`将线程进行**分离**,不用单独对工作线程进行回收,资源自动回收。 157 | 158 | 我们通过子线程的run调用函数进行while循环,让每一个线程池中的线程**永远都不会停终止**,访问请求被封装到请求队列(`list`)中,如果没有任务线程就**一直阻塞等待**,有任务线程就抢占式进行处理,直到**请求队列为空**,表示任务全部处理完成。 159 | 160 | ## 4. 如果一个客户请求需要占用线程很久的时间,会不会影响接下来的客户请求呢,有什么好的策略呢? 161 | 162 | 会影响接下来的客户请求,因为线程池内线程的数量时有限的,如果客户请求占用线程时间过久的话会影响到处理请求的效率,当请求处理过慢时会造成后续接受的请求只能在请求队列中等待被处理,从而影响接下来的客户请求。 163 | 164 | **应对策略:** 165 | 166 | 我们可以为线程处理请求对象设置处理超时时间, 超过时间先发送信号告知线程处理超时,然后设定一个时间间隔再次检测,若此时这个请求还占用线程则直接将其断开连接。 167 | 168 | 169 | 170 | ## 5 **什么是虚假唤醒?** 171 | 172 | 举个例子,我们现在有一个生产者-消费者队列和三个线程。 173 | 174 | **1)** 1号线程从队列中获取了一个元素,此时队列变为空。 175 | 176 | **2)** 2号线程也想从队列中获取一个元素,但此时队列为空,2号线程便只能进入阻塞(cond.wait()),等待队列非空。 177 | 178 | **3)** 这时,3号线程将一个元素入队,并调用cond.notify()唤醒条件变量。 179 | 180 | **4)** 处于等待状态的2号线程接收到3号线程的唤醒信号,便准备解除阻塞状态,执行接下来的任务(获取队列中的元素)。 181 | 182 | **5)** 然而可能出现这样的情况:当2号线程准备获得队列的锁,去获取队列中的元素时,此时1号线程刚好执行完之前的元素操作,返回再去请求队列中的元素,1号线程便获得队列的锁,检查到队列非空,就获取到了3号线程刚刚入队的元素,然后释放队列锁。 183 | 184 | **6)** 等到2号线程获得队列锁,判断发现队列仍为空,1号线程“偷走了”这个元素,所以对于2号线程而言,这次唤醒就是“虚假”的,它需要再次等待队列非空。 185 | 186 | 187 | 188 | ## 五、小结 189 | 190 | 通过线程池来高效的处理`Http`请求,**子线程**向由**请求队列**(双向链表实现)读取请求时候需要保证线程同步问题---**上锁**。 191 | 192 | 如何响应收到的HTTP请求呢?请看下文! -------------------------------------------------------------------------------- /doc/(3)高效响应HTTP请求.md: -------------------------------------------------------------------------------- 1 | # 一、前言 2 | 3 | 在逻辑处理模块中,响应HTTP请求采用主从状态机来完成。 4 | 5 | 传统的控制流程都是按照顺序执行的,状态机能处理任意顺序的事件,并能提供有意义的响应---即使这些时间发生的顺序和预计的不同。 6 | 7 | # 二、如何响应收到HTTP请求的报文 8 | 9 | ## **(1) http连接请求处理** 10 | 11 | 在启动服务器时,先创建好线程池。当浏览器端发出http连接请求,主线程创建http类对象数组用来接收请求并将所有数据读入各个对象对应buffer,然后将该对象插入任务队列;如果是连接请求,那么就将他注册到内核事件表中(通过静态成员变量完成)。线程池中的工作线程从任务队列中取出一个任务进行处理(解析请求报文)。 12 | 13 | ## (2) **http响应报文处理流程** 14 | 15 | 当上述报文解析完成后,服务器子线程调用process_write完成响应报文,响应报文包括 16 | 17 | 1.状态行:http/1.1 状态码 状态消息; 18 | 19 | 2.消息报头,内部调用add_content_length和add_linger函数 20 | 21 | l content-length记录响应报文长度,用于浏览器端判断服务器是否发送完数据 22 | 23 | l connection记录连接状态,用于告诉浏览器端保持长连接 24 | 25 | 3.空行 26 | 27 | 随后注册epollout事件。服务器主线程检测写事件,并调用http_conn::write函数将响应报文发送给浏览器端。至此整个http请求和响应全部完成。 28 | 29 | ## (3) **GET和POST的区别** 30 | 31 | - 最直观的区别就是GET把参数包含在URL中,POST通过request body传递参数。 32 | - GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。 33 | - GET请求在URL中传送的参数是有长度限制。(大多数)浏览器通常都会限制url长度在2K个字节,而(大多数)服务器最多处理64K大小的url。 34 | - GET产生一个TCP数据包;POST产生两个TCP数据包。对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);而对于POST,浏览器先发送header,服务器响应100(指示信息—表示请求已接收,继续处理)continue,浏览器再发送data,服务器响应200 ok(返回数据)。 35 | 36 | ## (4) HTTP 状态码 37 | 38 | - 1xx:指示信息--表示请求已接收,继续处理。 39 | - 2xx:成功--表示请求正常处理完毕。 40 | - 200 OK:客户端请求被正常处理。 41 | - 206 Partial content:客户端进行了范围请求。 42 | - 3xx:重定向--要完成请求必须进行更进一步的操作。 43 | - 301 Moved Permanently:永久重定向,该资源已被永久移动到新位置,将来任何对该资源的访问都要使用本响应返回的若干个URI之一。 44 | - 302 Found:临时重定向,请求的资源现在临时从不同的URI中获得。 45 | - 4xx:客户端错误--请求有语法错误,服务器无法处理请求。 46 | - 400 Bad Request:请求报文存在语法错误。 47 | - 403 Forbidden:请求被服务器拒绝。 48 | - 404 Not Found:请求不存在,服务器上找不到请求的资源。 49 | - 5xx:服务器端错误--服务器处理请求出错。 50 | - 500 Internal Server Error:服务器在执行请求时出现错误。 51 | 52 | 53 | 54 | 55 | 56 | ## **(5) 主从状态机的模式** 57 | 58 | ### 1 为什么要用状态机? 59 | 60 | 传统的控制流程都是按照顺序执行的,状态机能处理任意顺序的事件,并能提供有意义的响应---即使这些时间发生的顺序和预计的不同。 61 | 62 | 项目中使用**主从状态机**的模式进行解析,从状态机(`parse_line`)负责读取报文的一行,主状态机负责对该行数据进行解析,主状态机内部调用从状态机,从状态机驱动主状态机。每解析一部分都会将整个请求的`m_check_state`状态改变,状态机也就是根据这个状态来进行不同部分的解析跳转的: 63 | 64 | > 以下图片来源于《两猿社》~ 非常棒的服务器讲解! 65 | 66 | ![图片](https://ydlin.oss-cn-guangzhou.aliyuncs.com/blog-img/640.webp) 67 | 68 | #### **主状态机** 69 | 70 | 三种状态,标识解析位置。 71 | 72 | - CHECK_STATE_REQUESTLINE,解析请求行 73 | - CHECK_STATE_HEADER,解析请求头 74 | - CHECK_STATE_CONTENT,解析消息体,仅用于解析POST请求 75 | 76 | #### **从状态机** 77 | 78 | 三种状态,标识解析一行的读取状态。 79 | 80 | - LINE_OK,完整读取一行 81 | - LINE_BAD,报文语法有误 82 | - LINE_OPEN,读取的行不完整 83 | 84 | 85 | 86 | 87 | 88 | ```cpp 89 | void http_conn::process() { 90 | HTTP_CODE read_ret = process_read(); 91 | if(read_ret == NO_REQUEST) { 92 | modfd(m_epollfd, m_sockfd, EPOLLIN); 93 | return; 94 | } 95 | bool write_ret = process_write(read_ret); 96 | if(!write_ret) 97 | close_conn(); 98 | modfd(m_epollfd, m_sockfd, EPOLLOUT); 99 | } 100 | ``` 101 | 102 | HTTP请求报文:请求行(request line)、请求头部(header)、空行和请求数据 103 | 104 | 响应报文:状态行、消息报头、空行和响应正文。 105 | 106 | ### 2 有没有想过状态机会给项目带来哪些危害? 107 | 108 | 缺点:状态机的缺点就是性能比较低,一般一个状态做一个事情,性能比较差,在追求高性能的场景下一般不用,高性能场景一般使用流水线设计。 109 | 110 | ### 3 你的项目http请求怎么做的?如何保证http请求完整解析 111 | 112 | 该项目使用线程池(半同步半反应堆模式)并发处理用户请求,主线程负责读写,工作线程(线程池中的线程)负责处理逻辑(HTTP请求报文的解析等等) 113 | 114 | 主从状态机可以保证完整解析。 115 | 116 | 如何响应 117 | 118 | ![图片](https://mmbiz.qpic.cn/mmbiz_jpg/6OkibcrXVmBG9ibQZ4SgllXZqrkObpUHNKNoh8SsGMyOSGIgaE8nZdGhYua3E84VojicmKuJoict9s3ibraK6Lux1dQ/640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1&wx_co=1) 119 | 120 | 121 | 122 | ## 小结 123 | 124 | HTTP请求处理中主从状态机的思想非常有趣,简化了项目处理流程。数据库连接池是如何运行的,我们将放在下一章进行讲解。 125 | 126 | 127 | 128 | # 四、参考资料: 129 | 130 | - 《两猿社》 131 | - 《Linux高性能服务器》 -------------------------------------------------------------------------------- /doc/(4)数据库连接池与注册登录.md: -------------------------------------------------------------------------------- 1 | # 一、前言 2 | 3 | 这一章的主要讲解数据库连接池是如何实现的,以及登录注册的逻辑处理。 4 | 5 | # 二、数据库连接池是如何运行的 6 | 7 | 在处理用户注册,登录请求的时候,我们需要将这些用户的用户名和密码保存下载用于新用户的注册以及老用户的登录校验 8 | 9 | 若每次用户请求我们都需要新建一个数据库连接,请求结束后我们释放该数据库连接,当**`用户连接`**过多时,这种做法过于低效,所以类似**线程池**的做法,我们构建一个数据库连接池,预先生成一些数据库连接放在那里供用户请求使用。 10 | 11 | 作线程从数据库连接池取得一个连接,访问数据库中的数据,访问完毕后将连接交还连接池。 12 | 13 | ## 2.1 单个数据库连接是如何生成的 14 | 15 | ``` 16 | 使用mysql_init()初始化连接 17 | 使用mysql_real_connect()建立一个到mysql数据库的连接 18 | 使用mysql_query()执行查询语句 19 | 使用result = mysql_store_result(mysql)获取结果集 20 | 使用mysql_num_fields(result)获取查询的列数,mysql_num_rows(result)获取结果集的行数 21 | 通过mysql_fetch_row(result)不断获取下一行,然后循环输出 22 | 使用mysql_free_result(result)释放结果集所占内存 23 | 使用mysql_close(conn)关闭连接 24 | ``` 25 | 26 | ## 2.2 连接池实现的细节 27 | 28 | 对于一个数据库连接池来讲,就是预先生成多个这样的数据库连接,**然后放在一个链表中**,同时维护最大连接数`MAX_CONN`,当前可用连接数`FREE_CONN`和当前已用连接数`CUR_CONN`这三个变量。同样注意在对连接池操作时(获取,释放),要用到锁机制,因为它被所有线程共享。 29 | 30 | ### **(1) 连接池的实现:** 31 | 32 | 初始化,获取连接、释放连接,销毁连接池。 33 | 34 | 将数据库连接的获取与释放通过RAII机制封装,避免手动释放。 35 | 36 | 使用信号量实现多线程争夺连接的同步机制,这里将信号量初始化为数据库的连接总数。(实验中设置的数量量为8,在main.c 中设计) 37 | 38 | ### (2) 获取与释放连接 39 | 40 | 当线程数量大于数据库连接数量时,使用信号量进行同步,每次取出连接,信号量原子减1,释放连接原子加1,若连接池内没有连接了,则阻塞等待。另外,由于多线程操作连接池,会造成竞争,这里使用互斥锁完成同步。 41 | 42 | ### (3) 销毁连接 43 | 44 | 销毁的时候没有直接被外部调用,而是通过RAII机制来完成自动释放; 45 | 46 | 通过迭代器遍历连接池链表,关闭对应数据库连接,清空链表并重置空闲连接和现有连接数量。 47 | 48 | > RAII机制 49 | > 50 | > - RAII全称是“Resource Acquisition is Initialization”,直译过来是“资源获取即初始化”. 51 | > - RAII的核心思想是将资源或者状态与对象的生命周期绑定,通过C++的语言机制,实现资源和状态的安全管理,智能指针是RAII最好的例子 52 | > - 具体来说:构造函数的时候初始化获取资源,析构函数释放资源 53 | 54 | ## 2.3 大数据访问优化 55 | 56 | 登录中的用户名和密码你是load到本地,然后使用map匹配的,如果有10亿数据,即使load到本地后hash,也是很耗时的,你要怎么优化? 57 | 58 | > 数据查询的优化:保证在实现功能的基础上,**尽量减少对数据库的访问次数**;通过搜索参数,尽量减少对表的访问行数,最小化结果集,从而减轻网络负担;能够分开的操作尽量分开处理,提高每次的响应速度;在数据窗口使用SQL时,尽量把使用的索引放在选择的首列;算法的结构尽量简单; 59 | 60 | 61 | 62 | ## 三、 登录与注册 63 | 64 | 使用数据库连接池实现服务器访问数据库的功能,使用`POST`请求完成注册和登录的校验工作。 65 | 66 | 分为四部分内容 67 | 68 | 1. **载入数据库表:** 69 | 70 | 将数据库总的用户名和密码载入到服务器的map中来,map中的key为用户名,value为密码。 71 | 72 | 2. **提取用户名和密码** 73 | 74 | 提取用户名和密码,服务器端解析浏览器的请求报文,当解析为POST请求时,`CGI`标志位设置为1,并将请求报文的消息体赋值给m_string,进而提取出用户名和密码。 75 | 76 | 3. **同步线程登录和注册** 77 | 78 | 通过`m_url`定位/所在位置,根据/后的第一个字符判断是登录还是注册校验。 79 | 80 | - 2 --- 登录校验 81 | - 3 --- 注册校验 82 | 83 | 4. **页面跳转** 84 | 85 | 通过`m_url`定位/所在位置,根据/后的第一个字符,使用分支语句实现页面跳转。具体的, 86 | 87 | - 0 --- 跳转注册页面,GET 88 | - 1 --- 跳转登录页面,GET 89 | - 5 --- 显示图片页面,POST 90 | - 6 --- 显示视频页面,POST 91 | - 7 --- 显示关注页面,POST 92 | 93 | ### 6.1 各页面请求跳转的流程 94 | 95 | ![图片](https://mmbiz.qpic.cn/mmbiz_jpg/6OkibcrXVmBF79BLANEZ6cQoucgxyIz8B0Mz7VGZVTv4MpQC7pLL2bZiaic7sAVz2lhyk8ibL95apWmSE8AfGxAx6A/640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1&wx_co=1) 96 | 97 | ### 6.2 载入数据库 98 | 99 | 将数据库中的用户名和密码载入到服务器的map中来,map中的key为用户名,value为密码。 100 | 101 | ### 6.3 提取用户名和密码 102 | 103 | 服务器端解析浏览器的请求报文,当解析为POST请求时,cgi标志位设置为1,并将请求报文的消息体赋值给m_string,进而提取出用户名和密码。 104 | 105 | ### 6.4 同步线程登录 106 | 107 | 通过m_url定位/所在的位置,判断是登录还是注册校验 108 | 109 | 2 登录校验 110 | 111 | 3 注册校验 112 | 113 | 对数据库的操作需要通过锁来同步。 114 | 115 | ### 6.5 大数据量优化问题 116 | 117 | **登录中的用户名和密码你是load到本地,然后使用map匹配的,如果有10亿数据,即使l0ad到本地后hash,也是很耗时的,你要怎么优化?** 118 | 119 | 1.数据结构的优化:为了保证数据库的一致性和完整性,在逻辑设计的时候往往会设计过多的表间关联,尽可能的降低数据的冗余。 120 | 121 | 2.数据查询的优化:保证在实现功能的基础上,尽量减少对数据库的访问次数;通过搜索参数,尽量减少对表的访问行数,最小化结果集,从而减轻网络负担;能够分开的操作尽量分开处理,提高每次的响应速度;在数据窗口使用SQL时,尽量把使用的索引放在选择的首列;算法的结构尽量简单; 122 | 123 | 3.对算法那的优化:尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。.使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。 124 | 125 | 4.建立高效的索引: 126 | 127 | ## 四、小结 128 | 129 | 数据库的连接池的设计和线程池的线程池的设计的目的是一样,**减少开销**,虽然系统的数据量远远达不到大数据量,但是大数据量对于服务器性能考察是非常重要的,因此设计上应该花点功夫想清楚问题!下一章重点讲解日志系统实现。 130 | 131 | ## 五、参考资料 132 | 133 | - 《两猿社》 134 | - 《Linux高性能服务器》 -------------------------------------------------------------------------------- /doc/(5)同步异步日志系统.md: -------------------------------------------------------------------------------- 1 | # 一、前言 2 | 3 | 对于任何一个服务器而言,日志系统的设计是非常重要的,尝试设计一个简易的同步异步日志系统来完成系统日志的记录。 4 | 5 | ![image-20220601211113494](https://ydlin.oss-cn-guangzhou.aliyuncs.com/blog-img/image-20220601211113494.png) 6 | 7 | # 二、基础知识 8 | 9 | `日志`,由服务器自动创建,并记录运行状态,错误信息,访问数据的文件。 10 | 11 | **`同步日志`**,日志写入函数与工作线程串行执行,由于涉及到I/O操作,当单条日志比较大的时候,同步模式会阻塞整个处理流程,服务器所能处理的并发能力将有所下降,尤其是在峰值的时候,写日志可能成为系统的瓶颈。 12 | 13 | **`生产者-消费者模型`**,并发编程中的经典模型。以多线程为例,为了实现线程间数据同步,生产者线程与消费者线程共享一个缓冲区,其中**生产者**线程往**缓冲区**中push消息,**消费者**线程从缓冲区中pop消息。 14 | 15 | 任何时刻,**只能有一个**生产者或消费者可以访问缓冲区 16 | 17 | **`阻塞队列`**,将生产者-消费者模型进行封装,使用循环数组实现队列,作为两者共享的缓冲区。 18 | 19 | push成员是生产者,pop成员是消费者。 20 | 21 | **`异步日志`**,将所写的日志内容先存入阻塞队列,写线程从阻塞队列中取出内容,写入日志。 22 | 23 | **`单例模式`**,最简单也是被问到最多的设计模式之一,保证一个类只创建一个实例,同时提供全局访问的方法 24 | 25 | # 三、 单例模式 26 | 27 | 保证一个类仅有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。 28 | 29 | > 单例模式有两种实现方法,分别是懒汉和饿汉模式。顾名思义,懒汉模式,即非常懒,不用的时候不去初始化,所以在第一次被使用时才进行初始化;饿汉模式,即迫不及待,在程序运行时立即初始化。 30 | 31 | 实现思路:私有化它的构造函数,以防止外界创建单例类的对象;使用类的私有静态指针变量指向类的唯一实例,并用一个公有的静态方法获取该实例. 32 | 33 | ![image-20220323155623102](https://ydlin.oss-cn-guangzhou.aliyuncs.com/blog-img/image-20220323155623102.png) 34 | 35 | ## 1 **`为什么要用双检测,只检测一次不行吗?`** 36 | 37 | 如果只检测一次,在每次调用获取实例的方法时,都需要加锁,这将严重影响程序性能。双层检测可以有效避免这种情况,仅在第一次创建单例的时候加锁,其他时候都不再符合NULL == p的情况,直接返回已创建好的实例。 38 | 39 | ## 2 **局部静态变量之线程安全懒汉模式** 40 | 41 | 前面的双检测锁模式,写起来不太优雅,《Effective C++》(Item 04)中的提出另一种更优雅的单例模式实现,使用函数内的局部静态对象,这种方法不用加锁和解锁操作。 42 | 43 | ![image-20220323155743728](https://ydlin.oss-cn-guangzhou.aliyuncs.com/blog-img/image-20220323155743728.png) 44 | 45 | ## 3 **`为什么要把调用线程放入条件变量的请求队列后再解锁?`** 46 | 47 | 线程是并发执行的,如果在把调用线程A放在等待队列之前,就释放了互斥锁,这就意味着其他线程比如线程B可以获得互斥锁去访问公有资源,这时候线程A所等待的条件改变了,但是它没有被放在等待队列上,导致A忽略了等待条件被满足的信号。 48 | 49 | 倘若在线程A调用pthread_cond_wait开始,到把A放在等待队列的过程中,都持有互斥锁,其他线程无法得到互斥锁,就不能改变公有资源。 50 | 51 | # 四、**日志系统的运行机制** 52 | 53 | 步骤: 54 | 55 | 1:单例模式(局部静态变量懒汉方法)获取实例 56 | 57 | **2:主程序一开始Log::get_instance()->init()初始化实例。初始化后:服务器启动按当前时刻创建日志(**前缀为时间,后缀为自定义log文件名,并记录创建日志的时间day和行数count)**。如果是异步**(通过是否设置队列大小判断是否异步,0为同步)**,工作线程将要写的内容放进阻塞队列,还创建了写线程用于在阻塞队列里取出一个内容(指针),写入日志。** 58 | 59 | 3:其他功能模块调用write_log()函数写日志。(write_log:实现日志分级、分文件、按天分类,超行分类的格式化输出内容。)里面会根据异步、同步实现不同的写方式。 60 | 61 | - 日志文件 62 | - 局部变量的懒汉模式获取实例 63 | - 生成日志文件,并判断同步和异步写入方式 64 | - 同步 65 | - 判断是否分文件 66 | - 直接格式化输出内容,将信息写入日志文件 67 | - 异步 68 | - 判断是否分文件(通过队列的大小来决定) 69 | - 格式化输出内容,将内容写入阻塞队列,创建一个写线程,从阻塞队列取出内容写入日志文件 70 | 71 | ![图片](https://mmbiz.qpic.cn/mmbiz_jpg/6OkibcrXVmBEOjicsa8vpoLAlODicrC7AoM1h2eq9sDMdQY8TNYQoVckCRDd0m8SDH1myuB4gEJfejvznfZuJ3cpQ/640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1&wx_co=1) 72 | 73 | 同步和异步日志的处理代码 74 | 75 | ```cpp 76 | // 若异步,则将日志信息加入阻塞队列,同步则加锁向文件中写 77 | if (m_is_async && !m_log_queue->full()) 78 | { 79 | m_log_queue->push(log_str); 80 | } 81 | else 82 | { 83 | m_mutex.lock(); 84 | fputs(log_str.c_str(), m_fp); 85 | m_mutex.unlock(); 86 | } 87 | ``` 88 | 89 | # 五、重点知识 90 | 91 | ## 1 同步异步日志是怎么实现的?(CVTE) 92 | 93 | 在C++编写服务器的时候,涉及到Io操作的时候,会阻塞整个线程,同步日志可能比较简单,但是异步日志的话就需要注意一下,我们将所写的内容存入阻塞队列,创建写线程从阻塞队列中读取出内容,写入日志。 94 | 95 | 将消费者和生产者模式封装成阻塞队列。 96 | 97 | ## 2 日志的分级和分文件 98 | 99 | - Debug,调试代码时的输出,在系统实际运行时,一般不使用。 100 | - Warn,这种警告与调试时终端的warning类似,同样是调试代码时使用。 101 | - Info,报告系统当前的状态,当前执行的流程或接收的信息等。 102 | - Erro,输出系统的错误信息 103 | 104 | 超行、按天分文件逻辑,具体的, 105 | 106 | - 日志写入前会判断当前day是否为创建日志的时间,行数是否超过最大行限制 107 | - - 若为创建日志时间,写入日志,否则按当前时间创建新log,更新创建时间和行数 108 | - 若行数超过最大行限制,在当前日志的末尾加count/max_lines为后缀创建新log 109 | 110 | ## 3 针对高并发情况下,写线程数量不足,如何处理 111 | 112 | > 之前被问到的一个很好的问题,现在还没有一个很好的解决办法,后期如果有新的思路,会补一补 113 | > 114 | > 如果大佬们有好的建议,非常期待你们的答疑,麻烦在下方评论区中解答,非常感谢! 115 | 116 | # 六、总结 117 | 118 | 同步异步日志系统,其中异步日志系统主要是解决单条日志过大造成的问题,日志系统设计模块的学习还需要不断的进行,这对于服务器开发者来说是非常重要的。 119 | 120 | # 七、参考资料 121 | 122 | - 《两猿社》 123 | - 《Linux高性能服务器》 124 | - [最新版Web服务器项目详解 - 09 日志系统(上)](https://mp.weixin.qq.com/s/IWAlPzVDkR2ZRI5iirEfCg) 125 | - [最新版Web服务器项目详解 - 10 日志系统(下)](https://mp.weixin.qq.com/s/f-ujwFyCe1LZa3EB561ehA) 126 | - [muduo第五章:高效的多线程日志](https://github.com/chenshuo/muduo) -------------------------------------------------------------------------------- /doc/(6)压力测试.md: -------------------------------------------------------------------------------- 1 | # 一、前言 2 | 3 | 到这一章,项目基本上已经介绍完了,如果有什么不懂或者项目的不足之处,欢迎在评论区下留言,非常感谢。项目进行了适当的压力测试以及改进的一些思考。 4 | 5 | # 二、压测 6 | 7 | **Webbench是什么,介绍一下原理** 8 | 父进程fork若干个子进程,每个子进程在用户要求时间或默认的时间内对目标web循环发出实际访问请求,父子进程通过管道进行通信,子进程通过管道写端向父进程传递在若干次请求访问完毕后记录到的总信息,父进程通过管道读端读取子进程发来的相关信息,子进程在时间到后结束,父进程在所有子进程退出后统计并给用户显示最后的测试结果,然后退出。 9 | 10 | ![在这里插入图片描述](https://ydlin.oss-cn-guangzhou.aliyuncs.com/blog-img/3b868291e2ee4e0aa067cbed2feee16a.png) 11 | 12 | 压力测试的参数: 13 | 14 | ```bash 15 | webbench -c 10500 -t 5 http://127.0.0.1 16 | ``` 17 | 18 | 客户端数量10500, 运行测试的时间。 19 | 20 | ```php 21 | webbench -c 1000 -t 60 http://192.168.80.157/phpinfo.php 22 | 每秒钟响应请求数:24525 pages/min,每秒钟传输数据量20794612 bytes/sec. 23 | 并发1000运行60秒后产生的TCP连接数12000多个,已经显示有87个连接failed了,说明超负荷了。 24 | ``` 25 | 26 | # 三、服务器的改进之处 27 | 28 | (1) 服务器定时器的设计 29 | 30 | 定时器建立在双向链表上的 31 | 32 | | 位置 | 添加 | 删除 | 33 | | ------------ | ---- | ---- | 34 | | 刚好在头节点 | O(1) | O(1) | 35 | | 刚好在尾节点 | O(n) | O(1) | 36 | | 平均 | O(n) | O(1) | 37 | 38 | **Notes** 39 | 40 | 添加在为节点时间复杂度为O(n),因为项目的逻辑是先从头遍历新定时器在链表的位置,如果位置恰好在最后,则插入的时间复杂度O(N) 41 | 42 | a.在双向链表的基础上优化: 43 | 44 | 添加在尾节点的时间复杂度可以优化:在添加新的定时器的时候,**除了检测新定时器是否在小于头节点定时器的时间外,再先检测新定时器是否在大于尾节点定时器的时间,都不符合再使用常规插入。** 45 | 46 | b.不使用双向链表,使用最小堆结构可以进行优化。 47 | 48 | **最小堆优化?说一下时间复杂度和工作原理** 49 | 50 | 时间复杂度: 51 | 52 | 添加:`O(lgn)` 53 | 54 | 删除:`O(1)` 55 | 56 | 工作原理: 57 | 58 | 将所有定时器中超时时间最小的一个定时器的超时值作为alarm函数的定时值。这样,一旦定时任务处理函数tick()被调用,超时时间最小的定时器必然到期,我们就可以在tick 函数中处理该定时器。然后,再次从剩余的定时器中找出超时时间最小的一个(堆),并将这段最小时间设置为下一次alarm函数的定时值。如此反复,就实现了较为精确的定时。 59 | 60 | 61 | 62 | # 四、其他问题 63 | 64 | ### 大文件传输问题 65 | 66 | 发送数据数据调用`writev` 67 | 68 | 小文件调用一次就可以将数据全部发送出去。 69 | 70 | 大文件,需要多次调用`writev`, 然而默认的函数调用不用自动偏移指针。 71 | 72 | > writev以顺序iov[0],iov[1]至iov[iovcnt-1]从缓冲区中聚集输出数据 73 | 74 | iov[0]为存储报文状态行的缓冲区,iov[1]指向资源文件指针。 75 | 76 | 修改: 77 | 78 | - 由于报文消息报头较小,第一次传输后,需要更新`m_iv[1]`.iov_base和iov_len,m_iv[0].iov_len置成0,只传输文件,不用传输响应消息头 79 | - 每次传输后**都要更新下次传输的文件起始位置和长度** 80 | 81 | 补: 82 | 83 | `write`和`writev` 的区别: 84 | 85 | 1. writev允许处理非连续的数据块。也就是说,缓冲区可以逐个单独分配,不用是一块连续的较大的地址空间。 86 | 2. writev 的I/O是“原子的”。例如,如果你执行一个writev操作,所有数据将在一个连续操作中被写入,不会被中断。 87 | 3. 如果使用write,则必须在以下两种情况下进行选择:使用memcpy(带来额外开销)将它们复制到一个内存块中,然后再执行一个write调用。 88 | 4. 进行三个独立的write调用(带来额外开销)。另外,来自其他进程的write调用可以分散在这些write之间(也就是整体上看不是原子操作)。 89 | 90 | # 五、小结 91 | 92 | 此项目是基于Linux的**轻量级多线程Web服务器**,应用层实现了一个简单的HTTP服务器,利用**多路IO复用**,可以同时监听多个请求,使用线程池处理请求,**使用模拟proactor模式,主线程负责监听**,监听有事件之后,从socket中循环读取数据,然后将读取到的数据封装成一个请求对象放入队列。睡眠在**请求队列上的工作线程被唤醒进行处理**,使用**状态机解析HTTP请求报文**,实现**同步/异步日志系统**,记录服务器运行状态,并**对系统进行了压力测试**。 93 | 94 | 项目难点: 95 | 96 | 1、如何提高服务器的并发能力 97 | 98 | 2、由于涉及到I/O操作,当单条日志比较大的时候,同步模式会阻塞整个处理流程 99 | 100 | 3、多线程并发的情况下,保证线程的同步。 101 | 102 | 到这一部分基本上讲完了。 103 | 104 | 项目的代码:https://github.com/YDLinStars/LinuxWebServer 105 | 106 | [传送门](https://github.com/YDLinStars/LinuxWebServer) 107 | 108 | ## 六、参考资料 109 | 110 | - TCP/IP网络编程 111 | - Linux高性能服务器编程---游双 112 | - 社长的[WebServer](https://github.com/qinguoyi/TinyWebServer) -------------------------------------------------------------------------------- /http/README.md: -------------------------------------------------------------------------------- 1 | 2 | http连接处理类 3 | =============== 4 | 根据状态转移,通过主从状态机封装了http连接类。其中,主状态机在内部调用从状态机,从状态机将处理状态和数据传给主状态机 5 | > * 客户端发出http连接请求 6 | > * 从状态机读取数据,更新自身状态和接收数据,传给主状态机 7 | > * 主状态机根据从状态机状态,更新自身状态,决定响应请求还是继续读取 8 | 9 | -------------------------------------------------------------------------------- /http/http_conn.h: -------------------------------------------------------------------------------- 1 | #ifndef HTTPCONNECTION_H 2 | #define HTTPCONNECTION_H 3 | #include 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 | /** 17 | * @description: 18 | * @param {*} 19 | * @return {*} 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include "../lock/locker.h" 29 | #include "../CGImysql/sql_connection_pool.h" 30 | class http_conn 31 | { 32 | public: 33 | static const int FILENAME_LEN = 200; 34 | static const int READ_BUFFER_SIZE = 2048; 35 | static const int WRITE_BUFFER_SIZE = 1024; 36 | // 报文的请求方法,本项目只会用到GET POST 37 | enum METHOD 38 | { 39 | GET = 0, 40 | POST, 41 | HEAD, 42 | PUT, 43 | DELETE, 44 | TRACE, 45 | OPTIONS, 46 | CONNECT, 47 | PATH 48 | }; 49 | enum HTTP_CODE 50 | { 51 | NO_REQUEST, 52 | GET_REQUEST, 53 | BAD_REQUEST, 54 | NO_RESOURCE, 55 | FORBIDDEN_REQUEST, 56 | FILE_REQUEST, 57 | INTERNAL_ERROR, 58 | CLOSED_CONNECTION 59 | }; 60 | // 主状态机的状态 61 | enum CHECK_STATE 62 | { 63 | CHECK_STATE_REQUESTLINE = 0, 64 | CHECK_STATE_HEADER, 65 | CHECK_STATE_CONTENT 66 | }; 67 | // 从状态机的状态 68 | enum LINE_STATUS 69 | { 70 | LINE_OK = 0, 71 | LINE_BAD, 72 | LINE_OPEN 73 | }; 74 | 75 | public: 76 | http_conn() {} 77 | ~http_conn() {} 78 | 79 | public: 80 | // 初始化套接字,函数内部调用私有函数init 81 | void init(int sockfd, const sockaddr_in &addr); 82 | void close_conn(bool real_close = true); 83 | void process(); 84 | // 读取浏览器端发来的全部数据 85 | bool read_once(); 86 | bool write(); 87 | sockaddr_in *get_address() 88 | { 89 | return &m_address; 90 | } 91 | // 同步线程初始化数据库读取表 92 | void initmysql_result(connection_pool *connPool); 93 | 94 | private: 95 | void init(); 96 | // 从m_read_buf读取,并处理请求报文 97 | HTTP_CODE process_read(); 98 | // 向m_write_buf写入响应请求报文数据 99 | bool process_write(HTTP_CODE ret); 100 | // 主状态机解析报文中的请求行数据 101 | HTTP_CODE parse_request_line(char *text); 102 | // 主状态机解析报文中的请求头数据 103 | HTTP_CODE parse_headers(char *text); 104 | // 主状态机解析报文中的请求体数据 105 | HTTP_CODE parse_content(char *text); 106 | // 生成响应的报文 107 | HTTP_CODE do_request(); 108 | 109 | // m_start_line 是已经解析的字符 110 | // get_line用于将指向偏移,指向未处理的字符 111 | char *get_line() { return m_read_buf + m_start_line; }; 112 | 113 | // 从状态机读取一行,分析是请求报文地哪一部分 114 | LINE_STATUS parse_line(); 115 | void unmap(); 116 | 117 | // 根据响应报文格式,生成对应8个部分,以下函数均由do_request调用 118 | bool add_response(const char *format, ...); 119 | bool add_content(const char *content); 120 | bool add_status_line(int status, const char *title); 121 | bool add_headers(int content_length); 122 | bool add_content_type(); 123 | bool add_content_length(int content_length); 124 | bool add_linger(); 125 | bool add_blank_line(); 126 | 127 | public: 128 | static int m_epollfd; 129 | static int m_user_count; 130 | MYSQL *mysql; 131 | 132 | private: 133 | int m_sockfd; 134 | sockaddr_in m_address; 135 | 136 | // 存储读取的请求报文数据 137 | char m_read_buf[READ_BUFFER_SIZE]; 138 | // 缓冲区中m_read_buf中数据的最后一个字节的下一个位置 139 | int m_read_idx; 140 | // m_read_buf读取位置 141 | int m_checked_idx; 142 | // m_read_buf 已经解析的字符个数 143 | int m_start_line; 144 | 145 | // 存储发出的响应报文数据 146 | char m_write_buf[WRITE_BUFFER_SIZE]; 147 | // 值示buffer中的长度 148 | int m_write_idx; 149 | 150 | // 主状态机的状态 151 | CHECK_STATE m_check_state; 152 | // 请求方法 153 | METHOD m_method; 154 | char m_real_file[FILENAME_LEN]; 155 | char *m_url; 156 | char *m_version; 157 | char *m_host; 158 | int m_content_length; 159 | bool m_linger; 160 | char *m_file_address; 161 | struct stat m_file_stat; 162 | struct iovec m_iv[2]; 163 | int m_iv_count; 164 | int cgi; //是否启用的POST 165 | char *m_string; //存储请求头数据 166 | int bytes_to_send; 167 | int bytes_have_send; 168 | }; 169 | 170 | #endif 171 | -------------------------------------------------------------------------------- /lock/README.md: -------------------------------------------------------------------------------- 1 | 2 | 线程同步机制包装类 3 | =============== 4 | 多线程同步,确保任一时刻只能有一个线程能进入关键代码段. 5 | > * 信号量 6 | > * 互斥锁 7 | > * 条件变量 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /lock/locker.h: -------------------------------------------------------------------------------- 1 | #ifndef LOCKER_H 2 | #define LOCKER_H 3 | 4 | #include 5 | #include 6 | #include 7 | /* 封装信号量的类*/ 8 | class sem 9 | { 10 | public: 11 | // 创建并初始化信号量 12 | sem() 13 | { 14 | if (sem_init(&m_sem, 0, 0) != 0) 15 | { 16 | // 如果构造函数没有返回值 17 | throw std::exception(); 18 | } 19 | } 20 | // 销毁信号量 21 | sem(int num) 22 | { 23 | if (sem_init(&m_sem, 0, num) != 0) 24 | { 25 | throw std::exception(); 26 | } 27 | } 28 | ~sem() 29 | { 30 | sem_destroy(&m_sem); 31 | } 32 | bool wait() 33 | { 34 | return sem_wait(&m_sem) == 0; 35 | } 36 | bool post() 37 | { 38 | return sem_post(&m_sem) == 0; 39 | } 40 | 41 | private: 42 | sem_t m_sem; 43 | }; 44 | class locker 45 | { 46 | public: 47 | locker() 48 | { 49 | if (pthread_mutex_init(&m_mutex, NULL) != 0) 50 | { 51 | throw std::exception(); 52 | } 53 | } 54 | ~locker() 55 | { 56 | pthread_mutex_destroy(&m_mutex); 57 | } 58 | bool lock() 59 | { 60 | return pthread_mutex_lock(&m_mutex) == 0; 61 | } 62 | bool unlock() 63 | { 64 | return pthread_mutex_unlock(&m_mutex) == 0; 65 | } 66 | pthread_mutex_t *get() 67 | { 68 | return &m_mutex; 69 | } 70 | 71 | private: 72 | pthread_mutex_t m_mutex; 73 | }; 74 | class cond 75 | { 76 | public: 77 | cond() 78 | { 79 | if (pthread_cond_init(&m_cond, NULL) != 0) 80 | { 81 | //pthread_mutex_destroy(&m_mutex); 82 | throw std::exception(); 83 | } 84 | } 85 | ~cond() 86 | { 87 | pthread_cond_destroy(&m_cond); 88 | } 89 | bool wait(pthread_mutex_t *m_mutex) 90 | { 91 | int ret = 0; 92 | //pthread_mutex_lock(&m_mutex); 93 | ret = pthread_cond_wait(&m_cond, m_mutex); 94 | //pthread_mutex_unlock(&m_mutex); 95 | return ret == 0; 96 | } 97 | bool timewait(pthread_mutex_t *m_mutex, struct timespec t) 98 | { 99 | int ret = 0; 100 | //pthread_mutex_lock(&m_mutex); 101 | ret = pthread_cond_timedwait(&m_cond, m_mutex, &t); 102 | //pthread_mutex_unlock(&m_mutex); 103 | return ret == 0; 104 | } 105 | bool signal() 106 | { 107 | return pthread_cond_signal(&m_cond) == 0; 108 | } 109 | bool broadcast() 110 | { 111 | return pthread_cond_broadcast(&m_cond) == 0; 112 | } 113 | 114 | private: 115 | //static pthread_mutex_t m_mutex; 116 | pthread_cond_t m_cond; 117 | }; 118 | #endif 119 | -------------------------------------------------------------------------------- /lock/locker_new.h: -------------------------------------------------------------------------------- 1 | #ifdef LOCKER_H 2 | #define LOCKER_H 3 | 4 | #include 5 | #include 6 | #include 7 | /*用RAII思想封装信号量*/ 8 | class sem{ 9 | public: 10 | // 创建并初始化信号量 11 | sem(){ 12 | if(sem_init(&m_sem, 0, 0) != 0){ 13 | // 如果构造函数没有返回值 14 | throw std::exception(); 15 | } 16 | } 17 | sem(int num){ 18 | if(sem_init(&m_sem, 0, num) != 0){ 19 | // 如果构造函数没有返回值 20 | throw std::exception(); 21 | } 22 | } 23 | ~sem(){ 24 | sem_destroy(&m_sem); 25 | } 26 | 27 | bool wait(){ 28 | //信号-1,为0阻塞 29 | return sem_wait(&m_sem) == 0; 30 | } 31 | 32 | bool post(){ 33 | // 大于0唤醒调用线程 34 | return sem_post(&m_sem) == 0; 35 | } 36 | 37 | private: 38 | sem_t m_sem; 39 | }; 40 | /*互斥锁,互斥锁*/ 41 | class locker{ 42 | public: 43 | locker(){ 44 | if(pthread_mutex_init(&m_mutex,NULL) != 0){ 45 | throw std::exception(); 46 | } 47 | } 48 | ~locker(){ 49 | pthread_mutex_destroy(&m_mutex); 50 | } 51 | bool lock(){ 52 | return pthread_mutex_lock(&m_mutex) == 0; 53 | } 54 | 55 | bool unlock(){ 56 | return pthread_mutex_unlock(&m_mutex) == 0; 57 | } 58 | pthread_mutex_t *get(){ 59 | return &m_mutex; 60 | } 61 | private: 62 | pthread_mutex_t m_mutex; 63 | } 64 | 65 | // 条件变量 用于同一个线程之间的 66 | class cond{ 67 | public: 68 | cond(){ 69 | if(pthread_cond_init(&m_cond,NULL) != 0){ 70 | throw std::exception(); 71 | } 72 | } 73 | ~cond(){ 74 | pthread_cond_destroy(&m_cond); 75 | } 76 | 77 | bool wait(pthread_mutex_t *m_mutex){ 78 | return pthread_cond_wait(&m_cond, m_mutex) == 0; 79 | } 80 | //??? 用来干么的 81 | bool timewait(pthread_mutex_t *m_mutex, struct timespec t){ 82 | return pthread_cond_timedwait(&m_cond, m_mutex, &t) == 0; 83 | } 84 | bool signal() 85 | { 86 | return pthread_cond_signal(&m_cond) == 0; 87 | } 88 | bool broadcast(){ 89 | return pthread_cond_broadcast(&m_cond) == 0; 90 | } 91 | 92 | private: 93 | pthread_cond_t m_cond; 94 | }; 95 | #endif -------------------------------------------------------------------------------- /log/README.md: -------------------------------------------------------------------------------- 1 | 2 | 同步/异步日志系统 3 | =============== 4 | 同步/异步日志系统主要涉及了两个模块,一个是日志模块,一个是阻塞队列模块,其中加入阻塞队列模块主要是解决异步写入日志做准备. 5 | > * 自定义阻塞队列 6 | > * 单例模式创建日志 7 | > * 同步日志 8 | > * 异步日志 9 | > * 实现按天、超行分类 10 | 11 | 12 | -------------------------------------------------------------------------------- /log/block_queue.h: -------------------------------------------------------------------------------- 1 | /************************************************************* 2 | *循环数组实现的阻塞队列,m_back = (m_back + 1) % m_max_size; 3 | *线程安全,每个操作前都要先加互斥锁,操作完后,再解锁 4 | **************************************************************/ 5 | 6 | #ifndef BLOCK_QUEUE_H 7 | #define BLOCK_QUEUE_H 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "../lock/locker.h" 14 | using namespace std; 15 | 16 | template 17 | class block_queue 18 | { 19 | public: 20 | block_queue(int max_size = 1000) 21 | { 22 | if (max_size <= 0) 23 | { 24 | exit(-1); 25 | } 26 | 27 | m_max_size = max_size; 28 | m_array = new T[max_size]; 29 | m_size = 0; 30 | m_front = -1; 31 | m_back = -1; 32 | } 33 | 34 | void clear() 35 | { 36 | m_mutex.lock(); 37 | m_size = 0; 38 | m_front = -1; 39 | m_back = -1; 40 | m_mutex.unlock(); 41 | } 42 | 43 | ~block_queue() 44 | { 45 | m_mutex.lock(); 46 | if (m_array != NULL) 47 | delete [] m_array; 48 | 49 | m_mutex.unlock(); 50 | } 51 | //判断队列是否满了 52 | bool full() 53 | { 54 | m_mutex.lock(); 55 | if (m_size >= m_max_size) 56 | { 57 | 58 | m_mutex.unlock(); 59 | return true; 60 | } 61 | m_mutex.unlock(); 62 | return false; 63 | } 64 | //判断队列是否为空 65 | bool empty() 66 | { 67 | m_mutex.lock(); 68 | if (0 == m_size) 69 | { 70 | m_mutex.unlock(); 71 | return true; 72 | } 73 | m_mutex.unlock(); 74 | return false; 75 | } 76 | //返回队首元素 77 | bool front(T &value) 78 | { 79 | m_mutex.lock(); 80 | if (0 == m_size) 81 | { 82 | m_mutex.unlock(); 83 | return false; 84 | } 85 | value = m_array[m_front]; 86 | m_mutex.unlock(); 87 | return true; 88 | } 89 | //返回队尾元素 90 | bool back(T &value) 91 | { 92 | m_mutex.lock(); 93 | if (0 == m_size) 94 | { 95 | m_mutex.unlock(); 96 | return false; 97 | } 98 | value = m_array[m_back]; 99 | m_mutex.unlock(); 100 | return true; 101 | } 102 | 103 | int size() 104 | { 105 | int tmp = 0; 106 | 107 | m_mutex.lock(); 108 | tmp = m_size; 109 | 110 | m_mutex.unlock(); 111 | return tmp; 112 | } 113 | 114 | int max_size() 115 | { 116 | int tmp = 0; 117 | 118 | m_mutex.lock(); 119 | tmp = m_max_size; 120 | 121 | m_mutex.unlock(); 122 | return tmp; 123 | } 124 | //往队列添加元素,需要将所有使用队列的线程先唤醒 125 | //当有元素push进队列,相当于生产者生产了一个元素 126 | //若当前没有线程等待条件变量,则唤醒无意义 127 | bool push(const T &item) 128 | { 129 | 130 | m_mutex.lock(); 131 | if (m_size >= m_max_size) 132 | { 133 | 134 | m_cond.broadcast(); 135 | m_mutex.unlock(); 136 | return false; 137 | } 138 | 139 | m_back = (m_back + 1) % m_max_size; 140 | m_array[m_back] = item; 141 | 142 | m_size++; 143 | 144 | m_cond.broadcast(); 145 | m_mutex.unlock(); 146 | return true; 147 | } 148 | //pop时,如果当前队列没有元素,将会等待条件变量 149 | bool pop(T &item) 150 | { 151 | 152 | m_mutex.lock(); 153 | while (m_size <= 0) 154 | { 155 | 156 | if (!m_cond.wait(m_mutex.get())) 157 | { 158 | m_mutex.unlock(); 159 | return false; 160 | } 161 | } 162 | 163 | m_front = (m_front + 1) % m_max_size; 164 | item = m_array[m_front]; 165 | m_size--; 166 | m_mutex.unlock(); 167 | return true; 168 | } 169 | 170 | //增加了超时处理 171 | bool pop(T &item, int ms_timeout) 172 | { 173 | struct timespec t = {0, 0}; 174 | struct timeval now = {0, 0}; 175 | gettimeofday(&now, NULL); 176 | m_mutex.lock(); 177 | if (m_size <= 0) 178 | { 179 | t.tv_sec = now.tv_sec + ms_timeout / 1000; 180 | t.tv_nsec = (ms_timeout % 1000) * 1000; 181 | if (!m_cond.timewait(m_mutex.get(), t)) 182 | { 183 | m_mutex.unlock(); 184 | return false; 185 | } 186 | } 187 | 188 | if (m_size <= 0) 189 | { 190 | m_mutex.unlock(); 191 | return false; 192 | } 193 | 194 | m_front = (m_front + 1) % m_max_size; 195 | item = m_array[m_front]; 196 | m_size--; 197 | m_mutex.unlock(); 198 | return true; 199 | } 200 | 201 | private: 202 | locker m_mutex; 203 | cond m_cond; 204 | 205 | T *m_array; 206 | int m_size; 207 | int m_max_size; 208 | int m_front; 209 | int m_back; 210 | }; 211 | 212 | #endif 213 | -------------------------------------------------------------------------------- /log/log.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "log.h" 6 | #include 7 | using namespace std; 8 | 9 | Log::Log() 10 | { 11 | m_count = 0; 12 | m_is_async = false; 13 | } 14 | 15 | Log::~Log() 16 | { 17 | if (m_fp != NULL) 18 | { 19 | fclose(m_fp); 20 | } 21 | } 22 | //异步需要设置阻塞队列的长度,同步不需要设置 23 | bool Log::init(const char *file_name, int log_buf_size, int split_lines, int max_queue_size) 24 | { 25 | //如果设置了max_queue_size,则设置为异步 26 | if (max_queue_size >= 1) 27 | { 28 | m_is_async = true; 29 | m_log_queue = new block_queue(max_queue_size); 30 | pthread_t tid; 31 | //flush_log_thread为回调函数,这里表示创建线程异步写日志 32 | pthread_create(&tid, NULL, flush_log_thread, NULL); 33 | } 34 | 35 | m_log_buf_size = log_buf_size; 36 | m_buf = new char[m_log_buf_size]; 37 | memset(m_buf, '\0', m_log_buf_size); 38 | m_split_lines = split_lines; 39 | 40 | time_t t = time(NULL); 41 | struct tm *sys_tm = localtime(&t); 42 | struct tm my_tm = *sys_tm; 43 | 44 | 45 | const char *p = strrchr(file_name, '/'); 46 | char log_full_name[256] = {0}; 47 | 48 | if (p == NULL) 49 | { 50 | 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); 51 | } 52 | else 53 | { 54 | strcpy(log_name, p + 1); 55 | strncpy(dir_name, file_name, p - file_name + 1); 56 | 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); 57 | } 58 | 59 | m_today = my_tm.tm_mday; 60 | 61 | m_fp = fopen(log_full_name, "a"); 62 | if (m_fp == NULL) 63 | { 64 | return false; 65 | } 66 | 67 | return true; 68 | } 69 | 70 | void Log::write_log(int level, const char *format, ...) 71 | { 72 | struct timeval now = {0, 0}; 73 | gettimeofday(&now, NULL); 74 | time_t t = now.tv_sec; 75 | struct tm *sys_tm = localtime(&t); 76 | struct tm my_tm = *sys_tm; 77 | char s[16] = {0}; 78 | switch (level) 79 | { 80 | case 0: 81 | strcpy(s, "[debug]:"); 82 | break; 83 | case 1: 84 | strcpy(s, "[info]:"); 85 | break; 86 | case 2: 87 | strcpy(s, "[warn]:"); 88 | break; 89 | case 3: 90 | strcpy(s, "[erro]:"); 91 | break; 92 | default: 93 | strcpy(s, "[info]:"); 94 | break; 95 | } 96 | //写入一个log,对m_count++, m_split_lines最大行数 97 | m_mutex.lock(); 98 | m_count++; 99 | 100 | if (m_today != my_tm.tm_mday || m_count % m_split_lines == 0) //everyday log 101 | { 102 | 103 | char new_log[256] = {0}; 104 | fflush(m_fp); 105 | fclose(m_fp); 106 | char tail[16] = {0}; 107 | 108 | snprintf(tail, 16, "%d_%02d_%02d_", my_tm.tm_year + 1900, my_tm.tm_mon + 1, my_tm.tm_mday); 109 | 110 | if (m_today != my_tm.tm_mday) 111 | { 112 | snprintf(new_log, 255, "%s%s%s", dir_name, tail, log_name); 113 | m_today = my_tm.tm_mday; 114 | m_count = 0; 115 | } 116 | else 117 | { 118 | snprintf(new_log, 255, "%s%s%s.%lld", dir_name, tail, log_name, m_count / m_split_lines); 119 | } 120 | m_fp = fopen(new_log, "a"); 121 | } 122 | 123 | m_mutex.unlock(); 124 | 125 | va_list valst; 126 | va_start(valst, format); 127 | 128 | string log_str; 129 | m_mutex.lock(); 130 | 131 | //写入的具体时间内容格式 132 | int n = snprintf(m_buf, 48, "%d-%02d-%02d %02d:%02d:%02d.%06ld %s ", 133 | my_tm.tm_year + 1900, my_tm.tm_mon + 1, my_tm.tm_mday, 134 | my_tm.tm_hour, my_tm.tm_min, my_tm.tm_sec, now.tv_usec, s); 135 | 136 | int m = vsnprintf(m_buf + n, m_log_buf_size - 1, format, valst); 137 | m_buf[n + m] = '\n'; 138 | m_buf[n + m + 1] = '\0'; 139 | log_str = m_buf; 140 | 141 | m_mutex.unlock(); 142 | 143 | if (m_is_async && !m_log_queue->full()) 144 | { 145 | m_log_queue->push(log_str); 146 | } 147 | else 148 | { 149 | m_mutex.lock(); 150 | fputs(log_str.c_str(), m_fp); 151 | m_mutex.unlock(); 152 | } 153 | 154 | va_end(valst); 155 | } 156 | 157 | void Log::flush(void) 158 | { 159 | m_mutex.lock(); 160 | //强制刷新写入流缓冲区 161 | fflush(m_fp); 162 | m_mutex.unlock(); 163 | } 164 | -------------------------------------------------------------------------------- /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 | { 15 | public: 16 | //C++11以后,使用局部变量懒汉不用加锁 17 | static Log *get_instance() 18 | { 19 | static Log instance; 20 | return &instance; 21 | } 22 | 23 | static void *flush_log_thread(void *args) 24 | { 25 | Log::get_instance()->async_write_log(); 26 | } 27 | //可选择的参数有日志文件、日志缓冲区大小、最大行数以及最长日志条队列 28 | bool init(const char *file_name, int log_buf_size = 8192, int split_lines = 5000000, int max_queue_size = 0); 29 | 30 | void write_log(int level, const char *format, ...); 31 | 32 | void flush(void); 33 | 34 | private: 35 | Log(); 36 | virtual ~Log(); 37 | void *async_write_log() 38 | { 39 | string single_log; 40 | //从阻塞队列中取出一个日志string,写入文件 41 | while (m_log_queue->pop(single_log)) 42 | { 43 | m_mutex.lock(); 44 | fputs(single_log.c_str(), m_fp); 45 | m_mutex.unlock(); 46 | } 47 | } 48 | 49 | private: 50 | char dir_name[128]; //路径名 51 | char log_name[128]; //log文件名 52 | int m_split_lines; //日志最大行数 53 | int m_log_buf_size; //日志缓冲区大小 54 | long long m_count; //日志行数记录 55 | int m_today; //因为按天分类,记录当前时间是那一天 56 | FILE *m_fp; //打开log的文件指针 57 | char *m_buf; 58 | block_queue *m_log_queue; //阻塞队列 59 | bool m_is_async; //是否同步标志位 60 | locker m_mutex; 61 | }; 62 | 63 | 64 | #define LOG_DEBUG(format, ...) Log::get_instance()->write_log(0, format, ##__VA_ARGS__) 65 | #define LOG_INFO(format, ...) Log::get_instance()->write_log(1, format, ##__VA_ARGS__) 66 | #define LOG_WARN(format, ...) Log::get_instance()->write_log(2, format, ##__VA_ARGS__) 67 | #define LOG_ERROR(format, ...) Log::get_instance()->write_log(3, format, ##__VA_ARGS__) 68 | 69 | #endif 70 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "./lock/locker.h" 13 | #include "./threadpool/threadpool.h" 14 | #include "./timer/lst_timer.h" 15 | #include "./http/http_conn.h" 16 | #include "./log/log.h" 17 | #include "./CGImysql/sql_connection_pool.h" 18 | 19 | #define MAX_FD 65536 //最大文件描述符 20 | #define MAX_EVENT_NUMBER 10000 //最大事件数 21 | #define TIMESLOT 5 //最小超时单位 22 | 23 | #define SYNLOG //同步写日志 24 | //#define ASYNLOG //异步写日志 25 | 26 | //#define listenfdET //边缘触发非阻塞 27 | #define listenfdLT //水平触发阻塞 28 | 29 | //这三个函数在http_conn.cpp中定义,改变链接属性 30 | extern int addfd(int epollfd, int fd, bool one_shot); 31 | extern int remove(int epollfd, int fd); 32 | extern int setnonblocking(int fd); 33 | 34 | //设置定时器相关参数 35 | static int pipefd[2]; 36 | static sort_timer_lst timer_lst; 37 | static int epollfd = 0; 38 | 39 | //信号处理函数 40 | void sig_handler(int sig) 41 | { 42 | //为保证函数的可重入性,保留原来的errno 43 | int save_errno = errno; 44 | int msg = sig; 45 | send(pipefd[1], (char *)&msg, 1, 0); 46 | errno = save_errno; 47 | } 48 | 49 | //设置信号函数 50 | void addsig(int sig, void(handler)(int), bool restart = true) 51 | { 52 | struct sigaction sa; 53 | memset(&sa, '\0', sizeof(sa)); 54 | sa.sa_handler = handler; 55 | if (restart) 56 | sa.sa_flags |= SA_RESTART; 57 | sigfillset(&sa.sa_mask); 58 | assert(sigaction(sig, &sa, NULL) != -1); 59 | } 60 | 61 | 62 | void show_error(int connfd, const char *info) 63 | { 64 | printf("%s", info); 65 | send(connfd, info, strlen(info), 0); 66 | close(connfd); 67 | } 68 | 69 | 70 | //定时处理任务,重新定时以不断触发SIGALRM信号 71 | void timer_handler() 72 | { 73 | timer_lst.tick(); 74 | alarm(TIMESLOT); 75 | } 76 | 77 | //定时器回调函数,删除非活动连接在socket上的注册事件,并关闭 78 | void cb_func(client_data *user_data) 79 | { 80 | epoll_ctl(epollfd, EPOLL_CTL_DEL, user_data->sockfd, 0); 81 | assert(user_data); 82 | close(user_data->sockfd); 83 | http_conn::m_user_count--; 84 | LOG_INFO("close fd %d", user_data->sockfd); 85 | Log::get_instance()->flush(); 86 | } 87 | 88 | 89 | int main(int argc, char *argv[]) 90 | { 91 | #ifdef ASYNLOG 92 | Log::get_instance()->init("ServerLog", 2000, 800000, 8); //异步日志模型 93 | #endif 94 | 95 | #ifdef SYNLOG 96 | Log::get_instance()->init("ServerLog", 2000, 800000, 0); //同步日志模型 97 | #endif 98 | 99 | if (argc <= 1) 100 | { 101 | printf("usage: %s ip_address port_number\n", basename(argv[0])); 102 | return 1; 103 | } 104 | 105 | int port = atoi(argv[1]); 106 | // 忽略SIGPIPE信号 107 | addsig(SIGPIPE, SIG_IGN); 108 | 109 | //创建数据库连接池 110 | connection_pool *connPool = connection_pool::GetInstance(); 111 | connPool->init("localhost", "root", "root", "webdb", 3306, 8); 112 | 113 | //创建线程池 114 | threadpool *pool = NULL; 115 | try 116 | { 117 | pool = new threadpool(connPool); 118 | } 119 | catch (...) 120 | { 121 | return 1; 122 | } 123 | 124 | http_conn *users = new http_conn[MAX_FD]; 125 | assert(users); 126 | 127 | //初始化数据库读取表 128 | users->initmysql_result(connPool); 129 | 130 | int listenfd = socket(PF_INET, SOCK_STREAM, 0); 131 | assert(listenfd >= 0); 132 | 133 | //struct linger tmp={1,0}; 134 | //SO_LINGER若有数据待发送,延迟关闭 135 | //setsockopt(listenfd,SOL_SOCKET,SO_LINGER,&tmp,sizeof(tmp)); 136 | 137 | int ret = 0; 138 | struct sockaddr_in address; 139 | bzero(&address, sizeof(address)); 140 | address.sin_family = AF_INET; 141 | address.sin_addr.s_addr = htonl(INADDR_ANY); 142 | address.sin_port = htons(port); 143 | 144 | int flag = 1; 145 | setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)); 146 | ret = bind(listenfd, (struct sockaddr *)&address, sizeof(address)); 147 | assert(ret >= 0); 148 | ret = listen(listenfd, 5); 149 | assert(ret >= 0); 150 | 151 | //创建内核事件表 152 | epoll_event events[MAX_EVENT_NUMBER]; 153 | epollfd = epoll_create(5); 154 | assert(epollfd != -1); 155 | 156 | addfd(epollfd, listenfd, false); 157 | http_conn::m_epollfd = epollfd; 158 | 159 | //创建管道 160 | ret = socketpair(PF_UNIX, SOCK_STREAM, 0, pipefd); 161 | assert(ret != -1); 162 | setnonblocking(pipefd[1]); 163 | addfd(epollfd, pipefd[0], false); 164 | 165 | addsig(SIGALRM, sig_handler, false); 166 | addsig(SIGTERM, sig_handler, false); 167 | bool stop_server = false; 168 | 169 | client_data *users_timer = new client_data[MAX_FD]; 170 | 171 | bool timeout = false; 172 | alarm(TIMESLOT); 173 | 174 | while (!stop_server) 175 | { 176 | int number = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1); 177 | if (number < 0 && errno != EINTR) 178 | { 179 | LOG_ERROR("%s", "epoll failure"); 180 | break; 181 | } 182 | 183 | for (int i = 0; i < number; i++) 184 | { 185 | int sockfd = events[i].data.fd; 186 | 187 | //处理新到的客户连接 188 | if (sockfd == listenfd) 189 | { 190 | struct sockaddr_in client_address; 191 | socklen_t client_addrlength = sizeof(client_address); 192 | #ifdef listenfdLT 193 | int connfd = accept(listenfd, (struct sockaddr *)&client_address, &client_addrlength); 194 | if (connfd < 0) 195 | { 196 | LOG_ERROR("%s:errno is:%d", "accept error", errno); 197 | continue; 198 | } 199 | if (http_conn::m_user_count >= MAX_FD) 200 | { 201 | show_error(connfd, "Internal server busy"); 202 | LOG_ERROR("%s", "Internal server busy"); 203 | continue; 204 | } 205 | users[connfd].init(connfd, client_address); 206 | 207 | //初始化client_data数据 208 | //创建定时器,设置回调函数和超时时间,绑定用户数据,将定时器添加到链表中 209 | users_timer[connfd].address = client_address; 210 | users_timer[connfd].sockfd = connfd; 211 | util_timer *timer = new util_timer; 212 | timer->user_data = &users_timer[connfd]; 213 | timer->cb_func = cb_func; 214 | time_t cur = time(NULL); 215 | timer->expire = cur + 3 * TIMESLOT; 216 | users_timer[connfd].timer = timer; 217 | timer_lst.add_timer(timer); 218 | #endif 219 | 220 | #ifdef listenfdET 221 | while (1) 222 | { 223 | int connfd = accept(listenfd, (struct sockaddr *)&client_address, &client_addrlength); 224 | if (connfd < 0) 225 | { 226 | LOG_ERROR("%s:errno is:%d", "accept error", errno); 227 | break; 228 | } 229 | if (http_conn::m_user_count >= MAX_FD) 230 | { 231 | show_error(connfd, "Internal server busy"); 232 | LOG_ERROR("%s", "Internal server busy"); 233 | break; 234 | } 235 | users[connfd].init(connfd, client_address); 236 | 237 | //初始化client_data数据 238 | //创建定时器,设置回调函数和超时时间,绑定用户数据,将定时器添加到链表中 239 | users_timer[connfd].address = client_address; 240 | users_timer[connfd].sockfd = connfd; 241 | util_timer *timer = new util_timer; 242 | timer->user_data = &users_timer[connfd]; 243 | timer->cb_func = cb_func; 244 | time_t cur = time(NULL); 245 | timer->expire = cur + 3 * TIMESLOT; 246 | users_timer[connfd].timer = timer; 247 | timer_lst.add_timer(timer); 248 | } 249 | continue; 250 | #endif 251 | } 252 | 253 | else if (events[i].events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)) 254 | { 255 | //服务器端关闭连接,移除对应的定时器 256 | util_timer *timer = users_timer[sockfd].timer; 257 | timer->cb_func(&users_timer[sockfd]); 258 | 259 | if (timer) 260 | { 261 | timer_lst.del_timer(timer); 262 | } 263 | } 264 | 265 | //处理信号 266 | else if ((sockfd == pipefd[0]) && (events[i].events & EPOLLIN)) 267 | { 268 | int sig; 269 | char signals[1024]; 270 | ret = recv(pipefd[0], signals, sizeof(signals), 0); 271 | if (ret == -1) 272 | { 273 | continue; 274 | } 275 | else if (ret == 0) 276 | { 277 | continue; 278 | } 279 | else 280 | { 281 | for (int i = 0; i < ret; ++i) 282 | { 283 | switch (signals[i]) 284 | { 285 | case SIGALRM: 286 | { 287 | timeout = true; 288 | break; 289 | } 290 | case SIGTERM: 291 | { 292 | stop_server = true; 293 | } 294 | } 295 | } 296 | } 297 | } 298 | 299 | //处理客户连接上接收到的数据 300 | else if (events[i].events & EPOLLIN) 301 | { 302 | util_timer *timer = users_timer[sockfd].timer; 303 | if (users[sockfd].read_once()) 304 | { 305 | LOG_INFO("deal with the client(%s)", inet_ntoa(users[sockfd].get_address()->sin_addr)); 306 | Log::get_instance()->flush(); 307 | //若监测到读事件,将该事件放入请求队列 308 | pool->append(users + sockfd); 309 | 310 | //若有数据传输,则将定时器往后延迟3个单位 311 | //并对新的定时器在链表上的位置进行调整 312 | if (timer) 313 | { 314 | time_t cur = time(NULL); 315 | timer->expire = cur + 3 * TIMESLOT; 316 | LOG_INFO("%s", "adjust timer once"); 317 | Log::get_instance()->flush(); 318 | timer_lst.adjust_timer(timer); 319 | } 320 | } 321 | else 322 | { 323 | timer->cb_func(&users_timer[sockfd]); 324 | if (timer) 325 | { 326 | timer_lst.del_timer(timer); 327 | } 328 | } 329 | } 330 | else if (events[i].events & EPOLLOUT) 331 | { 332 | util_timer *timer = users_timer[sockfd].timer; 333 | if (users[sockfd].write()) 334 | { 335 | LOG_INFO("send data to the client(%s)", inet_ntoa(users[sockfd].get_address()->sin_addr)); 336 | Log::get_instance()->flush(); 337 | 338 | //若有数据传输,则将定时器往后延迟3个单位 339 | //并对新的定时器在链表上的位置进行调整 340 | if (timer) 341 | { 342 | time_t cur = time(NULL); 343 | timer->expire = cur + 3 * TIMESLOT; 344 | LOG_INFO("%s", "adjust timer once"); 345 | Log::get_instance()->flush(); 346 | timer_lst.adjust_timer(timer); 347 | } 348 | } 349 | else 350 | { 351 | timer->cb_func(&users_timer[sockfd]); 352 | if (timer) 353 | { 354 | timer_lst.del_timer(timer); 355 | } 356 | } 357 | } 358 | } 359 | if (timeout) 360 | { 361 | timer_handler(); 362 | timeout = false; 363 | } 364 | } 365 | close(epollfd); 366 | close(listenfd); 367 | close(pipefd[1]); 368 | close(pipefd[0]); 369 | delete[] users; 370 | delete[] users_timer; 371 | delete pool; 372 | return 0; 373 | } 374 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | server: main.c ./threadpool/threadpool.h ./http/http_conn.cpp ./http/http_conn.h ./lock/locker.h ./log/log.cpp ./log/log.h ./log/block_queue.h ./CGImysql/sql_connection_pool.cpp ./CGImysql/sql_connection_pool.h 2 | g++ -o server main.c ./threadpool/threadpool.h ./http/http_conn.cpp ./http/http_conn.h ./lock/locker.h ./log/log.cpp ./log/log.h ./CGImysql/sql_connection_pool.cpp ./CGImysql/sql_connection_pool.h -lpthread -lmysqlclient 3 | 4 | 5 | clean: 6 | rm -r server 7 | -------------------------------------------------------------------------------- /root/carousel/01.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YDLinStars/LinuxWebServer/a080cb1d4ac8caee401332d8aa8b276e0ffc6a80/root/carousel/01.jpeg -------------------------------------------------------------------------------- /root/carousel/01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YDLinStars/LinuxWebServer/a080cb1d4ac8caee401332d8aa8b276e0ffc6a80/root/carousel/01.jpg -------------------------------------------------------------------------------- /root/carousel/02.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YDLinStars/LinuxWebServer/a080cb1d4ac8caee401332d8aa8b276e0ffc6a80/root/carousel/02.jpeg -------------------------------------------------------------------------------- /root/carousel/02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YDLinStars/LinuxWebServer/a080cb1d4ac8caee401332d8aa8b276e0ffc6a80/root/carousel/02.jpg -------------------------------------------------------------------------------- /root/carousel/03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YDLinStars/LinuxWebServer/a080cb1d4ac8caee401332d8aa8b276e0ffc6a80/root/carousel/03.jpg -------------------------------------------------------------------------------- /root/carousel/04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YDLinStars/LinuxWebServer/a080cb1d4ac8caee401332d8aa8b276e0ffc6a80/root/carousel/04.jpg -------------------------------------------------------------------------------- /root/carousel/LinuxWebService.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YDLinStars/LinuxWebServer/a080cb1d4ac8caee401332d8aa8b276e0ffc6a80/root/carousel/LinuxWebService.jpg -------------------------------------------------------------------------------- /root/carousel/xxx11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YDLinStars/LinuxWebServer/a080cb1d4ac8caee401332d8aa8b276e0ffc6a80/root/carousel/xxx11.jpg -------------------------------------------------------------------------------- /root/css/public.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 15px 15px 15px 15px; 3 | background: #f2f2f2; 4 | } 5 | 6 | .layuimini-container { 7 | border: 1px solid #f2f2f2; 8 | border-radius: 5px; 9 | background-color: #ffffff 10 | } 11 | 12 | .layuimini-main { 13 | margin: 10px 10px 10px 10px; 14 | } 15 | 16 | /**必填红点 */ 17 | .layuimini-form > .layui-form-item > .required:after { 18 | content: '*'; 19 | color: red; 20 | position: absolute; 21 | margin-left: 4px; 22 | font-weight: bold; 23 | line-height: 1.8em; 24 | top: 6px; 25 | right: 5px; 26 | } 27 | 28 | .layuimini-form > .layui-form-item > .layui-form-label { 29 | width: 120px !important; 30 | } 31 | 32 | .layuimini-form > .layui-form-item > .layui-input-block { 33 | margin-left: 150px !important; 34 | } 35 | 36 | .layuimini-form > .layui-form-item > .layui-input-block > tip { 37 | display: inline-block; 38 | margin-top: 10px; 39 | line-height: 10px; 40 | font-size: 10px; 41 | color: #a29c9c; 42 | } 43 | 44 | /**搜索框*/ 45 | .layuimini-container .table-search-fieldset { 46 | margin: 0; 47 | border: 1px solid #e6e6e6; 48 | padding: 10px 20px 5px 20px; 49 | color: #6b6b6b; 50 | } 51 | 52 | /**自定义滚动条样式 */ 53 | ::-webkit-scrollbar { 54 | width: 6px; 55 | height: 6px 56 | } 57 | 58 | ::-webkit-scrollbar-track { 59 | background-color: transparent; 60 | -webkit-border-radius: 2em; 61 | -moz-border-radius: 2em; 62 | border-radius: 2em; 63 | } 64 | 65 | ::-webkit-scrollbar-thumb { 66 | background-color: #9c9da0; 67 | -webkit-border-radius: 2em; 68 | -moz-border-radius: 2em; 69 | border-radius: 2em 70 | } 71 | -------------------------------------------------------------------------------- /root/css/themes/default.css: -------------------------------------------------------------------------------- 1 | /*头部右侧背景色 headerRightBg */ 2 | .layui-layout-admin .layui-header { 3 | background-color: #ffffff !important; 4 | } 5 | 6 | /*头部右侧选中背景色 headerRightBgThis */ 7 | .layui-layout-admin .layui-header .layuimini-header-content > ul > .layui-nav-item.layui-this, .layuimini-tool i:hover { 8 | background-color: #e4e4e4 !important; 9 | } 10 | 11 | /*头部右侧字体颜色 headerRightColor */ 12 | .layui-layout-admin .layui-header .layui-nav .layui-nav-item a { 13 | color: rgba(107, 107, 107, 0.7); 14 | } 15 | 16 | /**头部右侧下拉字体颜色 headerRightChildColor */ 17 | .layui-layout-admin .layui-header .layui-nav .layui-nav-item .layui-nav-child a { 18 | color: rgba(107, 107, 107, 0.7) !important; 19 | } 20 | 21 | /*头部右侧鼠标选中 headerRightColorThis */ 22 | .layui-header .layuimini-menu-header-pc.layui-nav .layui-nav-item a:hover, .layui-header .layuimini-header-menu.layuimini-pc-show.layui-nav .layui-this a { 23 | color: #565656 !important; 24 | } 25 | 26 | /*头部右侧更多下拉颜色 headerRightNavMore */ 27 | .layui-header .layui-nav .layui-nav-more { 28 | border-top-color: rgba(160, 160, 160, 0.7) !important; 29 | } 30 | 31 | /*头部右侧更多下拉颜色 headerRightNavMore */ 32 | .layui-header .layui-nav .layui-nav-mored, .layui-header .layui-nav-itemed > a .layui-nav-more { 33 | border-color: transparent transparent rgba(160, 160, 160, 0.7) !important; 34 | } 35 | 36 | /**头部右侧更多下拉配置色 headerRightNavMoreBg headerRightNavMoreColor */ 37 | .layui-header .layui-nav .layui-nav-child dd.layui-this a, .layui-header .layui-nav-child dd.layui-this, .layui-layout-admin .layui-header .layui-nav .layui-nav-item .layui-nav-child .layui-this a { 38 | background-color: #1E9FFF !important; 39 | color: #ffffff !important; 40 | } 41 | 42 | /*头部缩放按钮样式 headerRightToolColor */ 43 | .layui-layout-admin .layui-header .layuimini-tool i { 44 | color: #565656; 45 | } 46 | 47 | /*logo背景颜色 headerLogoBg */ 48 | .layui-layout-admin .layuimini-logo { 49 | background-color: #192027 !important; 50 | } 51 | 52 | /*logo字体颜色 headerLogoColor */ 53 | .layui-layout-admin .layuimini-logo h1 { 54 | color: rgb(191, 187, 187); 55 | } 56 | 57 | /*左侧菜单更多下拉样式 leftMenuNavMore */ 58 | .layuimini-menu-left .layui-nav .layui-nav-more, .layuimini-menu-left-zoom.layui-nav .layui-nav-more { 59 | border-top-color: rgb(191, 187, 187); 60 | } 61 | 62 | /*左侧菜单更多下拉样式 leftMenuNavMore */ 63 | .layuimini-menu-left .layui-nav .layui-nav-mored, .layuimini-menu-left .layui-nav-itemed > a .layui-nav-more, .layuimini-menu-left-zoom.layui-nav .layui-nav-mored, .layuimini-menu-left-zoom.layui-nav-itemed > a .layui-nav-more { 64 | border-color: transparent transparent rgb(191, 187, 187) !important; 65 | } 66 | 67 | /*左侧菜单背景 leftMenuBg */ 68 | .layui-side.layui-bg-black, .layui-side.layui-bg-black > .layuimini-menu-left > ul, .layuimini-menu-left-zoom > ul { 69 | background-color: #28333E !important; 70 | } 71 | 72 | /*左侧菜单选中背景 leftMenuBgThis */ 73 | .layuimini-menu-left .layui-nav-tree .layui-this, .layuimini-menu-left .layui-nav-tree .layui-this > a, .layuimini-menu-left .layui-nav-tree .layui-nav-child dd.layui-this, .layuimini-menu-left .layui-nav-tree .layui-nav-child dd.layui-this a, .layuimini-menu-left-zoom.layui-nav-tree .layui-this, .layuimini-menu-left-zoom.layui-nav-tree .layui-this > a, .layuimini-menu-left-zoom.layui-nav-tree .layui-nav-child dd.layui-this, .layuimini-menu-left-zoom.layui-nav-tree .layui-nav-child dd.layui-this a { 74 | background-color: #1E9FFF !important 75 | } 76 | 77 | /*左侧菜单子菜单背景 leftMenuChildBg */ 78 | .layuimini-menu-left .layui-nav-itemed > .layui-nav-child { 79 | background-color: #0c0f13 !important; 80 | } 81 | 82 | /*左侧菜单字体颜色 leftMenuColor */ 83 | .layuimini-menu-left .layui-nav .layui-nav-item a, .layuimini-menu-left-zoom.layui-nav .layui-nav-item a { 84 | color: rgb(191, 187, 187) !important; 85 | } 86 | 87 | /*左侧菜单选中字体颜色 leftMenuColorThis */ 88 | .layuimini-menu-left .layui-nav .layui-nav-item a:hover, .layuimini-menu-left .layui-nav .layui-this a, .layuimini-menu-left-zoom.layui-nav .layui-nav-item a:hover, .layuimini-menu-left-zoom.layui-nav .layui-this a { 89 | color: #ffffff !important; 90 | } 91 | 92 | /**tab选项卡选中颜色 tabActiveColor */ 93 | .layuimini-tab .layui-tab-title .layui-this .layuimini-tab-active { 94 | background-color: #1e9fff; 95 | } 96 | -------------------------------------------------------------------------------- /root/images/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YDLinStars/LinuxWebServer/a080cb1d4ac8caee401332d8aa8b276e0ffc6a80/root/images/bg.jpg -------------------------------------------------------------------------------- /root/images/bigVideo.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YDLinStars/LinuxWebServer/a080cb1d4ac8caee401332d8aa8b276e0ffc6a80/root/images/bigVideo.mp4 -------------------------------------------------------------------------------- /root/images/captcha.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YDLinStars/LinuxWebServer/a080cb1d4ac8caee401332d8aa8b276e0ffc6a80/root/images/captcha.jpg -------------------------------------------------------------------------------- /root/images/donate_qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YDLinStars/LinuxWebServer/a080cb1d4ac8caee401332d8aa8b276e0ffc6a80/root/images/donate_qrcode.png -------------------------------------------------------------------------------- /root/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YDLinStars/LinuxWebServer/a080cb1d4ac8caee401332d8aa8b276e0ffc6a80/root/images/favicon.ico -------------------------------------------------------------------------------- /root/images/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YDLinStars/LinuxWebServer/a080cb1d4ac8caee401332d8aa8b276e0ffc6a80/root/images/home.png -------------------------------------------------------------------------------- /root/images/icon-login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YDLinStars/LinuxWebServer/a080cb1d4ac8caee401332d8aa8b276e0ffc6a80/root/images/icon-login.png -------------------------------------------------------------------------------- /root/images/loginbg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YDLinStars/LinuxWebServer/a080cb1d4ac8caee401332d8aa8b276e0ffc6a80/root/images/loginbg.png -------------------------------------------------------------------------------- /root/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YDLinStars/LinuxWebServer/a080cb1d4ac8caee401332d8aa8b276e0ffc6a80/root/images/logo.png -------------------------------------------------------------------------------- /root/js/lay-config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * date:2019/08/16 3 | * author:Mr.Chung 4 | * description:此处放layui自定义扩展 5 | * version:2.0.4 6 | */ 7 | 8 | window.rootPath = (function (src) { 9 | src = document.scripts[document.scripts.length - 1].src; 10 | return src.substring(0, src.lastIndexOf("/") + 1); 11 | })(); 12 | 13 | layui.config({ 14 | base: rootPath + "lay-module/", 15 | version: true 16 | }).extend({ 17 | miniAdmin: "layuimini/miniAdmin", // layuimini后台扩展 18 | miniMenu: "layuimini/miniMenu", // layuimini菜单扩展 19 | miniTab: "layuimini/miniTab", // layuimini tab扩展 20 | miniTheme: "layuimini/miniTheme", // layuimini 主题扩展 21 | miniTongji: "layuimini/miniTongji", // layuimini 统计扩展 22 | step: 'step-lay/step', // 分步表单扩展 23 | treetable: 'treetable-lay/treetable', //table树形扩展 24 | tableSelect: 'tableSelect/tableSelect', // table选择扩展 25 | iconPickerFa: 'iconPicker/iconPickerFa', // fa图标选择扩展 26 | echarts: 'echarts/echarts', // echarts图表扩展 27 | echartsTheme: 'echarts/echartsTheme', // echarts图表主题扩展 28 | wangEditor: 'wangEditor/wangEditor', // wangEditor富文本扩展 29 | layarea: 'layarea/layarea', // 省市县区三级联动下拉选择器 30 | }); 31 | -------------------------------------------------------------------------------- /root/js/lay-module/layuimini/miniMenu.js: -------------------------------------------------------------------------------- 1 | /** 2 | * date:2020/02/27 3 | * author:Mr.Chung 4 | * version:2.0 5 | * description:layuimini 菜单框架扩展 6 | */ 7 | layui.define(["element","laytpl" ,"jquery"], function (exports) { 8 | var element = layui.element, 9 | $ = layui.$, 10 | laytpl = layui.laytpl, 11 | layer = layui.layer; 12 | 13 | var miniMenu = { 14 | 15 | /** 16 | * 菜单初始化 17 | * @param options.menuList 菜单数据信息 18 | * @param options.multiModule 是否开启多模块 19 | * @param options.menuChildOpen 是否展开子菜单 20 | */ 21 | render: function (options) { 22 | options.menuList = options.menuList || []; 23 | options.multiModule = options.multiModule || false; 24 | options.menuChildOpen = options.menuChildOpen || false; 25 | if (options.multiModule) { 26 | miniMenu.renderMultiModule(options.menuList, options.menuChildOpen); 27 | } else { 28 | miniMenu.renderSingleModule(options.menuList, options.menuChildOpen); 29 | } 30 | miniMenu.listen(); 31 | }, 32 | 33 | /** 34 | * 单模块 35 | * @param menuList 菜单数据 36 | * @param menuChildOpen 是否默认展开 37 | */ 38 | renderSingleModule: function (menuList, menuChildOpen) { 39 | menuList = menuList || []; 40 | var leftMenuHtml = '', 41 | childOpenClass = '', 42 | leftMenuCheckDefault = 'layui-this'; 43 | var me = this ; 44 | if (menuChildOpen) childOpenClass = ' layui-nav-itemed'; 45 | leftMenuHtml = this.renderLeftMenu(menuList,{ childOpenClass:childOpenClass }) ; 46 | $('.layui-layout-body').addClass('layuimini-single-module'); //单模块标识 47 | $('.layuimini-header-menu').remove(); 48 | $('.layuimini-menu-left').html(leftMenuHtml); 49 | 50 | element.init(); 51 | }, 52 | 53 | /** 54 | * 渲染一级菜单 55 | */ 56 | compileMenu: function(menu,isSub){ 57 | var menuHtml = '' ; 58 | if(isSub){ 59 | menuHtml = '' 60 | } 61 | return laytpl(menuHtml).render(menu); 62 | }, 63 | compileMenuContainer :function(menu,isSub){ 64 | var wrapperHtml = '
    {{d.children}}
' ; 65 | if(isSub){ 66 | wrapperHtml = '
{{d.children}}
' ; 67 | } 68 | if(!menu.children){ 69 | return ""; 70 | } 71 | return laytpl(wrapperHtml).render(menu); 72 | }, 73 | 74 | each:function(list,callback){ 75 | var _list = []; 76 | for(var i = 0 ,length = list.length ; i=0)) { 21 | miniTongji.listen(); 22 | } 23 | }, 24 | 25 | /** 26 | * 监听统计代码 27 | */ 28 | listen: function () { 29 | var _hmt = _hmt || []; 30 | (function () { 31 | var hm = document.createElement("script"); 32 | hm.src = "https://hm.baidu.com/hm.js?d97abf6d61c21d773f97835defbdef4e"; 33 | var s = document.getElementsByTagName("script")[0]; 34 | s.parentNode.insertBefore(hm, s); 35 | })(); 36 | } 37 | }; 38 | 39 | exports("miniTongji", miniTongji); 40 | }); -------------------------------------------------------------------------------- /root/js/lay-module/step-lay/step.css: -------------------------------------------------------------------------------- 1 | .lay-step { 2 | font-size: 0; 3 | width: 400px; 4 | margin: 0 auto; 5 | max-width: 100%; 6 | padding-left: 200px; 7 | } 8 | 9 | .step-item { 10 | display: inline-block; 11 | line-height: 26px; 12 | position: relative; 13 | font-size: 14px; 14 | } 15 | 16 | .step-item-tail { 17 | width: 100%; 18 | padding: 0 10px; 19 | position: absolute; 20 | left: 0; 21 | top: 13px; 22 | } 23 | 24 | .step-item-tail i { 25 | display: inline-block; 26 | width: 100%; 27 | height: 1px; 28 | vertical-align: top; 29 | background: #c2c2c2; 30 | position: relative; 31 | } 32 | 33 | .step-item-tail .step-item-tail-done { 34 | background: #009688; 35 | } 36 | 37 | .step-item-head { 38 | position: relative; 39 | display: inline-block; 40 | height: 26px; 41 | width: 26px; 42 | text-align: center; 43 | vertical-align: top; 44 | color: #009688; 45 | border: 1px solid #009688; 46 | border-radius: 50%; 47 | background: #ffffff; 48 | } 49 | 50 | .step-item-head.step-item-head-active { 51 | background: #009688; 52 | color: #ffffff; 53 | } 54 | 55 | .step-item-main { 56 | display: block; 57 | position: relative; 58 | margin-left: -50%; 59 | margin-right: 50%; 60 | padding-left: 26px; 61 | text-align: center; 62 | } 63 | 64 | .step-item-main-title { 65 | font-weight: bolder; 66 | color: #555555; 67 | } 68 | 69 | .step-item-main-desc { 70 | color: #aaaaaa; 71 | } 72 | 73 | .lay-step + [carousel-item]:before { 74 | display: none; 75 | } 76 | 77 | .lay-step + [carousel-item] > * { 78 | background-color: transparent; 79 | } -------------------------------------------------------------------------------- /root/js/lay-module/step-lay/step.js: -------------------------------------------------------------------------------- 1 | layui.define(['layer', 'carousel'], function (exports) { 2 | var $ = layui.jquery; 3 | var layer = layui.layer; 4 | var carousel = layui.carousel; 5 | 6 | // 添加步骤条dom节点 7 | var renderDom = function (elem, stepItems, postion) { 8 | var stepDiv = '
'; 9 | for (var i = 0; i < stepItems.length; i++) { 10 | stepDiv += '
'; 11 | // 线 12 | if (i < (stepItems.length - 1)) { 13 | if (i < postion) { 14 | stepDiv += '
'; 15 | } else { 16 | stepDiv += '
'; 17 | } 18 | } 19 | 20 | // 数字 21 | var number = stepItems[i].number; 22 | if (!number) { 23 | number = i + 1; 24 | } 25 | if (i == postion) { 26 | stepDiv += '
' + number + '
'; 27 | } else if (i < postion) { 28 | stepDiv += '
'; 29 | } else { 30 | stepDiv += '
' + number + '
'; 31 | } 32 | 33 | // 标题和描述 34 | var title = stepItems[i].title; 35 | var desc = stepItems[i].desc; 36 | if (title || desc) { 37 | stepDiv += '
'; 38 | if (title) { 39 | stepDiv += '
' + title + '
'; 40 | } 41 | if (desc) { 42 | stepDiv += '
' + desc + '
'; 43 | } 44 | stepDiv += '
'; 45 | } 46 | stepDiv += '
'; 47 | } 48 | stepDiv += '
'; 49 | 50 | $(elem).prepend(stepDiv); 51 | 52 | // 计算每一个条目的宽度 53 | var bfb = 100 / stepItems.length; 54 | $('.step-item').css('width', bfb + '%'); 55 | }; 56 | 57 | var step = { 58 | // 渲染步骤条 59 | render: function (param) { 60 | param.indicator = 'none'; // 不显示指示器 61 | param.arrow = 'always'; // 始终显示箭头 62 | param.autoplay = false; // 关闭自动播放 63 | if (!param.stepWidth) { 64 | param.stepWidth = '400px'; 65 | } 66 | 67 | // 渲染轮播图 68 | carousel.render(param); 69 | 70 | // 渲染步骤条 71 | var stepItems = param.stepItems; 72 | renderDom(param.elem, stepItems, 0); 73 | $('.lay-step').css('width', param.stepWidth); 74 | 75 | //监听轮播切换事件 76 | carousel.on('change(' + param.filter + ')', function (obj) { 77 | $(param.elem).find('.lay-step').remove(); 78 | renderDom(param.elem, stepItems, obj.index); 79 | $('.lay-step').css('width', param.stepWidth); 80 | }); 81 | 82 | // 隐藏左右箭头按钮 83 | $(param.elem).find('.layui-carousel-arrow').css('display', 'none'); 84 | 85 | // 去掉轮播图的背景颜色 86 | $(param.elem).css('background-color', 'transparent'); 87 | }, 88 | // 下一步 89 | next: function (elem) { 90 | $(elem).find('.layui-carousel-arrow[lay-type=add]').trigger('click'); 91 | }, 92 | // 上一步 93 | pre: function (elem) { 94 | $(elem).find('.layui-carousel-arrow[lay-type=sub]').trigger('click'); 95 | } 96 | }; 97 | 98 | layui.link(layui.cache.base + 'step-lay/step.css'); 99 | 100 | exports('step', step); 101 | }); 102 | -------------------------------------------------------------------------------- /root/js/lay-module/tableSelect/tableSelect.js: -------------------------------------------------------------------------------- 1 | layui.define(['table', 'jquery', 'form'], function (exports) { 2 | "use strict"; 3 | 4 | var MOD_NAME = 'tableSelect', 5 | $ = layui.jquery, 6 | table = layui.table, 7 | form = layui.form; 8 | var tableSelect = function () { 9 | this.v = '1.1.0'; 10 | }; 11 | 12 | /** 13 | * 初始化表格选择器 14 | */ 15 | tableSelect.prototype.render = function (opt) { 16 | var elem = $(opt.elem); 17 | var tableDone = opt.table.done || function(){}; 18 | 19 | //默认设置 20 | opt.searchKey = opt.searchKey || 'keyword'; 21 | opt.searchPlaceholder = opt.searchPlaceholder || '关键词搜索'; 22 | opt.checkedKey = opt.checkedKey; 23 | opt.table.page = opt.table.page || true; 24 | opt.table.height = opt.height || 315; 25 | 26 | //最小宽度 27 | opt.width = opt.width || '530'; 28 | 29 | //多搜索条件 30 | opt.searchType = opt.searchType || 'one'; 31 | opt.searchList = opt.searchList || [{key: opt.searchKey, placeholder: opt.searchPlaceholder}]; 32 | 33 | elem.off('click').on('click', function(e) { 34 | e.stopPropagation(); 35 | 36 | if($('div.tableSelect').length >= 1){ 37 | return false; 38 | } 39 | 40 | var t = elem.offset().top + elem.outerHeight()+"px"; 41 | var l = elem.offset().left +"px"; 42 | var tableName = "tableSelect_table_" + new Date().getTime(); 43 | var tableBox = '
'; 44 | tableBox += '
'; 45 | tableBox += '
'; 46 | 47 | //判断是否多搜索条件 48 | if(opt.searchType == 'more'){ 49 | $.each(opt.searchList, function (index, item) { 50 | tableBox += ''; 51 | }); 52 | }else{ 53 | tableBox += ''; 54 | } 55 | 56 | tableBox += ''; 57 | tableBox += '
'; 58 | tableBox += ''; 59 | tableBox += '
'; 60 | tableBox += '
'; 61 | tableBox += '
'; 62 | tableBox = $(tableBox); 63 | $('body').append(tableBox); 64 | 65 | //数据缓存 66 | var checkedData = []; 67 | 68 | //渲染TABLE 69 | opt.table.elem = "#"+tableName; 70 | opt.table.id = tableName; 71 | opt.table.done = function(res, curr, count){ 72 | defaultChecked(res, curr, count); 73 | setChecked(res, curr, count); 74 | tableDone(res, curr, count); 75 | }; 76 | var tableSelect_table = table.render(opt.table); 77 | 78 | //分页选中保存数组 79 | table.on('radio('+tableName+')', function(obj){ 80 | if(opt.checkedKey){ 81 | checkedData = table.checkStatus(tableName).data 82 | } 83 | updataButton(table.checkStatus(tableName).data.length) 84 | }) 85 | table.on('checkbox('+tableName+')', function(obj){ 86 | if(opt.checkedKey){ 87 | if(obj.checked){ 88 | for (var i=0;i $(window).height(); 197 | var overWidth = (elem.offset().left + tableBox.outerWidth()) > $(window).width(); 198 | overHeight && tableBox.css({'top':'auto','bottom':'0px'}); 199 | overWidth && tableBox.css({'left':'auto','right':'5px'}) 200 | 201 | //关键词搜索 202 | form.on('submit(tableSelect_btn_search)', function(data){ 203 | tableSelect_table.reload({ 204 | where: data.field, 205 | page: { 206 | curr: 1 207 | } 208 | }); 209 | return false; 210 | }); 211 | 212 | //双击行选中 213 | table.on('rowDouble('+tableName+')', function(obj){ 214 | var checkStatus = {data:[obj.data]}; 215 | selectDone(checkStatus); 216 | }) 217 | 218 | //按钮选中 219 | tableBox.find('.tableSelect_btn_select').on('click', function() { 220 | var checkStatus = table.checkStatus(tableName); 221 | if(checkedData.length > 1){ 222 | checkStatus.data = checkedData; 223 | } 224 | selectDone(checkStatus); 225 | }) 226 | 227 | //写值回调和关闭 228 | function selectDone (checkStatus){ 229 | if(opt.checkedKey){ 230 | var selected = []; 231 | for(var i=0;i 0 && mData[len - 1].id == s_pid) { 52 | mData[len - 1].isParent = true; 53 | } 54 | mData.push(data[i]); 55 | sort(data[i].id, data); 56 | } 57 | } 58 | }; 59 | sort(param.treeSpid, tNodes); 60 | 61 | // 重写参数 62 | param.url = undefined; 63 | param.data = mData; 64 | param.page = { 65 | count: param.data.length, 66 | limit: param.data.length 67 | }; 68 | param.cols[0][param.treeColIndex].templet = function (d) { 69 | var mId = d.id; 70 | var mPid = d.pid; 71 | var isDir = d.isParent; 72 | var emptyNum = treetable.getEmptyNum(mPid, mData); 73 | var iconHtml = ''; 74 | for (var i = 0; i < emptyNum; i++) { 75 | iconHtml += ''; 76 | } 77 | if (isDir) { 78 | iconHtml += ' '; 79 | } else { 80 | iconHtml += ''; 81 | } 82 | iconHtml += '  '; 83 | var ttype = isDir ? 'dir' : 'file'; 84 | var vg = ''; 85 | return vg + iconHtml + d[param.cols[0][param.treeColIndex].field] + '' 86 | }; 87 | 88 | param.done = function (res, curr, count) { 89 | $(param.elem).next().addClass('treeTable'); 90 | $('.treeTable .layui-table-page').css('display', 'none'); 91 | $(param.elem).next().attr('treeLinkage', param.treeLinkage); 92 | // 绑定事件换成对body绑定 93 | /*$('.treeTable .treeTable-icon').click(function () { 94 | treetable.toggleRows($(this), param.treeLinkage); 95 | });*/ 96 | if (param.treeDefaultClose) { 97 | treetable.foldAll(param.elem); 98 | } 99 | if (doneCallback) { 100 | doneCallback(res, curr, count); 101 | } 102 | }; 103 | 104 | // 渲染表格 105 | table.render(param); 106 | }, 107 | // 计算缩进的数量 108 | getEmptyNum: function (pid, data) { 109 | var num = 0; 110 | if (!pid) { 111 | return num; 112 | } 113 | var tPid; 114 | for (var i = 0; i < data.length; i++) { 115 | if (pid == data[i].id) { 116 | num += 1; 117 | tPid = data[i].pid; 118 | break; 119 | } 120 | } 121 | return num + treetable.getEmptyNum(tPid, data); 122 | }, 123 | // 展开/折叠行 124 | toggleRows: function ($dom, linkage) { 125 | var type = $dom.attr('lay-ttype'); 126 | if ('file' == type) { 127 | return; 128 | } 129 | var mId = $dom.attr('lay-tid'); 130 | var isOpen = $dom.hasClass('open'); 131 | if (isOpen) { 132 | $dom.removeClass('open'); 133 | } else { 134 | $dom.addClass('open'); 135 | } 136 | $dom.closest('tbody').find('tr').each(function () { 137 | var $ti = $(this).find('.treeTable-icon'); 138 | var pid = $ti.attr('lay-tpid'); 139 | var ttype = $ti.attr('lay-ttype'); 140 | var tOpen = $ti.hasClass('open'); 141 | if (mId == pid) { 142 | if (isOpen) { 143 | $(this).hide(); 144 | if ('dir' == ttype && tOpen == isOpen) { 145 | $ti.trigger('click'); 146 | } 147 | } else { 148 | $(this).show(); 149 | if (linkage && 'dir' == ttype && tOpen == isOpen) { 150 | $ti.trigger('click'); 151 | } 152 | } 153 | } 154 | }); 155 | }, 156 | // 检查参数 157 | checkParam: function (param) { 158 | if (!param.treeSpid && param.treeSpid != 0) { 159 | layer.msg('参数treeSpid不能为空', {icon: 5}); 160 | return false; 161 | } 162 | 163 | if (!param.treeColIndex && param.treeColIndex != 0) { 164 | layer.msg('参数treeColIndex不能为空', {icon: 5}); 165 | return false; 166 | } 167 | return true; 168 | }, 169 | // 展开所有 170 | expandAll: function (dom) { 171 | $(dom).next('.treeTable').find('.layui-table-body tbody tr').each(function () { 172 | var $ti = $(this).find('.treeTable-icon'); 173 | var ttype = $ti.attr('lay-ttype'); 174 | var tOpen = $ti.hasClass('open'); 175 | if ('dir' == ttype && !tOpen) { 176 | $ti.trigger('click'); 177 | } 178 | }); 179 | }, 180 | // 折叠所有 181 | foldAll: function (dom) { 182 | $(dom).next('.treeTable').find('.layui-table-body tbody tr').each(function () { 183 | var $ti = $(this).find('.treeTable-icon'); 184 | var ttype = $ti.attr('lay-ttype'); 185 | var tOpen = $ti.hasClass('open'); 186 | if ('dir' == ttype && tOpen) { 187 | $ti.trigger('click'); 188 | } 189 | }); 190 | } 191 | }; 192 | 193 | layui.link(layui.cache.base + 'treetable-lay/treetable.css'); 194 | 195 | // 给图标列绑定事件 196 | $('body').on('click', '.treeTable .treeTable-icon', function () { 197 | var treeLinkage = $(this).parents('.treeTable').attr('treeLinkage'); 198 | if ('true' == treeLinkage) { 199 | treetable.toggleRows($(this), true); 200 | } else { 201 | treetable.toggleRows($(this), false); 202 | } 203 | }); 204 | 205 | exports('treetable', treetable); 206 | }); 207 | -------------------------------------------------------------------------------- /root/js/lay-module/wangEditor/fonts/w-e-icon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YDLinStars/LinuxWebServer/a080cb1d4ac8caee401332d8aa8b276e0ffc6a80/root/js/lay-module/wangEditor/fonts/w-e-icon.woff -------------------------------------------------------------------------------- /root/lib/layui-v2.6.3/css/modules/code.css: -------------------------------------------------------------------------------- 1 | html #layuicss-skincodecss{display:none;position:absolute;width:1989px}.layui-code-h3,.layui-code-view{position:relative;font-size:12px}.layui-code-view{display:block;margin:10px 0;padding:0;border:1px solid #eee;border-left-width:6px;background-color:#FAFAFA;color:#333;font-family:Courier New}.layui-code-h3{padding:0 10px;height:40px;line-height:40px;border-bottom:1px solid #eee}.layui-code-h3 a{position:absolute;right:10px;top:0;color:#999}.layui-code-view .layui-code-ol{position:relative;overflow:auto}.layui-code-view .layui-code-ol li{position:relative;margin-left:45px;line-height:20px;padding:0 10px;border-left:1px solid #e2e2e2;list-style-type:decimal-leading-zero;*list-style-type:decimal;background-color:#fff}.layui-code-view .layui-code-ol li:first-child{padding-top:10px}.layui-code-view .layui-code-ol li:last-child{padding-bottom:10px}.layui-code-view pre{margin:0}.layui-code-notepad{border:1px solid #0C0C0C;border-left-color:#3F3F3F;background-color:#0C0C0C;color:#C2BE9E}.layui-code-notepad .layui-code-h3{border-bottom:none}.layui-code-notepad .layui-code-ol li{background-color:#3F3F3F;border-left:none}.layui-code-demo .layui-code{visibility:visible!important;margin:-15px;border-top:none;border-right:none;border-bottom:none}.layui-code-demo .layui-tab-content{padding:15px;border-top:none} -------------------------------------------------------------------------------- /root/lib/layui-v2.6.3/css/modules/laydate/default/laydate.css: -------------------------------------------------------------------------------- 1 | .laydate-set-ym,.layui-laydate,.layui-laydate *,.layui-laydate-list{box-sizing:border-box}html #layuicss-laydate{display:none;position:absolute;width:1989px}.layui-laydate *{margin:0;padding:0}.layui-laydate{position:absolute;z-index:66666666;margin:5px 0;border-radius:2px;font-size:14px;-webkit-animation-duration:.2s;animation-duration:.2s;-webkit-animation-fill-mode:both;animation-fill-mode:both;animation-name:laydate-downbit}.layui-laydate-main{width:272px}.layui-laydate-content td,.layui-laydate-header *,.layui-laydate-list li{transition-duration:.3s;-webkit-transition-duration:.3s}@keyframes laydate-downbit{0%{opacity:.3;transform:translate3d(0,-5px,0)}100%{opacity:1;transform:translate3d(0,0,0)}}.layui-laydate-static{position:relative;z-index:0;display:inline-block;margin:0;-webkit-animation:none;animation:none}.laydate-ym-show .laydate-next-m,.laydate-ym-show .laydate-prev-m{display:none!important}.laydate-ym-show .laydate-next-y,.laydate-ym-show .laydate-prev-y{display:inline-block!important}.laydate-time-show .laydate-set-ym span[lay-type=month],.laydate-time-show .laydate-set-ym span[lay-type=year],.laydate-time-show .layui-laydate-header .layui-icon,.laydate-ym-show .laydate-set-ym span[lay-type=month]{display:none!important}.layui-laydate-header{position:relative;line-height:30px;padding:10px 70px 5px}.laydate-set-ym span,.layui-laydate-header i{padding:0 5px;cursor:pointer}.layui-laydate-header *{display:inline-block;vertical-align:bottom}.layui-laydate-header i{position:absolute;top:10px;color:#999;font-size:18px}.layui-laydate-header i.laydate-prev-y{left:15px}.layui-laydate-header i.laydate-prev-m{left:45px}.layui-laydate-header i.laydate-next-y{right:15px}.layui-laydate-header i.laydate-next-m{right:45px}.laydate-set-ym{width:100%;text-align:center;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.laydate-time-text{cursor:default!important}.layui-laydate-content{position:relative;padding:10px;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none}.layui-laydate-content table{border-collapse:collapse;border-spacing:0}.layui-laydate-content td,.layui-laydate-content th{width:36px;height:30px;padding:5px;text-align:center}.layui-laydate-content td{position:relative;cursor:pointer}.laydate-day-mark{position:absolute;left:0;top:0;width:100%;height:100%;line-height:30px;font-size:12px;overflow:hidden}.laydate-day-mark::after{position:absolute;content:'';right:2px;top:2px;width:5px;height:5px;border-radius:50%}.layui-laydate-footer{position:relative;height:46px;line-height:26px;padding:10px 20px}.layui-laydate-footer span{margin-right:15px;display:inline-block;cursor:pointer;font-size:12px}.layui-laydate-footer span:hover{color:#5FB878}.laydate-footer-btns{position:absolute;right:10px;top:10px}.laydate-footer-btns span{height:26px;line-height:26px;margin:0 0 0 -1px;padding:0 10px;border:1px solid #C9C9C9;background-color:#fff;white-space:nowrap;vertical-align:top;border-radius:2px}.layui-laydate-list>li,.layui-laydate-range .layui-laydate-main{display:inline-block;vertical-align:middle}.layui-laydate-list{position:absolute;left:0;top:0;width:100%;height:100%;padding:10px;background-color:#fff}.layui-laydate-list>li{position:relative;width:33.3%;height:36px;line-height:36px;margin:3px 0;text-align:center;cursor:pointer}.laydate-month-list>li{width:25%;margin:17px 0}.laydate-time-list>li{height:100%;margin:0;line-height:normal;cursor:default}.laydate-time-list p{position:relative;top:-4px;line-height:29px}.laydate-time-list ol{height:181px;overflow:hidden}.laydate-time-list>li:hover ol{overflow-y:auto}.laydate-time-list ol li{width:130%;padding-left:33px;line-height:30px;text-align:left;cursor:pointer}.layui-laydate-hint{position:absolute;top:115px;left:50%;width:250px;margin-left:-125px;line-height:20px;padding:15px;text-align:center;font-size:12px}.layui-laydate-range{width:546px}.layui-laydate-range .laydate-main-list-1 .layui-laydate-content,.layui-laydate-range .laydate-main-list-1 .layui-laydate-header{border-left:1px solid #e2e2e2}.layui-laydate,.layui-laydate-hint{border:1px solid #d2d2d2;box-shadow:0 2px 4px rgba(0,0,0,.12);background-color:#fff;color:#666}.layui-laydate-header{border-bottom:1px solid #e2e2e2}.layui-laydate-header i:hover,.layui-laydate-header span:hover{color:#5FB878}.layui-laydate-content{border-top:none 0;border-bottom:none 0}.layui-laydate-content th{font-weight:400;color:#333}.layui-laydate-content td{color:#666}.layui-laydate-content td.laydate-selected{background-color:#B5FFF8}.laydate-selected:hover{background-color:#00F7DE!important}.layui-laydate-content td:hover,.layui-laydate-list li:hover{background-color:#eee;color:#333}.laydate-time-list li ol{margin:0;padding:0;border:1px solid #e2e2e2;border-left-width:0}.laydate-time-list li:first-child ol{border-left-width:1px}.laydate-time-list>li:hover{background:0 0}.layui-laydate-content .laydate-day-next,.layui-laydate-content .laydate-day-prev{color:#d2d2d2}.laydate-selected.laydate-day-next,.laydate-selected.laydate-day-prev{background-color:#f8f8f8!important}.layui-laydate-footer{border-top:1px solid #e2e2e2}.layui-laydate-hint{color:#FF5722}.laydate-day-mark::after{background-color:#5FB878}.layui-laydate-content td.layui-this .laydate-day-mark::after{display:none}.layui-laydate-footer span[lay-type=date]{color:#5FB878}.layui-laydate .layui-this{background-color:#009688!important;color:#fff!important}.layui-laydate .laydate-disabled,.layui-laydate .laydate-disabled:hover{background:0 0!important;color:#d2d2d2!important;cursor:not-allowed!important;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none}.laydate-theme-molv{border:none}.laydate-theme-molv.layui-laydate-range{width:548px}.laydate-theme-molv .layui-laydate-main{width:274px}.laydate-theme-molv .layui-laydate-header{border:none;background-color:#009688}.laydate-theme-molv .layui-laydate-header i,.laydate-theme-molv .layui-laydate-header span{color:#f6f6f6}.laydate-theme-molv .layui-laydate-header i:hover,.laydate-theme-molv .layui-laydate-header span:hover{color:#fff}.laydate-theme-molv .layui-laydate-content{border:1px solid #e2e2e2;border-top:none;border-bottom:none}.laydate-theme-molv .laydate-main-list-1 .layui-laydate-content{border-left:none}.laydate-theme-grid .laydate-month-list>li,.laydate-theme-grid .laydate-year-list>li,.laydate-theme-grid .layui-laydate-content td,.laydate-theme-grid .layui-laydate-content thead,.laydate-theme-molv .layui-laydate-footer{border:1px solid #e2e2e2}.laydate-theme-grid .laydate-selected,.laydate-theme-grid .laydate-selected:hover{background-color:#f2f2f2!important;color:#009688!important}.laydate-theme-grid .laydate-selected.laydate-day-next,.laydate-theme-grid .laydate-selected.laydate-day-prev{color:#d2d2d2!important}.laydate-theme-grid .laydate-month-list,.laydate-theme-grid .laydate-year-list{margin:1px 0 0 1px}.laydate-theme-grid .laydate-month-list>li,.laydate-theme-grid .laydate-year-list>li{margin:0 -1px -1px 0}.laydate-theme-grid .laydate-year-list>li{height:43px;line-height:43px}.laydate-theme-grid .laydate-month-list>li{height:71px;line-height:71px} -------------------------------------------------------------------------------- /root/lib/layui-v2.6.3/css/modules/layer/default/icon-ext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YDLinStars/LinuxWebServer/a080cb1d4ac8caee401332d8aa8b276e0ffc6a80/root/lib/layui-v2.6.3/css/modules/layer/default/icon-ext.png -------------------------------------------------------------------------------- /root/lib/layui-v2.6.3/css/modules/layer/default/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YDLinStars/LinuxWebServer/a080cb1d4ac8caee401332d8aa8b276e0ffc6a80/root/lib/layui-v2.6.3/css/modules/layer/default/icon.png -------------------------------------------------------------------------------- /root/lib/layui-v2.6.3/css/modules/layer/default/loading-0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YDLinStars/LinuxWebServer/a080cb1d4ac8caee401332d8aa8b276e0ffc6a80/root/lib/layui-v2.6.3/css/modules/layer/default/loading-0.gif -------------------------------------------------------------------------------- /root/lib/layui-v2.6.3/css/modules/layer/default/loading-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YDLinStars/LinuxWebServer/a080cb1d4ac8caee401332d8aa8b276e0ffc6a80/root/lib/layui-v2.6.3/css/modules/layer/default/loading-1.gif -------------------------------------------------------------------------------- /root/lib/layui-v2.6.3/css/modules/layer/default/loading-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YDLinStars/LinuxWebServer/a080cb1d4ac8caee401332d8aa8b276e0ffc6a80/root/lib/layui-v2.6.3/css/modules/layer/default/loading-2.gif -------------------------------------------------------------------------------- /root/lib/layui-v2.6.3/font/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YDLinStars/LinuxWebServer/a080cb1d4ac8caee401332d8aa8b276e0ffc6a80/root/lib/layui-v2.6.3/font/iconfont.eot -------------------------------------------------------------------------------- /root/lib/layui-v2.6.3/font/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YDLinStars/LinuxWebServer/a080cb1d4ac8caee401332d8aa8b276e0ffc6a80/root/lib/layui-v2.6.3/font/iconfont.ttf -------------------------------------------------------------------------------- /root/lib/layui-v2.6.3/font/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YDLinStars/LinuxWebServer/a080cb1d4ac8caee401332d8aa8b276e0ffc6a80/root/lib/layui-v2.6.3/font/iconfont.woff -------------------------------------------------------------------------------- /root/lib/layui-v2.6.3/font/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YDLinStars/LinuxWebServer/a080cb1d4ac8caee401332d8aa8b276e0ffc6a80/root/lib/layui-v2.6.3/font/iconfont.woff2 -------------------------------------------------------------------------------- /root/page/fans.html: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 | 15 | 16 | 感谢关注, 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 28 | 29 | 30 | 31 |
32 | 关于我 33 |
34 |
35 |
36 |

个人Github地址

37 |
38 |

https://github.com/YDLinStars。

39 |
40 |
41 |
42 |

个人博客地址

43 |
44 |

个人CSDN的学习博客:ydlin.blog.csdn.net 45 |

46 |
47 |
48 |
49 |

如何启动项目

50 |
51 |

注意环境的配置: Ubuntu版本16.04, MySQL版本5.7.29。mysql 所需要的库文件在makefile文件里有说明。具体的配置查看ReadMe文件。启动: ./server port. 52 |

53 |
54 |
55 |
56 |

学习资料

57 |
58 |

TCP/IP网络编程 非常适合基础编程

59 |

Linux高性能服务器编程 代码中的线程池、锁、Proactor模式的半同步/半反应堆借鉴了文中代码

60 |

社长的服务器: https://github.com/qinguoyi/TinyWebServer

61 |
62 |
63 |
64 | 65 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /root/page/judge.html: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 | 15 | 16 | 服务器后台首页 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 28 | 29 | 30 | 31 |
32 |
33 | 欢迎访问 34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | 46 | 47 | 48 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /root/page/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 登录界面 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 18 | 300 | 301 | 302 | 303 |
304 | 329 |
330 | 333 | 334 | 375 | 376 | 377 | -------------------------------------------------------------------------------- /root/page/picture.html: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 | 15 | 16 | 登录界面 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 28 | 29 | 30 | 31 | 32 |
33 | 常规轮播 34 |
35 | 36 |
37 | 图片轮播,最后一张为大图 38 |
39 | 58 | 59 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /root/page/register.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 登录界面 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 18 | 300 | 301 | 302 | 303 |
304 | 329 |
330 | 333 | 334 | 381 | 382 | 383 | -------------------------------------------------------------------------------- /root/page/video.html: -------------------------------------------------------------------------------- 1 | 11 | 21 | 22 | 23 | 24 | 25 | 26 | 视频播放 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 38 | 39 | 40 | 41 |
42 | 视频播放 43 |
44 |
47 | 48 | 49 | -------------------------------------------------------------------------------- /root/page/welcome.html: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 | 15 | 16 | 登录界面 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 28 | 29 | 30 | 31 |
32 |
33 | 功能实现时间线 34 |
35 |
    36 |
  • 37 | 38 |
    39 |
    40 | 2021年12月基础版的Linux服务器 41 |
    42 |
    43 |
  • 44 |
  • 45 | 46 |
    47 |
    48 | 日志系统实现 49 |
    50 |
    51 |
  • 52 |
  • 53 | 54 |
    55 |
    56 | 2022年3月优化前端界面 57 |
    58 |
    59 |
  • 60 |
  • 61 | 62 |
    63 |
    64 | 2022年3月优化前端界面 65 |
    66 |
    67 |
  • 68 |
  •  70 | 71 |
    72 |
    73 | 更久前,轮子时代。维护几个独立组件:layer等 74 |
    75 |
    76 |
  • 77 |
78 | 79 |
80 | 选择以下功能模块 81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 | 98 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /root/xxx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YDLinStars/LinuxWebServer/a080cb1d4ac8caee401332d8aa8b276e0ffc6a80/root/xxx.jpg -------------------------------------------------------------------------------- /server: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YDLinStars/LinuxWebServer/a080cb1d4ac8caee401332d8aa8b276e0ffc6a80/server -------------------------------------------------------------------------------- /test_presure/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 服务器压力测试 4 | =============== 5 | Webbench是有名的网站压力测试工具,它是由[Lionbridge](http://www.lionbridge.com)公司开发。 6 | 7 | > * 测试处在相同硬件上,不同服务的性能以及不同硬件上同一个服务的运行状况。 8 | > * 展示服务器的两项内容:每秒钟响应请求数和每秒钟传输数据量。 9 | 10 | 11 | 12 | 13 | 测试规则 14 | ------------ 15 | * 测试示例 16 | 17 | ```C++ 18 | webbench -c 500 -t 30 http://127.0.0.1/phpionfo.php 19 | ``` 20 | * 参数 21 | 22 | > * `-c` 表示客户端数 23 | > * `-t` 表示时间 24 | 25 | 26 | 测试结果 27 | --------- 28 | Webbench对服务器进行压力测试,经压力测试可以实现上万的并发连接. 29 | > * 并发连接总数:10500 30 | > * 访问服务器时间:5s 31 | > * 每秒钟响应请求数:552852 pages/min 32 | > * 每秒钟传输数据量:1031990 bytes/sec 33 | > * 所有访问均成功 34 | 35 |
36 | 37 | -------------------------------------------------------------------------------- /test_presure/webbench-1.5/COPYRIGHT: -------------------------------------------------------------------------------- 1 | debian/copyright -------------------------------------------------------------------------------- /test_presure/webbench-1.5/ChangeLog: -------------------------------------------------------------------------------- 1 | debian/changelog -------------------------------------------------------------------------------- /test_presure/webbench-1.5/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS?= -Wall -ggdb -W -O 2 | CC?= gcc 3 | LIBS?= 4 | LDFLAGS?= 5 | PREFIX?= /usr/local 6 | VERSION=1.5 7 | TMPDIR=/tmp/webbench-$(VERSION) 8 | 9 | all: webbench tags 10 | 11 | tags: *.c 12 | -ctags *.c 13 | 14 | install: webbench 15 | install -s webbench $(DESTDIR)$(PREFIX)/bin 16 | install -m 644 webbench.1 $(DESTDIR)$(PREFIX)/man/man1 17 | install -d $(DESTDIR)$(PREFIX)/share/doc/webbench 18 | install -m 644 debian/copyright $(DESTDIR)$(PREFIX)/share/doc/webbench 19 | install -m 644 debian/changelog $(DESTDIR)$(PREFIX)/share/doc/webbench 20 | 21 | webbench: webbench.o Makefile 22 | $(CC) $(CFLAGS) $(LDFLAGS) -o webbench webbench.o $(LIBS) 23 | 24 | clean: 25 | -rm -f *.o webbench *~ core *.core tags 26 | 27 | tar: clean 28 | -debian/rules clean 29 | rm -rf $(TMPDIR) 30 | install -d $(TMPDIR) 31 | cp -p Makefile webbench.c socket.c webbench.1 $(TMPDIR) 32 | install -d $(TMPDIR)/debian 33 | -cp -p debian/* $(TMPDIR)/debian 34 | ln -sf debian/copyright $(TMPDIR)/COPYRIGHT 35 | ln -sf debian/changelog $(TMPDIR)/ChangeLog 36 | -cd $(TMPDIR) && cd .. && tar cozf webbench-$(VERSION).tar.gz webbench-$(VERSION) 37 | 38 | webbench.o: webbench.c socket.c Makefile 39 | 40 | .PHONY: clean install all tar 41 | -------------------------------------------------------------------------------- /test_presure/webbench-1.5/debian/changelog: -------------------------------------------------------------------------------- 1 | webbench (1.5) unstable; urgency=low 2 | 3 | * allow building with both Gnu and BSD make 4 | 5 | -- Radim Kolar Fri, Jun 25 12:00:20 CEST 2004 6 | 7 | webbench (1.4) unstable; urgency=low 8 | 9 | * check if url is not too long 10 | * report correct program version number 11 | * use yield() when waiting for test start 12 | * corrected error codes 13 | * check availability of test server first 14 | * do not abort test if first request failed 15 | * report when some childrens are dead. 16 | * use alarm, not time() for lower syscal use by bench 17 | * use mode 644 for installed doc 18 | * makefile cleaned for better freebsd ports integration 19 | 20 | -- Radim Kolar Thu, 15 Jan 2004 11:15:52 +0100 21 | 22 | webbench (1.3) unstable; urgency=low 23 | 24 | * Build fixes for freeBSD 25 | * Default benchmark time 60 -> 30 26 | * generate tar with subdirectory 27 | * added to freeBSD ports collection 28 | 29 | -- Radim Kolar Mon, 12 Jan 2004 17:00:24 +0100 30 | 31 | webbench (1.2) unstable; urgency=low 32 | 33 | * Only debian-related bugfixes 34 | * Updated Debian/rules 35 | * Adapted to fit new directory system 36 | * moved from debstd to dh_* 37 | 38 | -- Radim Kolar Fri, 18 Jan 2002 12:33:04 +0100 39 | 40 | webbench (1.1) unstable; urgency=medium 41 | 42 | * Program debianized 43 | * added support for multiple methods (GET, HEAD, OPTIONS, TRACE) 44 | * added support for multiple HTTP versions (0.9 -- 1.1) 45 | * added long options 46 | * added multiple clients 47 | * wait for start of second before test 48 | * test time can be specified 49 | * better error checking when reading reply from server 50 | * FIX: tests was one second longer than expected 51 | 52 | -- Radim Kolar Thu, 16 Sep 1999 18:48:00 +0200 53 | 54 | Local variables: 55 | mode: debian-changelog 56 | End: 57 | -------------------------------------------------------------------------------- /test_presure/webbench-1.5/debian/control: -------------------------------------------------------------------------------- 1 | Source: webbench 2 | Section: web 3 | Priority: extra 4 | Maintainer: Radim Kolar 5 | Build-Depends: debhelper (>> 3.0.0) 6 | Standards-Version: 3.5.2 7 | 8 | Package: webbench 9 | Architecture: any 10 | Depends: ${shlibs:Depends} 11 | Description: Simple forking Web benchmark 12 | webbench is very simple program for benchmarking WWW or Proxy servers. 13 | Uses fork() for simulating multiple clients load. Can use HTTP 0.9 - 1.1 14 | requests, but Keep-Alive connections are not supported. 15 | -------------------------------------------------------------------------------- /test_presure/webbench-1.5/debian/copyright: -------------------------------------------------------------------------------- 1 | Webbench was written by Radim Kolar 1997-2004 (hsn@netmag.cz). 2 | 3 | UNIX sockets code (socket.c) taken from popclient 1.5 4/1/94 4 | public domain code, created by Virginia Tech Computing Center. 5 | 6 | Copyright: GPL (see /usr/share/common-licenses/GPL) 7 | -------------------------------------------------------------------------------- /test_presure/webbench-1.5/debian/dirs: -------------------------------------------------------------------------------- 1 | usr/bin -------------------------------------------------------------------------------- /test_presure/webbench-1.5/debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # Sample debian/rules that uses debhelper. 3 | # GNU copyright 1997 to 1999 by Joey Hess. 4 | 5 | # Uncomment this to turn on verbose mode. 6 | #export DH_VERBOSE=1 7 | 8 | # This is the debhelper compatability version to use. 9 | export DH_COMPAT=3 10 | 11 | configure: configure-stamp 12 | configure-stamp: 13 | dh_testdir 14 | touch configure-stamp 15 | 16 | build: configure-stamp build-stamp 17 | build-stamp: 18 | dh_testdir 19 | $(MAKE) 20 | touch build-stamp 21 | 22 | clean: 23 | dh_testdir 24 | rm -f build-stamp configure-stamp 25 | 26 | # Add here commands to clean up after the build process. 27 | -$(MAKE) clean 28 | 29 | dh_clean 30 | 31 | install: build 32 | dh_testdir 33 | dh_testroot 34 | dh_clean -k 35 | dh_installdirs 36 | 37 | # Add here commands to install the package into debian/webbench. 38 | $(MAKE) install DESTDIR=$(CURDIR)/debian/webbench 39 | 40 | 41 | # Build architecture-independent files here. 42 | binary-indep: build install 43 | # We have nothing to do by default. 44 | 45 | # Build architecture-dependent files here. 46 | binary-arch: build install 47 | dh_testdir 48 | dh_testroot 49 | dh_installdocs 50 | dh_installman webbench.1 51 | dh_installchangelogs 52 | dh_link 53 | dh_strip 54 | dh_compress 55 | dh_fixperms 56 | # dh_makeshlibs 57 | dh_installdeb 58 | dh_shlibdeps 59 | dh_gencontrol 60 | dh_md5sums 61 | dh_builddeb 62 | 63 | binary: binary-indep binary-arch 64 | .PHONY: build clean binary-indep binary-arch binary install configure 65 | -------------------------------------------------------------------------------- /test_presure/webbench-1.5/socket.c: -------------------------------------------------------------------------------- 1 | /* $Id: socket.c 1.1 1995/01/01 07:11:14 cthuang Exp $ 2 | * 3 | * This module has been modified by Radim Kolar for OS/2 emx 4 | */ 5 | 6 | /*********************************************************************** 7 | module: socket.c 8 | program: popclient 9 | SCCS ID: @(#)socket.c 1.5 4/1/94 10 | programmer: Virginia Tech Computing Center 11 | compiler: DEC RISC C compiler (Ultrix 4.1) 12 | environment: DEC Ultrix 4.3 13 | description: UNIX sockets code. 14 | ***********************************************************************/ 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | int Socket(const char *host, int clientPort) 30 | { 31 | int sock; 32 | unsigned long inaddr; 33 | struct sockaddr_in ad; 34 | struct hostent *hp; 35 | 36 | memset(&ad, 0, sizeof(ad)); 37 | ad.sin_family = AF_INET; 38 | 39 | inaddr = inet_addr(host); 40 | if (inaddr != INADDR_NONE) 41 | memcpy(&ad.sin_addr, &inaddr, sizeof(inaddr)); 42 | else 43 | { 44 | hp = gethostbyname(host); 45 | if (hp == NULL) 46 | return -1; 47 | memcpy(&ad.sin_addr, hp->h_addr, hp->h_length); 48 | } 49 | ad.sin_port = htons(clientPort); 50 | 51 | sock = socket(AF_INET, SOCK_STREAM, 0); 52 | if (sock < 0) 53 | return sock; 54 | if (connect(sock, (struct sockaddr *)&ad, sizeof(ad)) < 0) 55 | return -1; 56 | return sock; 57 | } 58 | 59 | -------------------------------------------------------------------------------- /test_presure/webbench-1.5/tags: -------------------------------------------------------------------------------- 1 | !_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/ 2 | !_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/ 3 | !_TAG_PROGRAM_AUTHOR Darren Hiebert /dhiebert@users.sourceforge.net/ 4 | !_TAG_PROGRAM_NAME Exuberant Ctags // 5 | !_TAG_PROGRAM_URL http://ctags.sourceforge.net /official site/ 6 | !_TAG_PROGRAM_VERSION 5.9~svn20110310 // 7 | METHOD_GET webbench.c 35;" d file: 8 | METHOD_HEAD webbench.c 36;" d file: 9 | METHOD_OPTIONS webbench.c 37;" d file: 10 | METHOD_TRACE webbench.c 38;" d file: 11 | PROGRAM_VERSION webbench.c 39;" d file: 12 | REQUEST_SIZE webbench.c 50;" d file: 13 | Socket socket.c /^int Socket(const char *host, int clientPort)$/;" f 14 | alarm_handler webbench.c /^static void alarm_handler(int signal)$/;" f file: 15 | bench webbench.c /^static int bench(void)$/;" f file: 16 | benchcore webbench.c /^void benchcore(const char *host,const int port,const char *req)$/;" f 17 | benchtime webbench.c /^int benchtime=30;$/;" v 18 | build_request webbench.c /^void build_request(const char *url)$/;" f 19 | bytes webbench.c /^int bytes=0;$/;" v 20 | clients webbench.c /^int clients=1;$/;" v 21 | failed webbench.c /^int failed=0;$/;" v 22 | force webbench.c /^int force=0;$/;" v 23 | force_reload webbench.c /^int force_reload=0;$/;" v 24 | host webbench.c /^char host[MAXHOSTNAMELEN];$/;" v 25 | http10 webbench.c /^int http10=1; \/* 0 - http\/0.9, 1 - http\/1.0, 2 - http\/1.1 *\/$/;" v 26 | long_options webbench.c /^static const struct option long_options[]=$/;" v typeref:struct:option file: 27 | main webbench.c /^int main(int argc, char *argv[])$/;" f 28 | method webbench.c /^int method=METHOD_GET;$/;" v 29 | mypipe webbench.c /^int mypipe[2];$/;" v 30 | proxyhost webbench.c /^char *proxyhost=NULL;$/;" v 31 | proxyport webbench.c /^int proxyport=80;$/;" v 32 | request webbench.c /^char request[REQUEST_SIZE];$/;" v 33 | speed webbench.c /^int speed=0;$/;" v 34 | timerexpired webbench.c /^volatile int timerexpired=0;$/;" v 35 | usage webbench.c /^static void usage(void)$/;" f file: 36 | -------------------------------------------------------------------------------- /test_presure/webbench-1.5/webbench: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YDLinStars/LinuxWebServer/a080cb1d4ac8caee401332d8aa8b276e0ffc6a80/test_presure/webbench-1.5/webbench -------------------------------------------------------------------------------- /test_presure/webbench-1.5/webbench.1: -------------------------------------------------------------------------------- 1 | .TH WEBBENCH 1 "14 Jan 2004" 2 | .\" NAME should be all caps, SECTION should be 1-8, maybe w/ subsection 3 | .\" other parms are allowed: see man(7), man(1) 4 | .SH NAME 5 | webbench \- simple forking web benchmark 6 | .SH SYNOPSIS 7 | .B webbench 8 | .I "[options] URL" 9 | .br 10 | .SH "AUTHOR" 11 | This program and manual page was written by Radim Kolar, 12 | for the 13 | .B Supreme Personality of Godhead 14 | (but may be used by others). 15 | .SH "DESCRIPTION" 16 | .B webbench 17 | is simple program for benchmarking HTTP servers or any 18 | other servers, which can be accessed via HTTP proxy. Unlike others 19 | benchmarks, 20 | .B webbench 21 | uses multiple processes for simulating traffic 22 | generated by multiple users. This allows better operating 23 | on SMP systems and on systems with slow or buggy implementation 24 | of select(). 25 | .SH OPTIONS 26 | The programs follow the usual GNU command line syntax, with long 27 | options starting with two dashes (`-'). 28 | A summary of options are included below. 29 | .TP 30 | .B \-?, \-h, \-\-help 31 | Show summary of options. 32 | .TP 33 | .B \-v, \-\-version 34 | Show version of program. 35 | .TP 36 | .B \-f, \-\-force 37 | Do not wait for any response from server. Close connection after 38 | request is send. This option produce quite a good denial of service 39 | attack. 40 | .TP 41 | .B \-9, \-\-http09 42 | Use HTTP/0.9 protocol, if possible. 43 | .TP 44 | .B \-1, \-\-http10 45 | Use HTTP/1.0 protocol, if possible. 46 | .TP 47 | .B \-2, \-\-http11 48 | Use HTTP/1.1 protocol (without 49 | .I Keep-Alive 50 | ), if possible. 51 | .TP 52 | .B \-r, \-\-reload 53 | Forces proxy to reload document. If proxy is not 54 | set, option has no effect. 55 | .TP 56 | .B \-t, \-\-time 57 | Run benchmark for 58 | .I 59 | seconds. Default value is 30. 60 | .TP 61 | .B \-p, \-\-proxy 62 | Send request via proxy server. Needed for supporting others protocols 63 | than HTTP. 64 | .TP 65 | .B \-\-get 66 | Use GET request method. 67 | .TP 68 | .B \-\-head 69 | Use HEAD request method. 70 | .TP 71 | .B \-\-options 72 | Use OPTIONS request method. 73 | .TP 74 | .B \-\-trace 75 | Use TRACE request method. 76 | .TP 77 | .B \-c, \-\-clients 78 | Use 79 | .I 80 | multiple clients for benchmark. Default value 81 | is 1. 82 | .SH "EXIT STATUS" 83 | .TP 84 | 0 - sucess 85 | .TP 86 | 1 - benchmark failed, can not connect to server 87 | .TP 88 | 2 - bad command line argument(s) 89 | .TP 90 | 3 - internal error, i.e. fork failed 91 | .SH "TODO" 92 | Include support for using 93 | .I Keep-Alive 94 | HTTP/1.1 connections. 95 | .SH "COPYING" 96 | Webbench is distributed under GPL. Copyright 1997-2004 97 | Radim Kolar (hsn@netmag.cz). 98 | UNIX sockets code taken from popclient 1.5 4/1/94 99 | public domain code, created by Virginia Tech Computing Center. 100 | .BR 101 | This man page is public domain. 102 | -------------------------------------------------------------------------------- /test_presure/webbench-1.5/webbench.c: -------------------------------------------------------------------------------- 1 | /* 2 | * (C) Radim Kolar 1997-2004 3 | * This is free software, see GNU Public License version 2 for 4 | * details. 5 | * 6 | * Simple forking WWW Server benchmark: 7 | * 8 | * Usage: 9 | * webbench --help 10 | * 11 | * Return codes: 12 | * 0 - sucess 13 | * 1 - benchmark failed (server is not on-line) 14 | * 2 - bad param 15 | * 3 - internal error, fork failed 16 | * 17 | */ 18 | #include "socket.c" 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | /* values */ 28 | volatile int timerexpired=0; 29 | int speed=0; 30 | int failed=0; 31 | int bytes=0; 32 | /* globals */ 33 | int http10=1; /* 0 - http/0.9, 1 - http/1.0, 2 - http/1.1 */ 34 | /* Allow: GET, HEAD, OPTIONS, TRACE */ 35 | #define METHOD_GET 0 36 | #define METHOD_HEAD 1 37 | #define METHOD_OPTIONS 2 38 | #define METHOD_TRACE 3 39 | #define PROGRAM_VERSION "1.5" 40 | int method=METHOD_GET; 41 | int clients=1; 42 | int force=0; 43 | int force_reload=0; 44 | int proxyport=80; 45 | char *proxyhost=NULL; 46 | int benchtime=30; 47 | /* internal */ 48 | int mypipe[2]; 49 | char host[MAXHOSTNAMELEN]; 50 | #define REQUEST_SIZE 2048 51 | char request[REQUEST_SIZE]; 52 | 53 | static const struct option long_options[]= 54 | { 55 | {"force",no_argument,&force,1}, 56 | {"reload",no_argument,&force_reload,1}, 57 | {"time",required_argument,NULL,'t'}, 58 | {"help",no_argument,NULL,'?'}, 59 | {"http09",no_argument,NULL,'9'}, 60 | {"http10",no_argument,NULL,'1'}, 61 | {"http11",no_argument,NULL,'2'}, 62 | {"get",no_argument,&method,METHOD_GET}, 63 | {"head",no_argument,&method,METHOD_HEAD}, 64 | {"options",no_argument,&method,METHOD_OPTIONS}, 65 | {"trace",no_argument,&method,METHOD_TRACE}, 66 | {"version",no_argument,NULL,'V'}, 67 | {"proxy",required_argument,NULL,'p'}, 68 | {"clients",required_argument,NULL,'c'}, 69 | {NULL,0,NULL,0} 70 | }; 71 | 72 | /* prototypes */ 73 | static void benchcore(const char* host,const int port, const char *request); 74 | static int bench(void); 75 | static void build_request(const char *url); 76 | 77 | static void alarm_handler(int signal) 78 | { 79 | timerexpired=1; 80 | } 81 | 82 | static void usage(void) 83 | { 84 | fprintf(stderr, 85 | "webbench [option]... URL\n" 86 | " -f|--force Don't wait for reply from server.\n" 87 | " -r|--reload Send reload request - Pragma: no-cache.\n" 88 | " -t|--time Run benchmark for seconds. Default 30.\n" 89 | " -p|--proxy Use proxy server for request.\n" 90 | " -c|--clients Run HTTP clients at once. Default one.\n" 91 | " -9|--http09 Use HTTP/0.9 style requests.\n" 92 | " -1|--http10 Use HTTP/1.0 protocol.\n" 93 | " -2|--http11 Use HTTP/1.1 protocol.\n" 94 | " --get Use GET request method.\n" 95 | " --head Use HEAD request method.\n" 96 | " --options Use OPTIONS request method.\n" 97 | " --trace Use TRACE request method.\n" 98 | " -?|-h|--help This information.\n" 99 | " -V|--version Display program version.\n" 100 | ); 101 | }; 102 | int main(int argc, char *argv[]) 103 | { 104 | int opt=0; 105 | int options_index=0; 106 | char *tmp=NULL; 107 | 108 | if(argc==1) 109 | { 110 | usage(); 111 | return 2; 112 | } 113 | 114 | while((opt=getopt_long(argc,argv,"912Vfrt:p:c:?h",long_options,&options_index))!=EOF ) 115 | { 116 | switch(opt) 117 | { 118 | case 0 : break; 119 | case 'f': force=1;break; 120 | case 'r': force_reload=1;break; 121 | case '9': http10=0;break; 122 | case '1': http10=1;break; 123 | case '2': http10=2;break; 124 | case 'V': printf(PROGRAM_VERSION"\n");exit(0); 125 | case 't': benchtime=atoi(optarg);break; 126 | case 'p': 127 | /* proxy server parsing server:port */ 128 | tmp=strrchr(optarg,':'); 129 | proxyhost=optarg; 130 | if(tmp==NULL) 131 | { 132 | break; 133 | } 134 | if(tmp==optarg) 135 | { 136 | fprintf(stderr,"Error in option --proxy %s: Missing hostname.\n",optarg); 137 | return 2; 138 | } 139 | if(tmp==optarg+strlen(optarg)-1) 140 | { 141 | fprintf(stderr,"Error in option --proxy %s Port number is missing.\n",optarg); 142 | return 2; 143 | } 144 | *tmp='\0'; 145 | proxyport=atoi(tmp+1);break; 146 | case ':': 147 | case 'h': 148 | case '?': usage();return 2;break; 149 | case 'c': clients=atoi(optarg);break; 150 | } 151 | } 152 | 153 | if(optind==argc) { 154 | fprintf(stderr,"webbench: Missing URL!\n"); 155 | usage(); 156 | return 2; 157 | } 158 | 159 | if(clients==0) clients=1; 160 | if(benchtime==0) benchtime=60; 161 | /* Copyright */ 162 | fprintf(stderr,"Webbench - Simple Web Benchmark "PROGRAM_VERSION"\n" 163 | "Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.\n" 164 | ); 165 | build_request(argv[optind]); 166 | /* print bench info */ 167 | printf("\nBenchmarking: "); 168 | switch(method) 169 | { 170 | case METHOD_GET: 171 | default: 172 | printf("GET");break; 173 | case METHOD_OPTIONS: 174 | printf("OPTIONS");break; 175 | case METHOD_HEAD: 176 | printf("HEAD");break; 177 | case METHOD_TRACE: 178 | printf("TRACE");break; 179 | } 180 | printf(" %s",argv[optind]); 181 | switch(http10) 182 | { 183 | case 0: printf(" (using HTTP/0.9)");break; 184 | case 2: printf(" (using HTTP/1.1)");break; 185 | } 186 | printf("\n"); 187 | if(clients==1) printf("1 client"); 188 | else 189 | printf("%d clients",clients); 190 | 191 | printf(", running %d sec", benchtime); 192 | if(force) printf(", early socket close"); 193 | if(proxyhost!=NULL) printf(", via proxy server %s:%d",proxyhost,proxyport); 194 | if(force_reload) printf(", forcing reload"); 195 | printf(".\n"); 196 | return bench(); 197 | } 198 | 199 | void build_request(const char *url) 200 | { 201 | char tmp[10]; 202 | int i; 203 | 204 | bzero(host,MAXHOSTNAMELEN); 205 | bzero(request,REQUEST_SIZE); 206 | 207 | if(force_reload && proxyhost!=NULL && http10<1) http10=1; 208 | if(method==METHOD_HEAD && http10<1) http10=1; 209 | if(method==METHOD_OPTIONS && http10<2) http10=2; 210 | if(method==METHOD_TRACE && http10<2) http10=2; 211 | 212 | switch(method) 213 | { 214 | default: 215 | case METHOD_GET: strcpy(request,"GET");break; 216 | case METHOD_HEAD: strcpy(request,"HEAD");break; 217 | case METHOD_OPTIONS: strcpy(request,"OPTIONS");break; 218 | case METHOD_TRACE: strcpy(request,"TRACE");break; 219 | } 220 | 221 | strcat(request," "); 222 | 223 | if(NULL==strstr(url,"://")) 224 | { 225 | fprintf(stderr, "\n%s: is not a valid URL.\n",url); 226 | exit(2); 227 | } 228 | if(strlen(url)>1500) 229 | { 230 | fprintf(stderr,"URL is too long.\n"); 231 | exit(2); 232 | } 233 | if(proxyhost==NULL) 234 | if (0!=strncasecmp("http://",url,7)) 235 | { fprintf(stderr,"\nOnly HTTP protocol is directly supported, set --proxy for others.\n"); 236 | exit(2); 237 | } 238 | /* protocol/host delimiter */ 239 | i=strstr(url,"://")-url+3; 240 | /* printf("%d\n",i); */ 241 | 242 | if(strchr(url+i,'/')==NULL) { 243 | fprintf(stderr,"\nInvalid URL syntax - hostname don't ends with '/'.\n"); 244 | exit(2); 245 | } 246 | if(proxyhost==NULL) 247 | { 248 | /* get port from hostname */ 249 | if(index(url+i,':')!=NULL && 250 | index(url+i,':')0) 275 | strcat(request,"User-Agent: WebBench "PROGRAM_VERSION"\r\n"); 276 | if(proxyhost==NULL && http10>0) 277 | { 278 | strcat(request,"Host: "); 279 | strcat(request,host); 280 | strcat(request,"\r\n"); 281 | } 282 | if(force_reload && proxyhost!=NULL) 283 | { 284 | strcat(request,"Pragma: no-cache\r\n"); 285 | } 286 | if(http10>1) 287 | strcat(request,"Connection: close\r\n"); 288 | /* add empty line at end */ 289 | if(http10>0) strcat(request,"\r\n"); 290 | // printf("Req=%s\n",request); 291 | } 292 | 293 | /* vraci system rc error kod */ 294 | static int bench(void) 295 | { 296 | int i,j,k; 297 | pid_t pid=0; 298 | FILE *f; 299 | 300 | /* check avaibility of target server */ 301 | i=Socket(proxyhost==NULL?host:proxyhost,proxyport); 302 | if(i<0) { 303 | fprintf(stderr,"\nConnect to server failed. Aborting benchmark.\n"); 304 | return 1; 305 | } 306 | close(i); 307 | /* create pipe */ 308 | if(pipe(mypipe)) 309 | { 310 | perror("pipe failed."); 311 | return 3; 312 | } 313 | 314 | /* not needed, since we have alarm() in childrens */ 315 | /* wait 4 next system clock tick */ 316 | /* 317 | cas=time(NULL); 318 | while(time(NULL)==cas) 319 | sched_yield(); 320 | */ 321 | 322 | /* fork childs */ 323 | for(i=0;i0) 418 | { 419 | /* fprintf(stderr,"Correcting failed by signal\n"); */ 420 | failed--; 421 | } 422 | return; 423 | } 424 | s=Socket(host,port); 425 | if(s<0) { failed++;continue;} 426 | if(rlen!=write(s,req,rlen)) {failed++;close(s);continue;} 427 | if(http10==0) 428 | if(shutdown(s,1)) { failed++;close(s);continue;} 429 | if(force==0) 430 | { 431 | /* read all available data from socket */ 432 | while(1) 433 | { 434 | if(timerexpired) break; 435 | i=read(s,buf,1500); 436 | /* fprintf(stderr,"%d\n",i); */ 437 | if(i<0) 438 | { 439 | failed++; 440 | close(s); 441 | goto nexttry; 442 | } 443 | else 444 | if(i==0) break; 445 | else 446 | bytes+=i; 447 | } 448 | } 449 | if(close(s)) {failed++;continue;} 450 | speed++; 451 | } 452 | } 453 | -------------------------------------------------------------------------------- /test_presure/webbench-1.5/webbench.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YDLinStars/LinuxWebServer/a080cb1d4ac8caee401332d8aa8b276e0ffc6a80/test_presure/webbench-1.5/webbench.o -------------------------------------------------------------------------------- /threadpool/README.md: -------------------------------------------------------------------------------- 1 | 2 | 半同步/半反应堆线程池 3 | =============== 4 | 使用一个工作队列完全解除了主线程和工作线程的耦合关系:主线程往工作队列中插入任务,工作线程通过竞争来取得任务并执行它。 5 | > * 同步I/O模拟proactor模式 6 | > * 半同步/半反应堆 7 | > * 线程池 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /threadpool/threadpool.h: -------------------------------------------------------------------------------- 1 | #ifndef THREADPOOL_H 2 | #define THREADPOOL_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "../lock/locker.h" 9 | #include "../CGImysql/sql_connection_pool.h" 10 | 11 | template 12 | class threadpool 13 | { 14 | public: 15 | /*thread_number是线程池中线程的数量,max_requests是请求队列中最多允许的、等待处理的请求的数量*/ 16 | threadpool(connection_pool *connPool, int thread_number = 8, int max_request = 10000); 17 | ~threadpool(); 18 | bool append(T *request); 19 | 20 | private: 21 | /*工作线程运行的函数,它不断从工作队列中取出任务并执行之*/ 22 | static void *worker(void *arg); 23 | void run(); 24 | 25 | private: 26 | int m_thread_number; //线程池中的线程数 27 | int m_max_requests; //请求队列中允许的最大请求数 28 | pthread_t *m_threads; //描述线程池的数组,其大小为m_thread_number 29 | std::list m_workqueue; //请求队列 30 | locker m_queuelocker; //保护请求队列的互斥锁 31 | sem m_queuestat; //是否有任务需要处理 32 | bool m_stop; //是否结束线程 33 | connection_pool *m_connPool; //数据库 34 | }; 35 | template 36 | threadpool::threadpool( connection_pool *connPool, int thread_number, int max_requests) : m_thread_number(thread_number), m_max_requests(max_requests), m_stop(false), m_threads(NULL),m_connPool(connPool) 37 | { 38 | if (thread_number <= 0 || max_requests <= 0) 39 | throw std::exception(); 40 | m_threads = new pthread_t[m_thread_number]; 41 | if (!m_threads) 42 | throw std::exception(); 43 | for (int i = 0; i < thread_number; ++i) 44 | { 45 | printf("create the %dth thread\n",i); 46 | if (pthread_create(m_threads + i, NULL, worker, this) != 0) 47 | { 48 | delete[] m_threads; 49 | throw std::exception(); 50 | } 51 | if (pthread_detach(m_threads[i])) 52 | { 53 | delete[] m_threads; 54 | throw std::exception(); 55 | } 56 | } 57 | } 58 | template 59 | threadpool::~threadpool() 60 | { 61 | delete[] m_threads; 62 | m_stop = true; 63 | } 64 | template 65 | bool threadpool::append(T *request) 66 | { 67 | m_queuelocker.lock(); 68 | if (m_workqueue.size() > m_max_requests) 69 | { 70 | m_queuelocker.unlock(); 71 | return false; 72 | } 73 | m_workqueue.push_back(request); 74 | m_queuelocker.unlock(); 75 | m_queuestat.post(); 76 | return true; 77 | } 78 | template 79 | void *threadpool::worker(void *arg) 80 | { 81 | threadpool *pool = (threadpool *)arg; 82 | pool->run(); 83 | return pool; 84 | } 85 | template 86 | void threadpool::run() 87 | { 88 | while (!m_stop) 89 | { 90 | m_queuestat.wait(); 91 | m_queuelocker.lock(); 92 | if (m_workqueue.empty()) 93 | { 94 | m_queuelocker.unlock(); 95 | continue; 96 | } 97 | T *request = m_workqueue.front(); 98 | m_workqueue.pop_front(); 99 | m_queuelocker.unlock(); 100 | if (!request) 101 | continue; 102 | 103 | connectionRAII mysqlcon(&request->mysql, m_connPool); 104 | 105 | request->process(); 106 | } 107 | } 108 | #endif -------------------------------------------------------------------------------- /threadpool/threadpool_new.h: -------------------------------------------------------------------------------- 1 | #ifndef THREADPOOL_H 2 | #define THREADPOOL_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "../lock/locker.h" 9 | #include "../CGImysql/sql_connection_pool.h" 10 | 11 | template 12 | class threadpool 13 | { 14 | public: 15 | threadpool(connection_pool * connPool, int thread_number = 8, int max_request = 10000); 16 | ~threadpool(); 17 | bool append(T *request); 18 | private: 19 | static void * worker(void *arg); 20 | void run(); 21 | 22 | int m_thread_number; // /线程池中的线程数 23 | int m_max_request; // 请求队列中允许的最大请求数 24 | pthread_t *m_threads; // 描述线程池的数组,其大小为m_thread_number 25 | std::list m_workqueue; // 用list(底层是双向链表)做请求队列 26 | locker m_queuelocker; // 保护请求队列的互斥锁 27 | sem m_queuestat; // 是否有任务需要处理 28 | bool m_stop; // 是否结束线程 29 | connection_pool* m_connPool; // 数据库 30 | }; 31 | template 32 | threadpool::threadpool(connection_pool* connPool, int thread_number, int max_requests):m_thread_number(thread_number),m_max_requests(max_requests), m_stop(false), m_threads(nullptr), m_connPool(connPool){ 33 | if(thread_number <=0 || max_requests <= 0) 34 | throw std::exception();// 如果线程数量非法 抛出异常 35 | if(!m_threads) 36 | throw std::exception();// 如果指针为空 37 | for(int i = 0; i < thread_number; ++i){ 38 | cout<<"create the "< 53 | threadpool::~threadpool(){ 54 | delete[] m_threads; 55 | m_stop = true; 56 | } 57 | 58 | template 59 | bool threadpool::append(T *request){ 60 | // 在请求队列中,每一次需要判断当前请求队列的大小于最大请求书的关系。 61 | m_queuelocker.lock(); 62 | if(m_workkque.size() > m_max_requests){ 63 | m_queuelocker.unlock(); 64 | return false; 65 | } 66 | m_workqueue.push_back(request); 67 | m_queuelocker.unlock(); 68 | m_queuestat.post();// 需要处理的任务数量+1,信号量+1; 69 | return true; 70 | } 71 | 72 | template 73 | void* threadpool::worker(void *arg){ 74 | threadpool* pool = (threadpool*) arg; 75 | pool->run(); 76 | return pool; 77 | } 78 | 79 | template 80 | void threadpool::run(){ 81 | while(!m_stop){ 82 | // 需要处理的任务数量-1,信号量-1 83 | m_queuestat.wait(); 84 | m_queuelocker.lock(); 85 | if(m_workqueue.empty()){ 86 | // 如果说此时队列已经为空了直接解锁 87 | m_queuelocker.unlock(); 88 | continue; 89 | } 90 | T *request = m_workqueue.front(); 91 | m_workqueue.pop_front(); 92 | m_queuelocker.unlock(); 93 | if(!request) continue; 94 | 95 | // 数据库链接操作 96 | connectionRAII mysqlcon(&request->mysql, m_connPool); 97 | request->process(); 98 | } 99 | } 100 | 101 | 102 | #endif -------------------------------------------------------------------------------- /timer/README.md: -------------------------------------------------------------------------------- 1 | 2 | 定时器处理非活动连接 3 | =============== 4 | 由于非活跃连接占用了连接资源,严重影响服务器的性能,通过实现一个服务器定时器,处理这种非活跃连接,释放连接资源。利用alarm函数周期性地触发SIGALRM信号,该信号的信号处理函数利用管道通知主循环执行定时器链表上的定时任务. 5 | > * 统一事件源 6 | > * 基于升序链表的定时器 7 | > * 处理非活动连接 8 | -------------------------------------------------------------------------------- /timer/lst_timer.h: -------------------------------------------------------------------------------- 1 | #ifndef LST_TIMER 2 | #define LST_TIMER 3 | 4 | #include 5 | #include "../log/log.h" 6 | 7 | class util_timer; 8 | struct client_data 9 | { 10 | sockaddr_in address; 11 | int sockfd; 12 | util_timer *timer; 13 | }; 14 | 15 | class util_timer 16 | { 17 | public: 18 | util_timer() : prev(NULL), next(NULL) {} 19 | 20 | public: 21 | time_t expire; 22 | void (*cb_func)(client_data *); 23 | client_data *user_data; 24 | util_timer *prev; 25 | util_timer *next; 26 | }; 27 | 28 | class sort_timer_lst 29 | { 30 | public: 31 | sort_timer_lst() : head(NULL), tail(NULL) {} 32 | ~sort_timer_lst() 33 | { 34 | util_timer *tmp = head; 35 | while (tmp) 36 | { 37 | head = tmp->next; 38 | delete tmp; 39 | tmp = head; 40 | } 41 | } 42 | void add_timer(util_timer *timer) 43 | { 44 | if (!timer) 45 | { 46 | return; 47 | } 48 | if (!head) 49 | { 50 | head = tail = timer; 51 | return; 52 | } 53 | if (timer->expire < head->expire) 54 | { 55 | timer->next = head; 56 | head->prev = timer; 57 | head = timer; 58 | return; 59 | } 60 | add_timer(timer, head); 61 | } 62 | void adjust_timer(util_timer *timer) 63 | { 64 | if (!timer) 65 | { 66 | return; 67 | } 68 | util_timer *tmp = timer->next; 69 | if (!tmp || (timer->expire < tmp->expire)) 70 | { 71 | return; 72 | } 73 | if (timer == head) 74 | { 75 | head = head->next; 76 | head->prev = NULL; 77 | timer->next = NULL; 78 | add_timer(timer, head); 79 | } 80 | else 81 | { 82 | timer->prev->next = timer->next; 83 | timer->next->prev = timer->prev; 84 | add_timer(timer, timer->next); 85 | } 86 | } 87 | void del_timer(util_timer *timer) 88 | { 89 | if (!timer) 90 | { 91 | return; 92 | } 93 | if ((timer == head) && (timer == tail)) 94 | { 95 | delete timer; 96 | head = NULL; 97 | tail = NULL; 98 | return; 99 | } 100 | if (timer == head) 101 | { 102 | head = head->next; 103 | head->prev = NULL; 104 | delete timer; 105 | return; 106 | } 107 | if (timer == tail) 108 | { 109 | tail = tail->prev; 110 | tail->next = NULL; 111 | delete timer; 112 | return; 113 | } 114 | timer->prev->next = timer->next; 115 | timer->next->prev = timer->prev; 116 | delete timer; 117 | } 118 | void tick() 119 | { 120 | if (!head) 121 | { 122 | return; 123 | } 124 | //printf( "timer tick\n" ); 125 | LOG_INFO("%s", "timer tick"); 126 | Log::get_instance()->flush(); 127 | time_t cur = time(NULL); 128 | util_timer *tmp = head; 129 | while (tmp) 130 | { 131 | if (cur < tmp->expire) 132 | { 133 | break; 134 | } 135 | tmp->cb_func(tmp->user_data); 136 | head = tmp->next; 137 | if (head) 138 | { 139 | head->prev = NULL; 140 | } 141 | delete tmp; 142 | tmp = head; 143 | } 144 | } 145 | 146 | private: 147 | void add_timer(util_timer *timer, util_timer *lst_head) 148 | { 149 | util_timer *prev = lst_head; 150 | util_timer *tmp = prev->next; 151 | while (tmp) 152 | { 153 | if (timer->expire < tmp->expire) 154 | { 155 | prev->next = timer; 156 | timer->next = tmp; 157 | tmp->prev = timer; 158 | timer->prev = prev; 159 | break; 160 | } 161 | prev = tmp; 162 | tmp = tmp->next; 163 | } 164 | if (!tmp) 165 | { 166 | prev->next = timer; 167 | timer->prev = prev; 168 | timer->next = NULL; 169 | tail = timer; 170 | } 171 | } 172 | 173 | private: 174 | util_timer *head; 175 | util_timer *tail; 176 | }; 177 | 178 | #endif 179 | --------------------------------------------------------------------------------