├── .github └── workflows │ └── ccpp.yml ├── README.md ├── include ├── Epoll.h ├── HttpData.h ├── HttpParse.h ├── HttpRequest.h ├── HttpResponse.h ├── MutexLock.h ├── Server.h ├── Socket.h ├── ThreadPool.h ├── Timer.h ├── Util.h ├── main.h └── noncopyable.h ├── pages ├── 403.html ├── 404.html └── index.html ├── src ├── Epoll.cpp ├── HttpData.cpp ├── HttpParse.cpp ├── HttpRequest.cpp ├── HttpResponse.cpp ├── Server.cpp ├── Socket.cpp ├── ThreadPool.cpp ├── Timer.cpp ├── Util.cpp ├── main.cpp ├── threadpool2.cpp ├── threadpool3.cpp └── 线程模型.md ├── test ├── processpool.c └── threadpool.cpp ├── 整体设计.md ├── 更新说明.md └── 测试.md /.github/workflows/ccpp.yml: -------------------------------------------------------------------------------- 1 | name: C/C++ CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: configure 17 | run: ./configure 18 | - name: make 19 | run: make 20 | - name: make check 21 | run: make check 22 | - name: make distcheck 23 | run: make distcheck 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A C++ Lightweight Web Server 2 | 3 | [![license](https://camo.githubusercontent.com/b0224997019dec4e51d692c722ea9bee2818c837/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f6d6173686170652f6170697374617475732e737667)](https://opensource.org/licenses/MIT) [![Build Status](https://camo.githubusercontent.com/8c124d402beec8fe8fa404add68e7d8028ab6719/68747470733a2f2f7472617669732d63692e6f72672f4d617276696e4c652f5765625365727665722e7376673f6272616e63683d6d6173746572)](https://travis-ci.org/MarvinLe/WebServer) 4 | 5 | ## 简介 6 | 7 | 这是一个轻量级的 Web 服务器,目前支持 GET、HEAD 方法处理静态资源。并发模型选择: 线程池+Reactor+非阻塞方式运行。 8 | 9 | 测试页面: 10 | 11 | ------ 12 | 13 | | Part Ⅰ | Part Ⅱ | 14 | | ------------ | ---------------- | 15 | | [整体设计](https://github.com/rongweihe/WebServer/blob/master/%E6%95%B4%E4%BD%93%E8%AE%BE%E8%AE%A1.md) | [性能测试分析](https://github.com/rongweihe/WebServer/blob/master/%E6%B5%8B%E8%AF%95.md) | 16 | 17 | ------ 18 | 19 | ## 开发部署环境 20 | 21 | - 操作系统: Ubuntu 5.4.0-6 Ubuntu16.04.9 22 | - 编译器: g++ version 5.4.0 20160609 23 | - 版本控制: git 24 | - 编辑器: Vim 25 | - 压测工具:[WebBench](https://github.com/EZLippi/WebBench) 26 | 27 | ## Usage 28 | 29 | ``` 30 | cmake . && make 31 | 32 | ./webserver [-p port] [-t thread_numbers] [-r website_root_path] [-d daemon_run] 33 | ``` 34 | 35 | ## 核心功能及技术 36 | 37 | - 状态机解析 HTTP 请求,目前支持 HTTP GET、HEAD 方法 38 | - 使用 priority 队列实现的最小堆结构管理定时器,使用标记删除 39 | - 使用 epoll + 非阻塞IO + 边缘触发(ET) 实现高并发处理请求,使用 Reactor 编程模型 40 | - epoll 使用 EPOLLONESHOT 保证一个 socket 连接在任意时刻都只被一个线程处理 41 | - 使用多线程充分利用多核 CPU,并使用线程池避免线程频繁创建销毁的开销 42 | - 为减少内存泄漏的可能,使用智能指针等 RAII 机制 43 | 44 | ## 开发计划 45 | 46 | - 添加异步日志系统,记录服务器运行状态 47 | - 自动化构建: cmake 48 | - 集成开发工具: CLion 49 | 50 | ## 参考 51 | - https://github.com/linyacool/WebServer 52 | -------------------------------------------------------------------------------- /include/Epoll.h: -------------------------------------------------------------------------------- 1 | /******************************************************** 2 | *@file 封装Epoll.h 3 | *@Author rongweihe 4 | *@Data 2019/04/18 5 | *********************************************************/ 6 | #ifndef EPOLL_H_INCLUDED 7 | #define EPOLL_H_INCLUDED 8 | 9 | #include "HttpData.h" 10 | #include "Socket.h" 11 | #include "Timer.h" 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | class Epoll 20 | { 21 | public: 22 | 23 | static int init(int max_events); 24 | 25 | static int addfd(int epoll_fd, int fd, __uint32_t events, std::shared_ptr); 26 | 27 | static int modfd(int epoll_fd, int fd, __uint32_t events, std::shared_ptr); 28 | 29 | static int delfd(int epoll_fd, int fd, __uint32_t events); 30 | 31 | static std::vector> 32 | poll(const ServerSocket &serverSocket, int max_event, int timeout); 33 | 34 | static void handleConnection(const ServerSocket &serverSocket); 35 | 36 | public: 37 | static std::unordered_map> http_data_map_;//类成员变量下划线结尾 38 | static const int kMaxEvents;//const 变量为k开头,后跟大写开头单词 39 | static epoll_event *events_; 40 | static TimerManager timer_manager_; 41 | const static __uint32_t kDefaultEvents; 42 | }; 43 | 44 | #endif // EPOLL_H_INCLUDED 45 | -------------------------------------------------------------------------------- /include/HttpData.h: -------------------------------------------------------------------------------- 1 | /******************************************************** 2 | *@file 封装HttpData类 3 | *@Author rongweihe 4 | *@Data 2019/04/18 5 | *********************************************************/ 6 | #ifndef HTTPDATA_H_INCLUDED 7 | #define HTTPDATA_H_INCLUDED 8 | 9 | #include "HttpParse.h" 10 | #include "HttpResponse.h" 11 | #include "Socket.h" 12 | #include "Timer.h" 13 | #include 14 | 15 | class TimerNode; 16 | 17 | // C++11 新特性之十:enable_shared_from_this 18 | // std::enable_shared_from_this 能让一个对象(假设其名为 t ,且已被一个 std::shared_ptr 对象 pt 管理)安全地生成其他额外的 19 | // std::shared_ptr 实例(假设名为 pt1, pt2, ... ) ,它们与 pt 共享对象 t 的所有权。 20 | class HttpData : public std::enable_shared_from_this { 21 | public: 22 | HttpData() : epoll_fd(-1) {} 23 | 24 | public: 25 | std::shared_ptr request_; 26 | std::shared_ptr response_; 27 | std::shared_ptr clientSocket_; 28 | int epoll_fd; 29 | void closeTime(); 30 | void setTimer(std::shared_ptr); 31 | 32 | private: 33 | std::weak_ptr weak_ptr_timer_; 34 | }; 35 | #endif // HTTPDATA_H_INCLUDED 36 | -------------------------------------------------------------------------------- /include/HttpParse.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongweihe/WebServer/02ff8a5d59bd3d2de2db73776e57950a97a07bed/include/HttpParse.h -------------------------------------------------------------------------------- /include/HttpRequest.h: -------------------------------------------------------------------------------- 1 | /******************************************************** 2 | *@file 封装HttpRequest类 3 | *@Author rongweihe 4 | *@Data 2019/03/31 5 | *********************************************************/ 6 | #ifndef HTTPREQUEST_H_INCLUDED 7 | #define HTTPREQUEST_H_INCLUDED 8 | #include 9 | #include 10 | 11 | class HttpRequest; 12 | 13 | std::ostream &operator<<(std::ostream &, const HttpRequest &); 14 | 15 | struct HttpRequest 16 | { 17 | friend std::ostream &operator<<(std::ostream &, const HttpRequest &); 18 | enum HTTP_VERSION 19 | { 20 | HTTP_10 = 0, 21 | HTTP_11, 22 | VERSION_NOT_SUPPORT 23 | }; 24 | enum HTTP_METHOD 25 | { 26 | GET = 0, 27 | POST, 28 | PUT, 29 | DELETE, 30 | TRACE, 31 | OPTIONS, 32 | CONNECT, 33 | PATCH, 34 | METHOD_NOT_SUPPORT 35 | }; 36 | enum HTTP_HEADER 37 | { 38 | Host = 0, 39 | User_Agent, 40 | Connection, 41 | Accept_Encoding, 42 | Accept_Language, 43 | Accept, 44 | Cache_Control, 45 | Upgrade_Insecure_Requests 46 | }; 47 | struct EnumClassHash 48 | { 49 | template 50 | std::size_t operator()(T t) const 51 | { 52 | return static_cast(t); 53 | } 54 | }; 55 | static std::unordered_map header_map; 56 | 57 | HttpRequest(std::string url = std::string(""), HTTP_METHOD method = METHOD_NOT_SUPPORT, 58 | HTTP_VERSION version = VERSION_NOT_SUPPORT) : 59 | mMethod(method), mVersion(version), mUrl(url), mContent(nullptr), 60 | mHeaders(std::unordered_map()) {}; 61 | 62 | HTTP_METHOD mMethod; 63 | HTTP_VERSION mVersion; 64 | std::string mUrl; 65 | char *mContent; 66 | std::unordered_map mHeaders; 67 | }; 68 | #endif // HTTPREQUEST_H_INCLUDED 69 | -------------------------------------------------------------------------------- /include/HttpResponse.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongweihe/WebServer/02ff8a5d59bd3d2de2db73776e57950a97a07bed/include/HttpResponse.h -------------------------------------------------------------------------------- /include/MutexLock.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongweihe/WebServer/02ff8a5d59bd3d2de2db73776e57950a97a07bed/include/MutexLock.h -------------------------------------------------------------------------------- /include/Server.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongweihe/WebServer/02ff8a5d59bd3d2de2db73776e57950a97a07bed/include/Server.h -------------------------------------------------------------------------------- /include/Socket.h: -------------------------------------------------------------------------------- 1 | /******************************************************** 2 | *@file 封装套接字类 3 | *@Author rongweihe 4 | *@Date 2019/03/31 5 | *********************************************************/ 6 | #ifndef SOCKET_H_INCLUDED 7 | #define SOCKET_H_INCLUDED 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | class ClientSocket; 17 | void setReusePort(int fd); 18 | 19 | class ServerSocket{ 20 | public: 21 | ServerSocket(int port = 8080,const char *ip = nullptr); 22 | ~ServerSocket(); 23 | void bind(); 24 | void listen(); 25 | int accept(ClientSocket &) const; 26 | void close(); 27 | public: 28 | sockaddr_in m_addr; 29 | int listen_fd; 30 | int epoll_fd; 31 | int m_port; 32 | const char *m_ip; 33 | }; 34 | 35 | class ClientSocket { 36 | public: 37 | ClientSocket() {fd = -1;} 38 | void close(); 39 | ~ClientSocket(); 40 | socklen_t m_len; 41 | sockaddr_in m_addr; 42 | int fd; 43 | }; 44 | #endif // SOCKET_H_INCLUDED 45 | -------------------------------------------------------------------------------- /include/ThreadPool.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongweihe/WebServer/02ff8a5d59bd3d2de2db73776e57950a97a07bed/include/ThreadPool.h -------------------------------------------------------------------------------- /include/Timer.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongweihe/WebServer/02ff8a5d59bd3d2de2db73776e57950a97a07bed/include/Timer.h -------------------------------------------------------------------------------- /include/Util.h: -------------------------------------------------------------------------------- 1 | /******************************************************** 2 | *@file 封装参数类 3 | *@Author rongweihe 4 | * 5 | *********************************************************/ 6 | #ifndef UTIL_H_INCLUDED 7 | #define UTIL_H_INCLUDED 8 | #include 9 | using namespace std; 10 | 11 | std::string <rim(string &); 12 | std::string &rtrim(string &); 13 | std::string &trim(string &); 14 | 15 | int setnonblocking(int fd);/// 16 | void handle_for_sigpipe();/// 17 | int check_base_path(char *basePath); /// 18 | 19 | #endif // UTIL_H_INCLUDED 20 | -------------------------------------------------------------------------------- /include/main.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongweihe/WebServer/02ff8a5d59bd3d2de2db73776e57950a97a07bed/include/main.h -------------------------------------------------------------------------------- /include/noncopyable.h: -------------------------------------------------------------------------------- 1 | #ifndef NONCOPYABLE_H_INCLUDED 2 | #define NONCOPYABLE_H_INCLUDED 3 | 4 | class noncopyable { 5 | public: 6 | noncopyable(const noncopyable&) = delete; 7 | noncopyable& operator=(const noncopyable&) = delete; 8 | protected: 9 | noncopyable()=default; 10 | ~noncopyable()=default; 11 | }; 12 | 13 | #endif // NONCOPYABLE_H_INCLUDED 14 | -------------------------------------------------------------------------------- /pages/403.html: -------------------------------------------------------------------------------- 1 | 2 | 403 Forbidden 3 | 4 |

403 Forbidden

5 |
Mini Webserver/V1 (Ubuntu)
6 | 7 | -------------------------------------------------------------------------------- /pages/404.html: -------------------------------------------------------------------------------- 1 | 2 | 404 Not Found 3 | 4 |

404 Not Found

5 |
Mini Webserver/V1 (Ubuntu)
6 | 7 | -------------------------------------------------------------------------------- /pages/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Welcome to Mini! 5 | 12 | 13 | 14 |

Welcome to Mini !

15 |

If you see this page, the Mini webserver is successfully installed and 16 | working.

17 | 18 |

For online documentation and support please refer to 19 | Mini WebServer.
20 | 21 |

Thank you for using Mini WebServer.

22 | 23 | -------------------------------------------------------------------------------- /src/Epoll.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongweihe/WebServer/02ff8a5d59bd3d2de2db73776e57950a97a07bed/src/Epoll.cpp -------------------------------------------------------------------------------- /src/HttpData.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongweihe/WebServer/02ff8a5d59bd3d2de2db73776e57950a97a07bed/src/HttpData.cpp -------------------------------------------------------------------------------- /src/HttpParse.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongweihe/WebServer/02ff8a5d59bd3d2de2db73776e57950a97a07bed/src/HttpParse.cpp -------------------------------------------------------------------------------- /src/HttpRequest.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongweihe/WebServer/02ff8a5d59bd3d2de2db73776e57950a97a07bed/src/HttpRequest.cpp -------------------------------------------------------------------------------- /src/HttpResponse.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongweihe/WebServer/02ff8a5d59bd3d2de2db73776e57950a97a07bed/src/HttpResponse.cpp -------------------------------------------------------------------------------- /src/Server.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongweihe/WebServer/02ff8a5d59bd3d2de2db73776e57950a97a07bed/src/Server.cpp -------------------------------------------------------------------------------- /src/Socket.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongweihe/WebServer/02ff8a5d59bd3d2de2db73776e57950a97a07bed/src/Socket.cpp -------------------------------------------------------------------------------- /src/ThreadPool.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongweihe/WebServer/02ff8a5d59bd3d2de2db73776e57950a97a07bed/src/ThreadPool.cpp -------------------------------------------------------------------------------- /src/Timer.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongweihe/WebServer/02ff8a5d59bd3d2de2db73776e57950a97a07bed/src/Timer.cpp -------------------------------------------------------------------------------- /src/Util.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongweihe/WebServer/02ff8a5d59bd3d2de2db73776e57950a97a07bed/src/Util.cpp -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongweihe/WebServer/02ff8a5d59bd3d2de2db73776e57950a97a07bed/src/main.cpp -------------------------------------------------------------------------------- /src/threadpool2.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongweihe/WebServer/02ff8a5d59bd3d2de2db73776e57950a97a07bed/src/threadpool2.cpp -------------------------------------------------------------------------------- /src/threadpool3.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongweihe/WebServer/02ff8a5d59bd3d2de2db73776e57950a97a07bed/src/threadpool3.cpp -------------------------------------------------------------------------------- /src/线程模型.md: -------------------------------------------------------------------------------- 1 | ## 为什么需要多线程 2 | 3 | 进程切换上下文的代价是比较高的,幸运的是,有一种轻量级的模型可以处理多用户连接请求,这就是线程模型。这一讲里,我们就来了解一下线程模型。 4 | 5 | 线程(thread)是运行在进程中的一个逻辑流,现代操作系统都允许在单进程中运行多个线程。 6 | 7 | 线程由操作系统内核管理。每个线程都有自己的上下文(content),包括一个可以唯一标识线程的 ID、栈、程序计数器、寄存器等。 8 | 9 | 在同一个进程中,所有的线程共享该进程的整个虚拟地址空间,包括代码、数据、堆、共享库等。 10 | 11 | 实际上,从 Linux 内核角度来讲,可以认为它并没有线程这个概念,无论是我们所说的线程,还是进程,对于 Linux 而言,都属于 task,因此无论是进程还是线程,都拥有唯一属于自己的 task_struct 。实际上我们所谓的线程,更像是 task 这个概念,有的进程有一个 task,就叫做单线程进程;有的进程有多个线程,就叫做多线程进程。 12 | 13 | 每一个进程一开始都会产生一个线程,一般称为主线程,主线程可以在产生子线程,这样的主线程-子线程可以叫做一个对等线程。 14 | 15 | **你可能会问,既然可以使用多进程来处理并发,为什么还要使用多线程模式呢?** 16 | 17 | 简单来说,在同一个进程下,线程上下文切换的开销要比进程小得多。怎么理解线程的上下文呢? 18 | 19 | 我们的代码被 CPU 调度执行的时候,是需要一些数据支撑的,比如程序计数器告诉 CPU 代码执行到哪里了?寄存器里存了当前计算的一些中间值,内存里放置了一些当前用到的变量等。从一个计算场景切换到另外一个计算场景,程序计数器、寄存器等这些值重新载入新场景的值,就是线程的上下文切换。 20 | 21 | ## POSIX 线程模型 22 | 23 | POSIX 线程是现代 UNIX 系统提供的处理线程的标准接口。POSIX 定义的线程函数大约有 60 多个,这些函数可以帮助我们创建线程、回收线程。接下来我们先看一个简单的例子程序。 24 | 25 | ```c 26 | #include 27 | #include 28 | #include 29 | 30 | int another_shared = 0; 31 | 32 | void thread_run(void *arg) { 33 | int *calc = (int *) arg; 34 | printf("hello, world, tid == %d \n", pthread_self()); 35 | for (int i = 0; i < 100; i++) { 36 | *calc += 1; 37 | another_shared += 1; 38 | } 39 | } 40 | int main(int argc, char **argv) { 41 | int calc; 42 | pthread_t tid1; 43 | pthread_t tid2; 44 | 45 | pthread_create(&tid1, NULL, thread_run, &calc); 46 | pthread_create(&tid2, NULL, thread_run, &calc); 47 | 48 | pthread_join(tid1, NULL); 49 | pthread_join(tid2, NULL); 50 | printf("calculator is %d \n", calc); 51 | printf("another_shared is %d \n", another_shared); 52 | } 53 | ``` 54 | 55 | 上面的程序中,主线程依次创建了两个子线程,然后等待这两个子线程处理完毕之后终止。每个子线程都在对两个共享变量进行计算,最后在主线程中打印出最后的计算结果。 56 | 57 | 程序的第 18 和 19 行分别调用了 pthread_create 创建了两个线程,每个线程的入口都是 thread_run 函数,这里我们使用了 calculator 这个全局变量,并且通过传地址指针的方式,将这个值传给了 thread_run 函数。 58 | 59 | 当调用 pthread_create 结束,子线程会立即执行,主线程在此后调用了 pthread_join 函数等待子线程结束。 60 | 61 | 运行这个程序,很幸运,计算的结果是正确的。 62 | 63 | ![](https://cdn.jsdelivr.net/gh/rongweihe/ImageHost01/epoll/thread-02.png) 64 | 65 | ## 主要线程函数 66 | 67 | ### 创建线程 68 | 69 | 正如前面看到,通过调用 pthread_create 函数来创建一个线程。这个函数的原型如下: 70 | 71 | ```c++ 72 | 73 | int pthread_create(pthread_t *tid, const pthread_attr_t *attr, 74 |            void *(*func)(void *), void *arg); 75 | 76 | 返回:若成功则为0,若出错则为正的Exxx值 77 | ``` 78 | 79 | 每个线程都有一个线程 ID(tid)唯一来标识,其数据类型为 pthread_t,一般是 unsigned int。pthread_create 函数的第一个输出参数 tid 就是代表了线程 ID,如果创建线程成功,tid 就返回正确的线程 ID。 80 | 81 | 每个线程都会有很多属性,比如优先级、是否应该成为一个守护进程等,这些值可以通过 pthread_attr_t 来描述,一般我们不会特殊设置,可以直接指定这个参数为 NULL。 82 | 83 | 第三个参数为新线程的入口函数,该函数可以接收一个参数 arg,类型为指针,如果我们想给线程入口函数传多个值,那么需要把这些值包装成一个结构体,再把这个结构体的地址作为 pthread_create 的第四个参数,在线程入口函数内,再将该地址转为该结构体的指针对象。 84 | 85 | 简单来说,第三个参数:**你想让线程来干什么**?第四个参数:**干了这件事之后结果保存到哪** 86 | 87 | 在新线程的入口函数内,可以执行 pthread_self 函数返回线程 tid。 88 | 89 | ```c 90 | pthread_t pthread_self(void) 91 | ``` 92 | 93 | ### 终止线程 94 | 95 | 终止一个线程最直接的方法是在父线程内调用以下函数: 96 | 97 | ```c 98 | void pthread_exit(void *status) 99 | ``` 100 | 101 | 当调用这个函数之后,父线程会等待其他所有的子线程终止,之后父线程自己终止。 102 | 103 | 当然,如果一个子线程入口函数直接退出了,那么子线程也就自然终止了。所以,绝大多数的子线程执行体都是一个无限循环。 104 | 105 | 也可以通过调用 pthread_cancel 来主动终止一个子线程,和 pthread_exit 不同的是,它可以指定某个子线程终止。 106 | 107 | ```c 108 | int pthread_cancel(pthread_t tid) 109 | ``` 110 | 111 | ### 回收已终止线程的资源 112 | 113 | 我们可以通过调用 pthread_join 回收已终止线程的资源: 114 | 115 | ```c 116 | int pthread_join(pthread_t tid, void ** thread_return) 117 | ``` 118 | 119 | 当调用 pthread_join 时,主线程会阻塞,直到对应 tid 的子线程自然终止。和 pthread_cancel 不同的是,它不会强迫子线程终止。 120 | 121 | ### 分离线程 122 | 123 | 一个线程的重要属性是可结合的,或者是分离的。一个可结合的线程是能够被其他线程杀死和回收资源的;而一个分离的线程不能被其他线程杀死或回收资源。一般来说,默认的属性是可结合的。我们可以通过调用 pthread_detach 函数可以分离一个线程: 124 | 125 | ```c 126 | int pthread_detach(pthread_t tid) 127 | ``` 128 | 129 | 在高并发的例子里,每个连接都由一个线程单独处理,在这种情况下,服务器程序并不需要对每个子线程进行终止,这样的话,每个子线程可以在入口函数开始的地方,把自己设置为分离的,这样就能在它终止后自动回收相关的线程资源了,就不需要调用 pthread_join 函数了。 -------------------------------------------------------------------------------- /test/processpool.c: -------------------------------------------------------------------------------- 1 | 2 | /************************************************************************************************ 3 | < 半同步/半异步并发模式的进程池实现 4 | < filename: processpool.h 5 | < 特征 6 | < 1. 为了避免在父子进程之间传递文件描述符,将接受新连接的操作放到子进程中 7 | < 2. 一个客户连接上的的所有任务始终是由一个字进程来处理的 8 | < Author:rongweihe 9 | < Mail:rongweihe1995@gmail.com 10 | < Date: 2019-03-20 11 | ************************************************************************************************/ 12 | #ifndef processpool_H 13 | #define processpool_H 14 | #include 15 | #include 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 | #include 29 | #include 30 | #include 31 | 32 | /*描述一个子进程的类,m_pid 是目标子进程的 PID,m_pipefd 是父进程和子进程通信用的管道*/ 33 | class process 34 | { 35 | public: 36 | process() : m_pid( -1 ) {} 37 | public: 38 | pid_t m_pid; 39 | int m_pipefd[2]; 40 | }; 41 | /*进程池类,将它定义为模板类是为了代码复用,其模板参数是处理逻辑任务的类*/ 42 | template < typename T> 43 | class processpool 44 | { 45 | private: 46 | /*将构造函数定义为私有的,因此我们只能通过后面的 create 静态函数来创建 processpool 类*/ 47 | processpool( int listenfd,int process_number = 8 ); 48 | public: 49 | /*单体模式,以保证程序最多创建一个 processpool 实例,这是程序正确处理信号的必要条件*/ 50 | static processpool< T >* creat( int listenfd,int process_number = 8 ) 51 | { 52 | if ( !m_instance ) 53 | { 54 | m_instance = new processpool < T >(listenfd,process_number) 55 | } 56 | return m_instance; 57 | } 58 | ~processpool() 59 | { 60 | delete [] m_sub_process; 61 | } 62 | /*启动进程池*/ 63 | void run(); 64 | private: 65 | void setup_sig_pipe(); 66 | void run_parent(); 67 | void run_child(); 68 | private: 69 | /*进程池允许的最大子进程数量*/ 70 | static const int MAX_PROCESS_NUMBER = 16; 71 | 72 | /*每个子进程最多能处理的客户数量*/ 73 | static const int MAX_USER_PER_PROCESS = 65536; 74 | 75 | /*epoll 最多能处理的事件数*/ 76 | static const int MAX_EVENT_NUMBER = 10000; 77 | 78 | /*进程池中的进程数*/ 79 | int m_process_number; 80 | 81 | /*子进程在池中的序号从 0 开始*/ 82 | int m_idx; 83 | 84 | /*每个进程都有一个 epoll 内核事件表,用 m_epollfd 标识*/ 85 | int m_epollfd; 86 | 87 | /*监听 socket */ 88 | int m_listenfd; 89 | 90 | /*子进程通过 m_stop 来决定是否停止运行*/ 91 | int m_stop; 92 | 93 | /*保存所有子进程的描述信息*/ 94 | process* m_sub_process; 95 | 96 | /*进程池静态实例*/ 97 | static processpool* m_instance; 98 | }; 99 | template 100 | processpool* processpool::m_instance = NULL; 101 | 102 | /*用于处理信号的管道,以实现统一事件源,后面称之为信号管道*/ 103 | static int sig_pipefd[2]; 104 | static int setnonblocking(int fd) 105 | { 106 | int old_option = fcntl(fd,F_GETEL); 107 | int new_option = old_option | O_NONBLOCK; 108 | fcntl(fd,F_SETFL,new_option); 109 | return old_option; 110 | } 111 | static void addfd(int epollfd,int fd) 112 | { 113 | epoll_event event; 114 | event.data.fd = fd; 115 | /*采用epoll的EPOLLET(边沿触发)模式*/ 116 | event.events = EPOLLIN | EPOLLET; 117 | epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&event); 118 | setnonblocking(fd); 119 | } 120 | /*从 epollfd 标识的 epoll 内核事件表中删除 fd 上的所有注册事件*/ 121 | static void removefd(int epollfd,int fd) 122 | { 123 | epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,0); 124 | close(fd); 125 | } 126 | static void sig_handler(int sig) ///? 127 | { 128 | int save_errno = errno; 129 | int msg = sig; 130 | send(sig_pipefd[1],(char*)&msg,1,0); 131 | errno = save_errno; 132 | } 133 | static void addsig(int sig,void(handler)(int),bool restart = true) 134 | { 135 | /*信号安装函数sigaction(int signum,const struct sigaction *act,struct sigaction *oldact)*/ 136 | struct sigaction sa; 137 | memset(&sa, '\0',sizeof(sa)); 138 | sa.sa_handler = handler; 139 | if(restart) 140 | { 141 | sa.sa_flags | = SA_RESTART; 142 | } 143 | sigfillset(&sa.sa_mask); 144 | assert(sigaction(sig,&sa,NNULL) != -1); 145 | } 146 | /*进程池构造函数,参数 listenfd 是监听 socket,他必须在创建进程池之前被创建,否则子进程无法直接引用它,参数 process_number 指定进程池中子进程的数量*/ 147 | template 148 | processpool::processpool(int listenfd,int process_number) 149 | :m_listenfd(listenfd),m_process_number(process_number),m_idx(-1),m_stop(false) 150 | { 151 | assert( (process_number >0) && (process_number <= MAX_PROCESS_NUMBER) ); 152 | m_sub_process = new process[process_number]; 153 | assert(m_sub_process); 154 | /*创建 process_number 个子进程,并建立它们和父进程之间的管道*/ 155 | for(int i=0; i< process_number; ++i) 156 | { 157 | int ret = socketpair(PF_UNIX,SOCK_STREAM,0,m_sub_process[i].m_pipefd); 158 | assert(ret==0); 159 | m_sub_process[i].m_pid = fork(); 160 | assert(m_sub_process[i].m_pid>=0); 161 | if(m_sub_process[i].m_pid>0) 162 | { 163 | close(m_sub_process[i].m_pipefd[1]); 164 | continue; 165 | } 166 | else 167 | { 168 | close(m_sub_process[i].m_pipefd[0]); 169 | m_idx = i;///子进程赋予自己的index 170 | break;///这才是进程池的精华子进程需要退出循环,不然子进程也会fork 171 | } 172 | } 173 | } 174 | 175 | /*统一事件源,这个pipe不是用来和父进程通信的pipe*/ 176 | template 177 | void processpool::setup_sig_pipe() 178 | { 179 | /*创建epoll事件监听表和信号管道*/ 180 | m_epollfd = epoll_creat(5); 181 | assert( m_epollfd !=-1 ); 182 | int ret = socketpair(PF_UNIX,SOCK_STREAM,0,sig_pipefd); 183 | assert(ret!=-1); 184 | setnonblocking(sig_pipefd[1]); 185 | addfd(m_epollfd,sig_pipefd[0]); 186 | /*设置信号处理函数*/ 187 | addsig(SIGCHLD,sig_handler); 188 | addsig(SIGTERM,sig_handler); 189 | addsig(SIGINT,sig_handler); 190 | addsig(SIGPIPE,SIG_IGN); 191 | } 192 | /*父进程中 m_idx = -1,子进程 m_idx 大于等于0 我们要据此判断接下来要运行的是父进程代码还是子进程代码*/ 193 | template 194 | void processpool::run() 195 | { 196 | if(m_idx!=-1) 197 | { 198 | run_child(); 199 | return; 200 | } 201 | run_parent(); 202 | } 203 | void processpool::run_child() 204 | { 205 | setup_sig_pipe(); 206 | /*每个子进程都通过其在进程池中的序号值 m_idx 找到与父进程通信的管道*/ 207 | int pipefd = m_sub_process[m_idx].m_pipefd[1]; 208 | /*子进程需要监听管道文件描述符 pipefd,因为父进程将通过它来通知子进程 accept 新连接*/ 209 | addfd(m_epollfd,pipefd); 210 | epoll_event events[MAX_EVENT_NUMBER]; 211 | T* users = new T[MAX_USER_PER_PROCESS]; 212 | assert(users); 213 | int number = 0; 214 | int ret = -1; 215 | while(!m_stop) 216 | { 217 | /*监听事件*/ 218 | number = epoll_wait(m_epollfd,events,MAX_EVENT_NUMBER,-1); 219 | if( number <0 && errno!=EINTR ) 220 | { 221 | printf("epoll error!\n"); 222 | break; 223 | } 224 | for(int i=0; i 0 回收指定ID的子进程 281 | * 282 | */ 283 | while ( ( pid = waitpid( -1, &stat, WNOHANG ) ) > 0 ) 284 | { 285 | continue; 286 | } 287 | break; 288 | } 289 | case SIGTERM: 290 | case SIGINT: 291 | { 292 | m_stop = true; 293 | break; 294 | } 295 | default: 296 | { 297 | break; 298 | } 299 | } 300 | } 301 | } 302 | } 303 | else if(events[i].events & EPOLLIN) 304 | { 305 | users[sockfd].process(); 306 | } 307 | else 308 | { 309 | continue; 310 | } 311 | } 312 | } 313 | delete [] users; 314 | users = NULL; 315 | close(pipefd); 316 | close(m_epollfd); 317 | //close( m_listenfd ); 318 | /*我们将上面这句话注释掉,以提醒读者:应该由m_listenfd的创建者来关闭这个文件描述符,及所谓的“对象由哪个函数创建,就应该由哪个函数销毁”*/ 319 | } 320 | template 321 | void processpool::run_parent() 322 | { 323 | setup_sig_pipe(); 324 | /*父进程监听 m_listenfd */ 325 | addfd(m_epollfd,m_listenfd); 326 | epoll_event events[MAX_EVENT_NUMBER]; 327 | int sub_porcess_counter = 0; 328 | int new_conn = 1; 329 | int number = 0; 330 | int ret = -1; 331 | while(!m_stop) 332 | { 333 | number = epoll_wait(m_epollfd,events,MAX_EVENT_NUMBER,-1); 334 | if( (number <0 ) && (errno != EINTR )) 335 | { 336 | printf("epoll error \n"); 337 | break; 338 | } 339 | for(int i=0; i 0 回收指定ID的子进程 393 | * 394 | */ 395 | while ( ( pid = waitpid( -1, &stat, WNOHANG ) ) > 0 ) 396 | { 397 | /* 398 | 如果进程池中某个第i个子进程退出了,则主进程关闭相应的通信管道,并设置相应的 399 | m_pid = -1;以标记子进程已经退出 400 | */ 401 | for(int i=0; i1、并发模型 8 | 9 | 程序使用的是 Reactor 处理模式。包括主线程和工作线程两部分。 10 | 11 | **主线程:** 使用 epoll 作为 IO 多路复用的实现方式,主线程只负责监听文件描述符上是否有事件发生,有的话就将对应的文件描述符交给工作线程去处理。 12 | 13 | **工作线程:** 在程序开始时便创建的固定数量的线程池,避免了频繁创建线程带来的资源开销。 14 | 15 | ### 2、Reactor模式 16 | 17 | ![Reactor模型.jpg](https://i.loli.net/2019/04/19/5cb92928d4004.jpg) 18 | 19 | 服务器程序通常需要处理三类事件:I/O 事件,信号以及定时事件。随着网络设计模式的兴起,两种事件处理模式应运而生,同步 I/O 模型常用于实现 Reactor 模式;而异步 I/O 模型则用于 Proactor 模式。 20 | 21 | **Reactor 模式** 是一种事件处理模式,它要求 **主线程(I/O处理单元)** 只负责监听文件描述符上是否有时间发生,有的话就将该事件通知 **工作线程(逻辑单元)** 除此之外,主线程不做任何其它实质性的工作。 22 | 23 | 读写数据,接受新的连接,以及处理客户请求均在工作线程中完成。 24 | 25 | 使用同步 I/O 模型(以 epoll_wait 为例)实现的 Reactor 模式的一般工作流程是: 26 | 27 | 28 | - 1、主线程往 epoll 内核事件表中注册 socket 上的读就绪事件。 29 | - 2、主线程调用 epoll_wait 等待 socket 上有数据可读。 30 | - 3、当 socket 上有数据可读时,epoll_wait 通知主线程。主线程则将可读事件放入请求队列。 31 | - 4、睡眠在请求队列上某个工作线程被唤醒,它从 socket 读取数据,并处理客户请求,然后往epoll 内核事件表中注册 socket 上的写就绪事件。 32 | - 5、主线程调用 epoll_wait 等待 socket 可写。 33 | - 6、当 socket 可写,epoll_wait 通知主线程。主线程则将可写事件放入请求队列。 34 | - 7、睡眠在请求队列上某个工作线程被唤醒,它往 socket 上写入服务器处理客户请求的结果。 35 | 36 | 37 | 流程图 38 | 39 | ![img](https://pic1.zhimg.com/80/v2-1f6f0caee133fde633b7cda601e87cd8_hd.jpg) 40 | 41 | ### 3、epoll 工作模式的选择 42 | 43 | **LT(电平触发):** 当 epoll_wait 检测到 socket 上有事件发生并将此事件通知应用程序之后,应用程序可以不用立即处理该事件。这样,当下一次调用 epoll_wait 时,还会再次向应用程序告知此事件,知道该事件被处理。 44 | 45 | **ET(边沿触发):** 当 epoll_wait 检测到 socket 上有事件发生并将此事件通知应用程序之后,应用程序必须立即处理该事件。 46 | 47 | **所以:** 本项目采用的 ET 模式在很大程度上降低了同一个 epoll 事件被重复触发的次数,因此效率要比 LT 模式高。 48 | 49 | **EPOLLONESHOT 事件:** 针对使用 ET 模式还是可能被触发多次,只有在 epoll_ctl 函数的文件描述符上注册 EPOLLONESHOT 事件,此时只触发一次,从而保证一个 socket 连接在任意时刻都只被一个线程处理。 50 | -------------------------------------------------------------------------------- /更新说明.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 最近陆陆续续有学弟学妹来问我 Webserver 相关的事情,因为本职也有工作,平时比较忙,但思考了决定,我还是会抽出时间来继续维护。 4 | 5 | # 常见的的问题汇总 6 | 7 | #### 事件驱动模型的设计思想到底是啥? 8 | 9 | 事件驱动模型的设计思想是:一个无限循环的事件分发线程在后台运行,一旦做了某种操作就触发这个事件,这个事件就会被放置到事件队列中,事件分发任务的线程就会这个发生的事件找到对应的事件回调函数去处理。 10 | 11 | #### 事件分发线程是根据啥去找到事件的回调函数并调用它呢? 12 | 13 | 答案是套接字:回调函数和套接字对应的,通过套接字找到对应的回调函数。 14 | 15 | #### 事件驱动模型的优势是啥? 16 | 17 | 事件驱动的好处是占用资源少,效率高,可扩展性强,是支持高性能高并发的不二之选。因为是事件驱动,不需要分配固定的资源,仅仅使用几个线程就可以支持上万的连接,每个线程的利用率得到了最大提升。 18 | 19 | #### IO 网络通信是怎么实现事件驱动模型的? 20 | 21 | 通过使用 poll、epoll 等 I/O 分发技术,可以设计出基于套接字的事件驱动程序,从而满足高性能、高并发的需求。 22 | 23 | #### Reactor 模型是啥玩意? 24 | 25 | Reactor 模型(中文叫做反应堆模型)也就是事件驱动模型或者是 Event loop 模型。 26 | 这个模型的核心有三点。 27 | 28 | - 它存在一个无限循环的事件分发线程,或者叫做 reactor 线程、Event loop 线程。这个事件分发线程的背后,就是 poll、epoll 等 I/O 分发技术的使用。 29 | - 第二,所有的 I/O 操作都可以抽象成事件,每个事件必须有回调函数来处理。acceptor 上有连接建立成功、已连接套接字上发送缓冲区空出可以写、通信管道 pipe 上有数据可以读,这些都是一个个事件,通过事件分发,这些事件都可以一一被检测,并调用对应的回调函数加以处理。 30 | - Reactor 模型——解决了空闲连接占用资源的问题,Reactor 线程只负责处理 I/O 相关的工作,业务逻辑相关的工作都被裁剪成一个一个的小任务,放到线程池里由空闲的线程来执行。当结果完成后,再交给反应堆线程,由 Reactor 线程通过套接字将结果发送出去。所以,这个模式性能更优。 31 | - 1:阻塞IO+多进程——实现简单,性能一般 32 | 2:阻塞IO+多线程——相比于阻塞IO+多进程,减少了上下文切换所带来的开销,性能有所提高。 33 | 3:阻塞IO+线程池——相比于阻塞IO+多线程,减少了线程频繁创建和销毁的开销,性能有了进一步的提高。 34 | 4:Reactor+线程池——相比于阻塞IO+线程池,采用了更加先进的事件驱动设计思想,资源占用少、效率高、扩展性强,是支持高性能高并发场景的利器。 35 | 36 | ### single reactor thread + worker threads 37 | 38 |
39 | 40 | **需要注意的是:**和 I/O 事件处理相比,应用程序的业务逻辑处理是比较耗时的,比如 XML 文件的解析、数据库记录的查找、文件资料的读取和传输、计算型工作的处理等,这些工作相对而言比较独立,它们会拖慢整个反应堆模式的执行效率。 41 | 42 | 所以,将这些 decode、compute、enode 型工作放置到另外的线程池中,和反应堆线程解耦,是一个比较明智的选择。反应堆线程只负责处理 I/O 相关的工作,业务逻辑相关的工作都被裁剪成一个一个的小任务,放到线程池里由空闲的线程来执行。当结果完成后,再交给反应堆线程,由反应堆线程通过套接字将结果发送出去。 43 | 44 | **服务端:** 45 | 46 | ![](https://cdn.jsdelivr.net/gh/rongweihe/ImageHost01/epoll/27-poll-server.png) -------------------------------------------------------------------------------- /测试.md: -------------------------------------------------------------------------------- 1 | ### 环境配置 2 | 3 | #### 测试机环境 4 | 5 | - 主机:Win7,Inter(R) Core(TM) i5-4210U CPU @ 1.70GHz 2.40 GHz 6 | 7 | - 虚拟机:Ubuntu16.04.9 CPU资源:4 核, 内存:4GB 8 | 9 | - Nginx:一种 web 服务器,如果虚拟机没有配置 Nginx ,则需要配置 10 | 11 | 如果没有正确配置 Nginx 监听端口, 则启动 webbench 会报错: 12 | 13 | **webbench Connect to server failed. Aborting benchmark.** 14 | 15 | 参考文章 安装和监听 8080 端口。 16 | 17 | 浏览器访问 http://127.0.0.1:8080 出现下面结果代表配置和监听 8080 端口成功。 18 | 19 |

20 | 21 | #### 测试工具 22 | 23 | - [webbench](https://github.com/EZLippi/WebBench) 24 | 25 | 使用方法: 26 | 27 | ```c++ 28 | [root@localhost ~]$ wget http://www.ha97.com/code/webbench-1.5.tar.gz 29 | [root@localhost ~]$ tar -xzvf webbench-1.5.tar.gz 30 | [root@localhost ~]$ cd webbench-1.5 31 | [root@localhost webbench-1.5]$ make 32 | [root@localhost webbench-1.5]$ sudo make install 33 | ``` 34 | 35 | -t 表示运行测试的时间,如果不指定默认是 30 秒,-c 表示客户端数量,也就是并发数。 36 | 37 | ```c++ 38 | [root@localhost webbench-1.5]$ webbench -c 1000 -t 30 http://blog.csdn.net/ 39 | Webbench - Simple Web Benchmark 1.5 40 | Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software. 41 | 42 | Request: 43 | GET / HTTP/1.0 44 | User-Agent: WebBench 1.5 45 | Host: blog.csdn.net 46 | 47 | 48 | Runing info: 1000 clients, running 30 sec. 49 | 50 | Speed=3932 pages/min, 30209 bytes/sec. 51 | Requests: 1949 susceed, 17 failed. 52 | ``` 53 | 54 | 结果分析 55 | 56 | ```html 57 | Pages/min:指的输出页数/分 58 | bytes/sec:是指比特/秒 59 | 这两个指标能反应网站的访问速度。susceed 和 failed 表示请求的成功数目和失败数目,猜测失败的原因是浏览器没有正确响应客户端的 GET 请求,得不到 200 OK 的响应。 60 | 61 | 上面的测试使用了相同的参数 (1000的并发数目,30秒),但是不能根据测试结果比较网站的性能。因为还有其它因素,比如测试的当前网页有没有涉及到数据库的访问等等。 62 | ``` 63 | 64 | - Linux Top 命令 65 | 66 | ```html 67 | 查看 Linux 系统进程和其它的状况动态的显示。能够实时显示系统中各个进程的资源占用状况。 68 | 参数解析: 69 | 【第一行】:任务队列信息 top - 14:55:55 (表示系统当前时间) up 7 days 16:00 (表示系统运行时间) 70 | 1、users (表示当前登录用户数) load average (系统负载 三个浮点数分别表示:1分钟、5分钟、15分钟的负载情况) 71 | 系统平均负载被定义为在特定时间间隔内运行队列中的平均进程数。如果一个进程满足以下条件则其就会位于运行队列中:数据是每隔5秒钟检查一次活跃的进程数,然后根据这个数值算出来的。如果这个数除以CPU 的数目,结果高于5的时候就表明系统在超负荷运转了。 72 | 1、它没有在等待I/O操作的结果 73 | 2、它没有主动进入等待状态(也就是没有调用'wait') 74 | 3、没有被停止(例如:等待终止) 75 | 【第二行】:【进程信息】 76 | 表示进程总数; 77 | 运行进程数; 78 | 睡眠进程数; 79 | 停止进程数; 80 | 僵尸进程数; 81 | 【第三行】:【CPU信息】 82 | us 用户空间占用CPU百分比; 83 | sy 内核空间占用CPU百分比; 84 | ni 用户进程空间内改变过优先级的进程占用CPU百分比; 85 | id 空闲CPU百分比; 86 | wa 等待输入输出的CPU时间百分比; 87 | hi:硬件CPU中断占用百分比; 88 | si:软中断占用百分比; 89 | st:虚拟机占用百分比 90 | 【第四行】: 91 | Mem:total:物理内存总量; 92 | used:使用的物理内存总量; 93 | free:空闲内存总量; 94 | buffers:内核缓存的内存总量; 95 | 【第五行】: 96 | Swap:total:交换区总量; 97 | used:使用的交换区总量; 98 | free:空闲的交换区总量; 99 | cached:缓冲交换区总量; 100 | 【第六行】:再下面就是进程信息: 101 | PID:进程的ID ; 102 | USER:进程所有者 ; 103 | PR:进程的优先级别,越小越优先被执行 ; 104 | NInice:值 ; 105 | VIRT:进程占用的虚拟内存 ; 106 | RES:进程占用的物理内存 ; 107 | SHR:进程使用的共享内存 ; 108 | S:进程的状态。S表示休眠,R表示正在运行,Z表示僵死状态,N表示该进程优先值为负数 ; 109 | %CPU:进程占用CPU的使用率 ; 110 | %MEM:进程使用的物理内存和总内存的百分比 ; 111 | TIME+:该进程启动后占用的总的CPU时间,即占用CPU使用时间的累加值 ; 112 | COMMAND:进程启动命令名称; 113 | ``` 114 | 115 | #### 测试用例 116 | 117 | ##### case-01 118 | 119 | - **4** 个工作线程并以守护进程运行(./webserver -d -t 4 -p 8080) 120 | 121 | - Linux page size is 4096 bytes 122 | 123 | - wenbench设置: **1000** 客户端、连接 60s、短连接 124 | 125 | - 空闲时线程 CPU占用情况: 126 | 127 |

128 | 129 | **测试结果:** 130 | 131 | QPS: 6083 传输速度: 4.95 MB/S 132 | 133 | - 测试结果: 134 | 135 |

136 | 137 | 138 | 139 | - 测试时线程 CPU 占用: 140 | 141 |

142 | 143 | ##### case-02 144 | 145 | - ##### 8 个工作线程并以守护进程运行( ./webserver -d -t 8 -p 8080) 146 | 147 | - ##### 页面大小: Linux page size is 4096 bytes 148 | 149 | - ##### wenbench设置: 1000 客户端、连接60s、短连接 150 | 151 | - ##### 空闲时 8 线程 CPU 占用情况: 152 | 153 |

154 | 155 | ##### **测试结果:** 156 | 157 | QPS: 3785 传输速度: 3.08 MB/S 158 | 159 | - 测试结果: 160 | 161 |

162 | 163 | ##### 分析: 164 | 165 | 虚拟机配置的是四个核心,从结果可以看出开 8 个线程的反而比开四个线程的 QPS 低了将近一半,猜测应该是线程之间切换开销增大造成的; 166 | 167 | 另外,后面我很纳闷的是,为什么本地虚拟机的 QPS 和传输速度都很低,后来,我重新查看虚拟机的配置的时候,发现我当初配置的是 1 核==。 168 | 169 | 下面是配置 4 核 4G 情况的结果: 170 | 171 |

172 | 173 | 174 | 175 | ##### case - 03 176 | 177 | 测试线程池的测试结果: 178 | 179 | ```c 180 | g++ -o threadpool threadpool.cpp -lpthread 181 | ./threadpool 182 | ``` 183 | 184 |

185 | 186 |

187 | 188 |

--------------------------------------------------------------------------------