文章目录

  • 1 - 处理事件
    • 1-1 接收新连接
    • 1-2 最小堆定时器
    • 1-4 将request加入线程池
    • 1-5 处理request

1 - 处理事件

因为epoll_wait函数已经返回了需要处理的事件数目events_num,并且已经将events_num个数的事件排到了数组的最前面,因此使用一个for循环取出前events_num个事件的data

处理事件的逻辑为:

  • 遍历events_num

    • 若当前事件是新连接,接收新连接
    • 否则,判断该事件合法以后,将该事件的请求request加入线程池(使用工作线程处理该事件)

1-1 接收新连接

调用acceptConnection函数接收server socket监听到的连接

acceptConnection函数内部逻辑如下:

  1. 申请一块内存,接收新的client socket连接(调用accept函数),将其放到刚刚申请的内存上
  2. 设置client socket为非阻塞
  3. 为client socket申请request内存,设置事件集合,并在内核事件表中注册
  4. 使用最小堆实现的定时器管理client request是否超时,维护client request队列(结合锁实现定时器节点的添加和刹车农户)
void acceptConnection(int listen_fd, int epoll_fd, const string &path)
{struct sockaddr_in client_addr;memset(&client_addr, 0, sizeof(struct sockaddr_in));socklen_t client_addr_len = 0;int accept_fd = 0;while((accept_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_addr_len)) > 0){/*// TCP的保活机制默认是关闭的int optval = 0;socklen_t len_optval = 4;getsockopt(accept_fd, SOL_SOCKET,  SO_KEEPALIVE, &optval, &len_optval);cout << "optval ==" << optval << endl;*/// 设为非阻塞模式int ret = setSocketNonBlocking(accept_fd);if (ret < 0){perror("Set non block failed!");return;}requestData *req_info = new requestData(epoll_fd, accept_fd, path);// 文件描述符可以读,边缘触发(Edge Triggered)模式,保证一个socket连接在任一时刻只被一个线程处理__uint32_t _epo_event = EPOLLIN | EPOLLET | EPOLLONESHOT;epoll_add(epoll_fd, accept_fd, static_cast<void*>(req_info), _epo_event);// 新增时间信息mytimer *mtimer = new mytimer(req_info, TIMER_TIME_OUT);req_info->addTimer(mtimer);pthread_mutex_lock(&qlock);myTimerQueue.push(mtimer);pthread_mutex_unlock(&qlock);}//if(accept_fd == -1)//   perror("accept");
}

1-2 最小堆定时器

设计定时器的一种思路:

将所有定时器中超时时间最小的一个定时器的超时值作为心搏间隔。一旦心搏函数tick被调用,超时时间最小的定时器必然到期,我们在心搏函数tick中处理该定时器。然后,从剩余的定时器中找到超时时间最小的一个,将这段最小时间设置为下一次心搏间隔。重复以上过程既能实现定时功能。

最小堆又叫小根堆,指的是每个节点的值都 ≤ 子节点的值 的完全二叉树

关于最小堆的节点插入与删除操作,可以参考我之前总结的博文 堆排序【二叉堆简介】【二叉堆插入/删除堆顶/构造】【堆排序】【最大堆/最小堆】

定时器是针对请求request来说的,有一个request到来,就为他创建一个定时器。定时器的结构如下:

struct mytimer
{bool deleted;size_t expired_time;requestData *request_data;mytimer(requestData *_request_data, int timeout);~mytimer();void update(int timeout);bool isvalid();void clearReq();void setDeleted();bool isDeleted() const;size_t getExpTime() const;
};

使用request初始化定时器的时候,要获取当前时间:

mytimer::mytimer(requestData *_request_data, int timeout): deleted(false), request_data(_request_data)
{//cout << "mytimer()" << endl;struct timeval now;gettimeofday(&now, NULL);// 以毫秒计expired_time = ((now.tv_sec * 1000) + (now.tv_usec / 1000)) + timeout;
}

然后将定时器添加到request的信息中,并且将该定时器添加到定时器队列中(注意定时器队列属于临界资源,进队列进出操作都需要加锁)。

使用STL的priority_queue实现定时器队列。在priority_queue中,元素被赋予优先级。当访问元素时,具有最高优先级的元素最先删除。优先队列具有最高级先出 (first in, largest out)的行为特征。因此,需要根据定时器自定义队列优先级排序规则

