zoukankan      html  css  js  c++  java
  • SbWebServer的几个设计

    基于生产者消费者模型的线程池设计:

    数据结构:

    成员:

    1.一个队列m_queue.其中存放Task,也就是任务,生产者线程,也就是主线程,往这个队列中push;消费者,也就是工作线程,不停地从其中拿走Task去工作。

    关于Task,原型为boost::function<void()> Task;

    2.一个表示线程数量的变量m_threadNum

    3.一个表示队列,即流水线的最大长度变量m_maxSize

    4.一个互斥量m_mutex,用于条件变量判断时对队列的状态进行保护

    5.一个条件变量m_notFull,用于通知唤醒消费者线程

    6.一个条件变量m_notEmpty,用于通知唤醒生产者线程

    函数:

    1.线程池构造函数ThreadPool(int threadNum,int maxSize)

    threadNum为线程数量,maxSize为队列最大大小

    在构造函数中创建threadNum个工作线程

    2.静态函数startThread

    工作线程实际上的工作内容,创建的工作线程从startThread开始工作。

    首先pthread_detach,分离后线程的资源自动回收,不用join

    startThread中传入线程池的this指针,这样静态函数startThread可以通过这个this指针调用线程池中的Work函数,Work才是真正的线程工作的主体

    3.Work函数

    Work内是一个for循环f(;;),循环内调用函数Take()从线程池的队列中取走任务,如果取来的任务可用,则执行这个任务

    4.Take函数
    典型的生产者消费者模型,首先用m_mutex加锁,加锁保护当前队列状态的原子性,用while(m_queue.empty())判断流水线中是否有工作,如果没有则wait陷入沉睡,直到生产者唤醒

    如果有工作,则拿走,并返回这个Task;Work函数拿到这个Task后执行这个任务

    5.append函数

    参数传入bind的function

    典型的生产者消费者模型,用于生产者往流水线中添加任务。依然是mutex加锁保护队列的状态,如果非满则将这个function加入到队列中

    页面缓存设计

    缓存Cache:
    数据成员:

    一个哈希表unordered_map:key为页面名,作为key;value为一个shared_ptr指针,指向FileInfo,FileInfo为封装了页面mmap到内存的类

    一个互斥锁mutex,用于缓存满了删除时保证原子性

    函数:

    1.getFile用于在页面缓存中寻找请求的资源。

    首先加锁,防止请求资源时该缓存被淘汰而出现问题。然后在哈希表中用文件名去寻找,如果未找到,则将这个页面加入缓存。如果此时缓存已满,则淘汰掉访问次数较少的页面,访问次数的统计被封装在FileInfo中。

    2.清楚缓存的函数cleancache:

    遍历哈希表,如果访问次数小于10的页面均淘汰掉。

    页面封装FileInfo:

    数据成员:

    1.存储 通过mmap映射到内存的地址 的变量addr:所访问的页面都通过mmap映射到内存,然后记录这个地址,封装到FileInfo中,这样每次打开就不用了重新IO打开文件,只要通过这个文件对应的FileInfo中的addr即可寻址到页面对应的位置

    2.size表示文件的大小

    3.count用于统计页面的访问次数,当缓存满时以此为基准进行淘汰

    方法:

    1.构造函数:通过文件名打开对应文件,然后使用mmap将之映射到内存,mmap返回的结果赋予addr,用于寻址

    2.析构函数:munmap来释放映射到内存的内容。当然,析构的时间由shared_ptr来掌握

     

    Http_Handle封装:

    数据成员:

    1.文件描述符fd,用于表示对应的套接字

    2.状态变量state:

    state为Read,process转入processRead进行处理,表示此时应该将对端的数据存入readBuf缓冲区,并分析readBuf缓冲区的内容,根据url找到对应的文件,打开并加入缓存,并写好响应头到writeBuf

    state为Write,process转入processWrite进行处理,将writeBuf内的响应信息写到对端,将请求的资源写入到对端。

    3.两个缓存readBuf和writeBuf:readBuf用于存放对端发送过来的数据,比如Http头的信息,将之存放在readBuf中;writeBuf用于存放响应信息,比如响应行

    4.一个指向所请求资源的FileInfo封装的shared_ptr指针,通过这个指针来找到资源内容,并写道对端。

    整体流程:

    首先服务端进入被动打开:

    创建一个Socket,将这个socket绑定到8080端口,listen后转为监听套接字,用于监听请求

    创建epoll,设定监听的文件描述符个数

    将监听套接字listenfd加入epoll的监听范围

    初始化线程池,创建若干工作线程,设定队列(流水线)大小

    进入while(true)循环:

    epoll_wait等待事件发生,当listenfd可读,代表有请求,由于使用的是ET模式,因此listenfd要一直accept到返回EAGAIN,每accept一个,返回套接字connfd,将这个套接字封装成Http_Handle,此时状态为Read,及等待对端像这个socket发送数据并分析。将这个套接字connfd也加入到epoll的监听里,监听时间为EPOLLIN,也就是可读。

    当epoll_wait等待来的是其他事件,将这个事件对应的fd,把它的process函数bind到function上,并将这个function加入到流水线

    工作线程从流水线上拿到这个Task,进行执行:

    此时这个Task相当于绑定到某个Http_Handle上的Http_Handle::process,消费者执行process时,根据Http_Handle中state的状态来决定接下来的动作:

    1.Read:代表读取对端发过来的连接包到readBuf,并分析readBuf的内容,找到URL指定的资源,这是通过缓存来找到的。找到后将响应行和响应头相关的信息写入writeBuf(因为writeBuf本来就被初始化为全0,所以不用在尾部加)。结束后将状态设置为Write,即只需要讲这些信息以及文件写入到对端就可以了。修改本fd在epoll中的监听事件,本来是监听可读,现在要写了,因此监听EPOLLOUT。

    2.Write:代表此时应该向对端写数据了,转入执行processWrite:这个过程就是将writeBuf中的头信息写到对端,然后用Http_Handle中封装的FileInfo的智能指针找到映射到内存里的文件,将文件写到对端。一切结束后代表这个Socket的任务已经完成,断开连接。

    这里主线程相当于生产者,不断的往队列中append不同fd的process函数,然后消费者从队列中拿走这些process函数, process函数会根据Http_Handle.中State的状态不同执行processRead或processWrite.

  • 相关阅读:
    Maven安装
    Linux登录欢迎图案
    GC的性能指标和内存容量配置原则
    java堆结构和垃圾回收
    框架设计知识点纵览(笔记)
    .net core在Linux本地化Localization的一次填坑
    .Net Identity OAuth 2.0 SecurityStamp 使用
    CentOS 7 安装. Net Core SDK 2.0
    Docker基本命令与使用 —— Docker容器的网络连接(四)
    Docker基本命令与使用 —— Dockerfile指令与构建(三)
  • 原文地址:https://www.cnblogs.com/lxy-xf/p/11531182.html
Copyright © 2011-2022 走看看