tinyserver学习理解记录

参考我自建的tinyserver_built

项目结构

项目一般包含以下主要目录和文件:

  • src/:源代码目录,包含服务器实现的各个模块。
  • root/:网站的根目录,包含 HTML 文件、图片等静态资源。

1.主要模块

1. main.cpp
  • 启动服务器的入口点,初始化服务器并开始监听端口。
2. src/server/webserver.cppwebserver.h
  • WebServer 类:负责服务器的初始化、资源加载、事件循环等核心功能。
3. src/http/http_conn.cpphttp_conn.h
  • http_conn 类:处理 HTTP 连接,包括解析请求、生成响应。
4. src/log/log.cpplog.h
  • 日志系统:用于记录服务器运行时的信息、错误和调试信息。
5. src/lock/locker.h
  • 锁机制:实现线程间同步,防止资源竞争。
6. src/threadpool/threadpool.h
  • 线程池:管理工作线程,执行并发任务。
7. src/timer/lst_timer.cpplst_timer.h
  • 定时器:管理连接的超时处理,保持长连接的状态。

2. 代码解析

1. main.cpp
int main(int argc, char *argv[]) {
    WebServer server;
    server.init();  // 初始化服务器
    server.start(); // 启动服务器
    return 0;
}
2. WebServer
void WebServer::init() {
    // 初始化日志、线程池、数据库连接池等
}

void WebServer::start() {
    // 事件循环,处理客户端连接
    while (true) {
        int event_number = epoll_wait();
        // 处理每个事件,如读写事件、定时器事件
    }
}
3. http_conn
void http_conn::process() {
    // 解析请求行、请求头和请求体
    // 根据请求类型生成响应
}

bool http_conn::read() {
    // 从客户端读取数据
}

bool http_conn::write() {
    // 向客户端写入数据
}
4. 日志系统
void Log::init() {
    // 初始化日志文件
}

void Log::write_log() {
    // 将日志写入文件
}

3. 运行流程

  1. 启动服务器:初始化日志、线程池、数据库连接池等资源。
  2. 等待连接:使用 epoll 等机制等待客户端连接。
  3. 处理请求:当有新连接时,创建 http_conn 实例处理请求。
  4. 生成响应:根据请求类型生成相应的响应并返回给客户端。
  5. 日志记录:记录运行中的重要信息和错误。

线程池

在线程池部分做几点解释,然后大家去看代码的时候就更容易看懂了:

  • 所谓线程池,就是一个pthread_t类型的普通数组,通过pthread_create()函数创建m_thread_number线程,用来执行worker()函数以执行每个请求处理函数(HTTP请求的process函数),通过pthread_detach()将线程设置成脱离态(detached)后,当这一线程运行结束时,它的资源会被系统自动回收,而不再需要在其它线程中对其进行 pthread_join() 操作。
  • 操作工作队列一定要加锁(locker),因为它被所有线程共享。
  • 我们用信号量来标识请求队列中的请求数,通过m_queuestat.wait();来等待一个请求队列中待处理的HTTP请求,然后交给线程池中的空闲线程来处理。

threadpool.cpp

总体流程

threadpool-----

1、创建一个描述线程池的大小为 m_thread_number 的线程数组 m_threads

2、循环创建 m_thread_number 个线程,每个线程运行 worker 函数。


等一下,首先讲讲任务队列

多线程服务器中,任务队列和线程池数组中的线程之间的关系

  • 任务队列

    • 任务队列 (m_workqueue) 是一个共享资源,保存着所有待处理的任务。
    • 每当有新任务到来时,会将任务添加到队列中。

    线程池数组

    • 线程池数组 (m_threads) 包含了多个线程,每个线程都是在初始化时创建的。
    • 这些线程不断地从任务队列中取出任务进行处理。

总结来说:就是线程数组池的线程从任务队列中取出任务来处理

同步和互斥:为了防止多个线程同时访问任务队列而产生竞态条件,需要使用互斥锁 (m_queuelocker) 和信号量 (m_queuestat) 来进行同步。

继续

3、任务添加,当有新的请求到来时,调用 appendappend_p 方法,将请求添加到任务队列中,并通知线程池有新的任务需要处理

4、工作线程:workerrun方法来执行任务


Reactor 和 Proactor 模型

  • Reactor 模型:线程会根据请求的状态(读或写)分别处理。如果是读操作(m_state == 0),先读取数据,然后处理请求;如果是写操作,直接处理写操作。
  • Proactor 模型:线程直接处理请求。