extern priority_queue<mytimer*, deque<mytimer*>, timerCmp> myTimerQueue;
//...
struct timerCmp
{bool operator()(const mytimer *a, const mytimer *b) const;
};
//...
bool timerCmp::operator()(const mytimer *a, const mytimer *b) const
{return a->getExpTime() > b->getExpTime();
}
//...
size_t mytimer::getExpTime() const
{return expired_time;
}

1-4 将request加入线程池

这里就用到了之前博文中 WebServer代码解读(1)【main函数流程】【忽略SIGPIPE信号:handle_for_sigpipe】【创建EPOLL内核事件表】【线程池组成部分与工作逻辑】 中的 threadpool_add函数

///添加需要执行的任务。第二个参数为对应函数指针,第三个为对应函数参数。flags 未使用。
int threadpool_add(threadpool_t *pool, void (*function)(void *), void *argument, int flags);//传入参数为
int rc = threadpool_add(tp, myHandler, events[i].data.ptr, 0);

解释一下实参myHandler函数,该函数用于处理request。这是一个函数指针,代码如下:

void myHandler(void *args)
{requestData *req_data = (requestData*)args;req_data->handleRequest();
}

1-5 处理request

处理request的函数封装在requestData结构体中,主要的逻辑为:

  1. 从产生request的fd中读取数据
  2. 针对该request的类型(URI、报头、报体、其他)进行对应类型的解析
  3. 若没有完成该request(state没有置为完成状态),那么将该request重新设计定时器(包括将定时器加入定时器队列),重新在内核事件表中注册
void requestData::handleRequest()
{char buff[MAX_BUFF];bool isError = false;while (true){int read_num = readn(fd, buff, MAX_BUFF);if (read_num < 0){perror("1");isError = true;break;}else if (read_num == 0){// 有请求出现但是读不到数据,可能是Request Aborted,或者来自网络的数据没有达到等原因perror("read_num == 0");if (errno == EAGAIN){if (againTimes > AGAIN_MAX_TIMES)isError = true;else++againTimes;}else if (errno != 0)isError = true;break;}string now_read(buff, buff + read_num);content += now_read;if (state == STATE_PARSE_URI){int flag = this->parse_URI();if (flag == PARSE_URI_AGAIN){break;}else if (flag == PARSE_URI_ERROR){perror("2");isError = true;break;}}if (state == STATE_PARSE_HEADERS){int flag = this->parse_Headers();if (flag == PARSE_HEADER_AGAIN){  break;}else if (flag == PARSE_HEADER_ERROR){perror("3");isError = true;break;}if(method == METHOD_POST){state = STATE_RECV_BODY;}else {state = STATE_ANALYSIS;}}if (state == STATE_RECV_BODY){int content_length = -1;if (headers.find("Content-length") != headers.end()){content_length = stoi(headers["Content-length"]);}else{isError = true;break;}if (content.size() < content_length)continue;state = STATE_ANALYSIS;}if (state == STATE_ANALYSIS){int flag = this->analysisRequest();if (flag < 0){isError = true;break;}else if (flag == ANALYSIS_SUCCESS){state = STATE_FINISH;break;}else{isError = true;break;}}}if (isError){delete this;return;}// 加入epoll继续if (state == STATE_FINISH){if (keep_alive){printf("ok\n");this->reset();}else{delete this;return;}}// 一定要先加时间信息,否则可能会出现刚加进去,下个in触发来了,然后分离失败后,又加入队列,最后超时被删,然后正在线程中进行的任务出错,double free错误。// 新增时间信息pthread_mutex_lock(&qlock);mytimer *mtimer = new mytimer(this, 500);timer = mtimer;myTimerQueue.push(mtimer);pthread_mutex_unlock(&qlock);__uint32_t _epo_event = EPOLLIN | EPOLLET | EPOLLONESHOT;int ret = epoll_mod(epollfd, fd, static_cast<void*>(this), _epo_event);if (ret < 0){// 返回错误处理delete this;return;}
}

WebServer代码解读(3)【最小堆定时器与队列】相关推荐

  1. 使用最小堆使用优先级队列(c语言版本)

    下面的例子来自Weiss的<数据结构与算法分析:c语言描述>,自己亲自敲了一遍,跑了个demo,并将结果记录下来. binheap.h的头文件声明 //description: 使最小堆实 ...

  2. 优先级队列 c语言,使用最小堆使用优先级队列(c语言版本)

    下面的例子来自Weiss的<数据结构与算法分析:c语言描述>,自己亲自敲了一遍,跑了个demo,并将结果记录下来. binheap.h的头文件声明 //description: 使最小堆实 ...

  3. 200行代码解读TDEngine背后的定时器

    作者 | beyondma来源 | CSDN博客 导读:最近几周,本文作者几篇有关陶建辉老师最新的创业项目-TdEngine代码解读文章出人意料地引起了巨大的反响,原以为C语言已经是昨日黄花,不过从读 ...

  4. 基于Libevent最小根堆定时器的C++定时器实现

    在libevent中定时器的实现是通过基于最小堆的优先级队列来实现的,对于这两个数据结构比较陌生的可以去翻算法导论的6.5节中,主要的源码都在min_heap.c中,下面是C++的实现. 数据结构 t ...

  5. 【高性能定时器】时间堆(最小堆)

    最小堆及其应用:时间堆 最小堆及其应用:时间堆 一. 堆 1. 概念 2. 最小堆的实现 3. 性质 4. 代码 二.时间堆 1. 概念简述 2. 实现细节 3. 代码 一. 堆 1. 概念 堆是一种 ...

  6. STM32学习心得十八:通用定时器基本原理及相关实验代码解读

    记录一下,方便以后翻阅~ 主要内容: 1) 三种定时器分类及区别: 2) 通用定时器特点: 3) 通用定时器工作过程: 4) 实验一:定时器中断实验补充知识及部代码解读: 6) 实验二:定时器PWM输 ...

  7. Linux定时器一(最小堆)

    创作人QQ:851301776,邮箱:lfr890207@163.com,欢迎大家一起技术交流,本博客主要是自己学习的心得体会,只为每天进步一点点! 个人座右铭: 1.没有横空出世,只要厚积一定发. ...

  8. Linux服务器开发,定时器方案红黑树,时间轮,最小堆

    ─────────────────────────────────────────────────────────────── ┌------------┐ │▉▉♥♥♥♥♥♥♥♥ 99% │ ♥❤ ...

  9. 后端开发【一大波有用知识】定时器方案红黑树,时间轮,最小堆

    目录: 一.如何组织定时任务? 定时器收网络IO处理造成误差特别大,该怎么处理? 用何种数据机构存储定时器? 红黑树如何解决相同时间的key值的? 最小堆 时间轮 一个帮助理解单层级时间轮的例子 如何 ...

