《网络编程》之——Linux高性能服务器开发小项目的分析记录

这篇博客详细分析了基于《Linux高性能服务器编程》的Mywebserver项目,包括互斥锁、线程池、请求队列的实现,并对比了Tinywebserver的改进,如引入Reactor和模拟Proactor模式,实现更灵活的并发处理。
摘要由CSDN通过智能技术生成

Linux高性能服务器开发小项目分析记录
参考书籍:《Linux高性能服务器编程》游双
基础班:Mywebserver,就是书上的服务器项目
进阶版:Tinywebserver


PartⅠ:mywebserver


一、locker.h,locker.cpp (与Tiny相同)

互斥锁类locker

用于内存池:请求队列(工作队列)的互斥访问

  • 向工作队列中添加任务append()时,会访问/修改请求队列

  • 线程中运行的主要逻辑:从请求队列中取出请求并执行其process()函数,故会访问/修改请求队列

条件变量类cond

项目中没有用到

信号量类sem

用于内存池:请求队列——生产者消费者模型

  • append()向请求队列中添加请求,则信号量++(post())

  • run()从请求队列中取出请求,则信号量–(wait())

二、threadpool.h 线程池

创建多个线程

创建一定数量(m_thread_number)的线程(pthread_create()),线程的入口函数worker()需要是静态函数,原因:

  • 参考链接参考链接

  • 类中调用pthread_create()时,类的成员函数作为pthread_create()的参数时,必须是静态函数

  • 因为类的成员函数有一个隐藏的参数——this指针,而线程的入口函数必须是接受一个void指针作为参数,故入口函数的参数中有一个隐藏的this指针是不允许的。为了解决矛盾,我们使用static成员函数,其独立于实例,参数中不会有this指针,故其可以用作线程的入口函数

将线程设为脱离

创建线程后,将线程设为脱离(pthread_detach()),这是为了自动回收线程资源

  • pthread_detach()的作用——当线程终止时,线程的资源将会立即被回收,而不用等待另一个线程调用pthread_join
/* Indicate that the thread TH is never to be joined with PTHREAD\_JOIN.   The resources of TH will therefore be freed immediately when it   terminates, instead of waiting for another thread to perform PTHREAD\_JOIN   on it.  */ 
extern int pthread\_detach (pthread\_t \_\_th) \_\_THROW;

请求队列(工作队列)

请求队列——以队列形式组织,其实现了:将主线程和工作线程解耦(主线程向请求队列中添加任务,工作线程通过竞争来取得任务并执行任务)

  • 请求队列的访问:由于会有多个线程对其进行访问/修改,故通过互斥锁实现互斥访问

  • 工作线程竞争请求队列中的任务:通过信号量实现生产者/消费者模型,任务交给哪个线程执行是随机的。(也可以用Round Robin算法让线程轮流获取任务)

工作线程们运行的函数

worker()->run()

  • worker():只是一个桥梁,静态成员函数,用作pthread_create()的参数;其中只是调用线程的主要逻辑run()

  • run():类的成员函数,需要静态成员函数worker()作为媒介来成为线程的工作函数。其主要逻辑:

  • 竟态获取请求队列中的任务,然后执行任务的process()函数

只实现了Proactor模式

run()中只执行process()过程,数据的读写是在主线程main()中完成的

reactor模式的实现——把数据读写放到工作线程中完成就行了(参考Tinywebserver)

三、main.cpp

网络编程常规步骤

循环获取就绪事件并处理

这便是主线程的主要工作逻辑。

由于是Proactor模式,故可读可写事件发生时,main中将数据读入/写出完毕,然后再通知工作线程进行后续逻辑处理

由于该项目中,写完成后,没有后续的处理逻辑,故只有读完成后才会通知工作线程(将任务append到线程池的请求队列)

