WebServer代码解读(3)【最小堆定时器与队列】
文章目录
- 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函数内部逻辑如下:
- 申请一块内存,接收新的client socket连接(调用accept函数),将其放到刚刚申请的内存上
- 设置client socket为非阻塞
- 为client socket申请request内存,设置事件集合,并在内核事件表中注册
- 使用最小堆实现的定时器管理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结构体中,主要的逻辑为:
- 从产生request的fd中读取数据
- 针对该request的类型(URI、报头、报体、其他)进行对应类型的解析
- 若没有完成该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)【最小堆定时器与队列】相关推荐
- 使用最小堆使用优先级队列(c语言版本)
下面的例子来自Weiss的<数据结构与算法分析:c语言描述>,自己亲自敲了一遍,跑了个demo,并将结果记录下来. binheap.h的头文件声明 //description: 使最小堆实 ...
- 优先级队列 c语言,使用最小堆使用优先级队列(c语言版本)
下面的例子来自Weiss的<数据结构与算法分析:c语言描述>,自己亲自敲了一遍,跑了个demo,并将结果记录下来. binheap.h的头文件声明 //description: 使最小堆实 ...
- 200行代码解读TDEngine背后的定时器
作者 | beyondma来源 | CSDN博客 导读:最近几周,本文作者几篇有关陶建辉老师最新的创业项目-TdEngine代码解读文章出人意料地引起了巨大的反响,原以为C语言已经是昨日黄花,不过从读 ...
- 基于Libevent最小根堆定时器的C++定时器实现
在libevent中定时器的实现是通过基于最小堆的优先级队列来实现的,对于这两个数据结构比较陌生的可以去翻算法导论的6.5节中,主要的源码都在min_heap.c中,下面是C++的实现. 数据结构 t ...
- 【高性能定时器】时间堆(最小堆)
最小堆及其应用:时间堆 最小堆及其应用:时间堆 一. 堆 1. 概念 2. 最小堆的实现 3. 性质 4. 代码 二.时间堆 1. 概念简述 2. 实现细节 3. 代码 一. 堆 1. 概念 堆是一种 ...
- STM32学习心得十八:通用定时器基本原理及相关实验代码解读
记录一下,方便以后翻阅~ 主要内容: 1) 三种定时器分类及区别: 2) 通用定时器特点: 3) 通用定时器工作过程: 4) 实验一:定时器中断实验补充知识及部代码解读: 6) 实验二:定时器PWM输 ...
- Linux定时器一(最小堆)
创作人QQ:851301776,邮箱:lfr890207@163.com,欢迎大家一起技术交流,本博客主要是自己学习的心得体会,只为每天进步一点点! 个人座右铭: 1.没有横空出世,只要厚积一定发. ...
- Linux服务器开发,定时器方案红黑树,时间轮,最小堆
─────────────────────────────────────────────────────────────── ┌------------┐ │▉▉♥♥♥♥♥♥♥♥ 99% │ ♥❤ ...
- 后端开发【一大波有用知识】定时器方案红黑树,时间轮,最小堆
目录: 一.如何组织定时任务? 定时器收网络IO处理造成误差特别大,该怎么处理? 用何种数据机构存储定时器? 红黑树如何解决相同时间的key值的? 最小堆 时间轮 一个帮助理解单层级时间轮的例子 如何 ...
最新文章
- 京东姚霆:推理能力,正是多模态技术未来亟需突破的瓶颈!
- 【数据迁移】使用传输表空间迁移数据
- 第五人格每天服务器维护多长时间,第五人格:每天玩的时间并不长,大概一天1-5局...
- Java Error(一)
- 解决Ubuntu 14下,PhpStorm 9.x 编辑器界面中文乱码的问题
- 读入一段文本到 vector 对象,每个单词存储为 vector 中的一个元素。把 vector 对象中每个单词转化为大写字母。输出 vector 对象中转化后的元素,每八个单词为一行输出。
- leetcode 279 四平方定理
- 【渝粤题库】广东开放大学 商务办公软件应用与实践 形成性考核
- 华为带动涨价?二季度中国市场智能手机均价涨了13%
- html的长度单位的选择,在以下几种长度单位中,哪一个是相对于html元素设置长度的?()...
- 计算机硬盘各分区名称,电脑分区后修改磁盘名称的方法步骤
- css空心三角形_CSS实现空心三角指示箭头
- 【转】cp: omitting directory”错误的解释和解决办法
- H5如何获取内网IP和公网IP
- 哪些窗体置顶得程序是怎么实现得?
- rgb的颜色转换以及十六进制转为十进制的那些事
- python利用以下公式求π的值_Python 计算 π 值的简单示例
- Java 通过EasyExcel导出的Excel文档的字体,背景色,自动列宽等符合要求
- 基于小波变化图像融合
- 狂神说——Mybatis 学习
热门文章
- mapv使用(基于mapbox)
- 《几乎必问》Spring 面试题开胃菜
- iOS编程 手动忽略clang编译器警告
- Unity 3d转2d再转3d
- linux crontab 定时任务 邮件问题 及其相关的 dead.letter 问题
- Bribe the Prisoners
- GetOrganelle软件从ngs数据中组装线粒体、叶绿体基因组;GeSeq网站注释细胞系基因组
- 百度推广账户搭建思路
- Zabbix监控实战-Tomcat监控
- python生成表格并显示在浏览器_python自动打开浏览器下载zip并提取内容写入excel...