最新文章

  1. 京东姚霆:推理能力,正是多模态技术未来亟需突破的瓶颈!
  2. 【数据迁移】使用传输表空间迁移数据
  3. 第五人格每天服务器维护多长时间,第五人格:每天玩的时间并不长,大概一天1-5局...
  4. Java Error(一)
  5. 解决Ubuntu 14下,PhpStorm 9.x 编辑器界面中文乱码的问题
  6. 读入一段文本到 vector 对象,每个单词存储为 vector 中的一个元素。把 vector 对象中每个单词转化为大写字母。输出 vector 对象中转化后的元素,每八个单词为一行输出。
  7. leetcode 279 四平方定理
  8. 【渝粤题库】广东开放大学 商务办公软件应用与实践 形成性考核
  9. 华为带动涨价?二季度中国市场智能手机均价涨了13%
  10. html的长度单位的选择,在以下几种长度单位中,哪一个是相对于html元素设置长度的?()...
  11. 计算机硬盘各分区名称,电脑分区后修改磁盘名称的方法步骤
  12. css空心三角形_CSS实现空心三角指示箭头
  13. 【转】cp: omitting directory”错误的解释和解决办法
  14. H5如何获取内网IP和公网IP
  15. 哪些窗体置顶得程序是怎么实现得?
  16. rgb的颜色转换以及十六进制转为十进制的那些事
  17. python利用以下公式求π的值_Python 计算 π 值的简单示例
  18. Java 通过EasyExcel导出的Excel文档的字体,背景色,自动列宽等符合要求
  19. 基于小波变化图像融合
  20. 狂神说——Mybatis 学习

热门文章

  1. mapv使用(基于mapbox)
  2. 《几乎必问》Spring 面试题开胃菜
  3. iOS编程 手动忽略clang编译器警告
  4. Unity 3d转2d再转3d
  5. linux crontab 定时任务 邮件问题 及其相关的 dead.letter 问题
  6. Bribe the Prisoners
  7. GetOrganelle软件从ngs数据中组装线粒体、叶绿体基因组;GeSeq网站注释细胞系基因组
  8. 百度推广账户搭建思路
  9. Zabbix监控实战-Tomcat监控
  10. python生成表格并显示在浏览器_python自动打开浏览器下载zip并提取内容写入excel...