项目中只实现了LT+ONESHOT模式。虽然是LT模式,但是由于是ONESHOT,故也要用非阻塞IO循环读取以保证将TCP缓存中的数据全部读出。(在ET模式下,事件触发后需要将缓冲区中的数据完全读完,否则会陷入死锁,故必须要用非阻塞IO循环读取)。
为什么必须要用ONESHOT呢?(ONESHOT的作用)——《Linux高性能服务器编程》P157:

  • 即使是用ET模式,一个socket上的事件还是可能被触发多次(LT模式则更是如此),这在并发编程中就会引起一个问题:socket上的数据被获取后,一个线程开始处理数据(此时HTTP请求报文可能并不完整);而在处理数据的过程中,又有新的数据可读(EPOLLIN再次被触发),此时另一个线程被唤醒去处理新到来的数据。这就会出现同时有两个线程操作同一个socket的情况,这显然是不被期望的。
  • 正常情况应该是:数据到来–>数据被读入socket对应的http_conn类对象H中–>唤醒一个线程去操作这个类对象H(处理数据),数据处理过程中不应再次触发事件,避免又有另一个线程开始对该类对象H进行操作。

四、http_conn.h,http_conn.cpp

主要逻辑

处理HTTP请求:

  • process_read()——正常流程下,process_read()返回的是do_request()的结果

  • parse_line()

  • get_line()

  • parse_request_line()

  • parse_hearders()

  • parse_content()

do_request():根据HTTP请求的解析结果,进行处理得到后续生成响应所需的前提状态

  • 项目中只实现了GET:

  • 获取请求资源的信息,并判断请求的合法性

  • 若合法,则将文件只读地打开,并映射如内存

  • 若要实现POST:需要在do_request()中添加POST的处理逻辑

生成HTTP响应数据并放入发送缓存

  • process_write()——根据process_read()(do_request())获得的文件合法性,生成响应报文放入发送缓冲区,并设置发送缓冲区的标志信息

  • process_write()处理完成之后,注册EPOLLOUT事件,等待主线程调用write()发送缓冲区中的数据

其他函数

  • write()——采用分散写writev(),客户请求的资源(响应体)和process_write()生成的报文数据(状态行和响应头),分别放在两块内存中,前者在文件映射到的内存中,后者在发送缓存中

  • setnonblocking()、addfd()、removefd()、modfd()等socket编程常用函数也定义在http_conn.cpp中(因为类中也会用到这些函数)

有限状态机-处理HTTP请求中使用

  • 主状态机:当前解析到哪部分(请求首行、请求头部、请求体)

  • 第一行是首行,解析完直接跳到解析头部状态

  • 头部解析时若遇到空行,则说明头部解析完毕,若有请求体则跳到解析请求体状态(头部中会有Content-Length来告诉你有没有请求体)

  • 请求体解析

  • 从状态机:请求数据的每一行的处理状态(完整的一行、不完整的一行、错误的一行)

  • 请求分析结果状态机:用于标记请求报文解析的结果,并用于决定后续的报文解析和响应生成

  • 请求方法状态机:用于标记请求报文中的请求方法,该项目中只支持GET


PartⅡ:整个项目的模型


一、mywebserver:

并发模式:半同步/半反应堆模式 —— 我认为应该叫“半同步/半模拟前摄器模式”

事件处理(事件分发)模型:同步IO模拟的Proactor模型

二、Tinywebserver:

并发模式:半同步/半反应堆模式、半同步/半模拟前摄器模式 都实现了(其实就是实现了下面的Reactor和模拟Proactor)

事件处理(事件分发)模型:同步IO模拟的Proactor模型、Reactor模型 都实现了(通过threadpool.h中的m_actor_model变量实现选择用哪种模型,就是选择read()和write()在主线程完成还是工作线程完成而已)

关于游双讲的服务器框架,我的疑问与理解:我的博客


PartⅢ:Tinywebserver做了哪些改进


坑,待填

----------书山有路勤为径---------
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值