# Linux轻量级高并发Web服务器 **Repository Path**: miku-39c5bb/webserver ## Basic Information - **Project Name**: Linux轻量级高并发Web服务器 - **Description**: Linux轻量级高并发Web服务器 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-05-29 - **Last Updated**: 2023-07-11 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Linux轻量级高并发Web服务器 ## 项目介绍 在 Linux 环境下使用 C++ 搭建轻量级高并发 Web 服务器,支持一定数量的客户端连接并及时响应。 主要工作: 1. 使用 Socket 实现不同主机之间的通信 2. 使用 Epoll 技术实现 I/O 多路复用,提高效率 3. 使用线程池和模拟 Proactor 模式实现高并发处理 4. 利用双向链表和定时器实现长连接检测与关闭,节省资源 ## 项目的执行过程 项目的编译和执行过程:需要在Linux系统下,执行以下命令 ```shell mkdir build cmake .. make ./app 10000 ``` 然后打开本机浏览器,以`IP:Port/index.html`的形式输入地址,例如192.168.xxx.xxx:10000/index.html,就可以得到服务器发送过来的响应 压力测试:执行以下命令即可 ``` cd test_presure/webbench-1.5/ make ./webbench -c 4000 -t 5 http://192.168.xxx.xxx:10000/index.html # -c 表示客户端数 # -t 表示时间 ``` 测试结果如下: ![img](./pic/image2.png) ## 项目细节与程序框图 ### 技术框架 1. 线程池 + 非阻塞 socket + epoll + 事件处理的并发模型 2. 状态机解析HTTP请求 3. 心跳机制 ### 主要内容 1. 使用 socket 实现服务器和浏览器客户端的通信; 2. 用 epoll 事件检测技术实现 IO 多路复用,提高运行效率; 3. 采用模拟 Proactor的事件处理模式,利用线程池实现多线程机制,实现高并发通信,减少频繁创建和销毁线程带来的开销;(信号和互斥锁) 4. 主进程负责事件的读写,子线程负责业务逻辑——用有限状态机解析HTTP(GET)请求报文;生成相应的响应报文。 5. 利用链表数据结构实现心跳机制(超时检测处理)。 ![img](pic/image1.jpeg) ### 项目主体思路 1. 项目采用**`模拟Proactor模式`**。原理是:**主线程执行数据读写操作**,读写完成之后,**主线程向工作线程通知这一”完成事件“**。那么从**工作线程**的角度来看,它们就直接获得了数据读写的结果,接下来要做的只是对读写的结果**进行逻辑处理**。 2. 此外,项目中还涉及到了**`epoll`实现多路复用、边沿模式ET、EPOLLONESHOT事件、线程池、定时器关闭长连接**等工作 1. **线程池**中是有一个动态创建的**线程数组**,一个**请求队列**,以及相关的锁和互斥量。**线程创建时传入`worker`函数,该函数有一个循环,循环中调用请求类型了`process`函数,可以保证请求队列中一有请求加入,线程池中的就会有一个线程来处理请求**。**这个`process`函数,即工作线程需要执行的逻辑处理函数**。每有一个客户端连接进来,即在`main.cpp`中检测到`EPOLLIN`事件时,就会先`read`读入请求,然后**调用`append`函数把这个请求加入到线程池的请求队列**中,然后就会**有空闲的线程从请求队列的队首取出一个请求**,并**执行`process`函数解析客户端的请求**。 2. **定时器关闭长连接**,利用双向链表和定时器实现长连接检测与关闭,节省资源。设置一个定时器类`util_timer`,以及一个定时器链表`sort_timer_lst`,并将**这个两个类整合到客户端请求类`http_conn`中,定时器类`util_timer`是每个`http_conn`请求都有一个,并在`init`函数中进行`timer`定时器初始化,定时器链表`sort_timer_lst`是静态成员,所有`http_conn`请求共同维护**。 在`main.cpp`中,设置**信号处理函数`addsig(SIGALRM, sig_to_pipe)`**,当有`SIGALRM`到来时,就发送到管道的读端`pipefd[0]`,并且`pipefd[0]`已加入`epoll`中,可以方便的检测`SIGALRM`信号。**在进行循环接收客户端请求前,先设置时钟`alarm(TIMESLOT); `,5s后产生一个`SIGALRM`**。**当检测到`pipefd[0]`有`SIGALRM`时,设置`timeout = true;`**,用`timeout`变量标记有定时任务需要处理,但**不立即处理定时任务,这是因为定时任务的优先级不是很高**,我们优先处理其他更重要的任务。最后**在当前这一轮的`epoll`返回的事件数组检查结束后,才处理定时事件**,因为I/O事件有更高的优先级,当然,这样做将导致定时任务不能精准的按照预定的时间执行。**定时处理任务,实际上就是调用`tick()`函数,删除过期的请求**。然后,还需要**再次设置时钟`alarm(TIMESLOT)`**,因为一次 `alarm `调用只会引起一次`SIGALRM `信号,所以我们要重新定时,以不断触发 `SIGALRM`信号。当然,还要重置`timeout = false`。 3. **主线程调用`read`函数**,读入客户端的HTTP请求,然后**将该客户端请求加入线程池的请求队列** 4. 线程池中,**当有线程空闲时,就会执行`process`函数进行处理,主要是负责解析请求、生成响应** `process`函数,由线程池中的**工作线程**调用,这是处理HTTP请求的入口函数,`process`函数的处理思路如下: 1. **先调用`process_read`,解析请求行、请求头、空行、请求数据**,并设置相应的成员变量的值 解析完客户端的请求后,**在`process_read`中调用`do_request`函数,打开客户端请求的文件,并将文件进行内存映射**,这部分内容就是客户端所需要的数据 2. 然后调用`process_write`,生成响应。 在**`process_write`中,会生成响应行、响应头、空行,这些部分最终经过`add_response`函数写入`m_write_buf`中** 而**响应数据则在内存映射中,地址为`m_file_address`** **将`m_write_buf`、`m_file_address`这两块地址放入`m_iv`中** 3. 将该客户端请求的套接字的`epoll`检测事件设为**`EPOLLOUT`** 5. 最后**主线程通过`epoll`检测到`EPOLLOUT`事件**,调用`write`函数,**通过`writev`将`m_iv`中的响应写入客户端的套接字中,客户端就得到了需要的数据**