总结

通过线程池管理多个工作线程,服务器能够高效地处理并发请求。线程池的工作机制包括任务添加、线程启动、任务处理和选择不同的处理模型(Reactor 或 Proactor)。

为什么要使用线程池?

当你需要限制你应用程序中同时运行的线程数时,线程池非常有用。因为启动一个新线程会带来性能开销,每个线程也会为其堆栈分配一些内存等。为了任务的并发执行,我们可以将这些任务任务传递到线程池,而不是为每个任务动态开启一个新的线程。

线程池中的线程数量是依据什么确定的?

在StackOverflow上面发现了一个还不错的回答,意思是:
线程池中的线程数量最直接的限制因素是中央处理器(CPU)的处理器(processors/cores)的数量N:如果你的CPU是4-cores的,对于CPU密集型的任务(如视频剪辑等消耗CPU计算资源的任务)来说,那线程池中的线程数量最好也设置为4(或者+1防止其他因素造成的线程阻塞);对于IO密集型的任务,一般要多于CPU的核数,因为线程间竞争的不是CPU的计算资源而是IO,IO的处理一般较慢,多于cores数的线程将为CPU争取更多的任务,不至在线程处理IO的过程造成CPU空闲导致资源浪费,公式:最佳线程数 = CPU当前可使用的Cores数 * 当前CPU的利用率 * (1 + CPU等待时间 / CPU处理时间)(还有回答里面提到的Amdahl准则可以了解一下

http解析与响应连接

process()

1、在线程池的run()方法中调用了process来处理http请求任务

2、方法process_read()来解析HTTP报文从而解析HTTP请求,解析成功后,modfd 函数用于修改 epoll 事件表,将当前连接的事件修改为读事件 (EPOLLIN) 或写事件 (EPOLLOUT)。

3、process_read()函数的作用就是将请求报文进行解析,因为用户的请求内容包含在这个请求报文里面,只有通过解析,知道用户请求的内容是什么,是请求图片,还是视频,或是其他请求,我们根据这些请求返回相应的HTML页面等


讲下HHTP请求类型

GET 请求

  • 目的:从服务器获取资源。
  • 特点:数据通过 URL 参数传递,长度有限。
  • 示例:访问网页、查询数据等。

POST 请求

  • 目的:向服务器提交数据。
  • 特点:数据通过请求体传递,适合传输大数据量和敏感信息。
  • 示例:提交表单、上传文件等。

4、经过上述解析,当得到一个完整的,正确的HTTP请求时,就到了do_request方法

5、首先对GET请求和不同POST请求(登录,注册,请求图片,视频等等)做不同的预处理

6、生成响应,process_write,根据解析结果生成相应的 HTTP 响应,包括状态行、头部和内容。


先来看看这些不同请求是怎么来的

假设你已经搭好了你的HTTP服务器,然后你在本地浏览器中键入localhost:9000,然后回车,这时候你就给你的服务器发送了一个GET请求,什么都没做,然后服务器端就会解析你的这个HTTP请求,然后发现是个GET请求,然后返回给你一个静态HTML页面,也就是项目中的judge.html页面

那POST请求怎么来的呢?这时你会发现,返回的这个judge页面中包含着一些新用户已有账号这两个button元素,当你用鼠标点击这个button时,你的浏览器就会向你的服务器发送一个POST请求,服务器段通过检查action来判断你的POST请求类型是什么,进而做出不同的响应。

处理流程总结

  1. 初始化连接:设置套接字和地址,添加文件描述符到 epoll 中,增加用户计数。
  2. 读取请求:从客户端读取数据,判断是水平触发(LT)还是边缘触发(ET)。
  3. 解析请求:根据 HTTP 请求行、请求头和请求内容的状态逐步解析完整的 HTTP 请求。
  4. 生成响应:根据解析结果生成相应的 HTTP 响应,包括状态行、头部和内容。
  5. 主处理流程:调用 process_read 读取和解析请求,如果请求不完整则继续等待;调用 process_write 生成并发送响应,如果发送失败则关闭连接。

数据库连接池

https://huixxi.github.io/2020/06/02/%E5%B0%8F%E7%99%BD%E8%A7%86%E8%A7%92%EF%BC%9A%E4%B8%80%E6%96%87%E8%AF%BB%E6%87%82%E7%A4%BE%E9%95%BF%E7%9A%84TinyWebServer/#more

未完待续

  • 19
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值