IO多路转接模型-----epoll

epoll:

Linux下性能最高的多路转接模型

epoll 有3个相关的系统调用.

epoll_create

功能:创建epoll,在内核中创建eventpoll结构体,size决定了epoll最多监控多少个描述符,在Linux2.6.8之后被忽略,但是必须>0。返回一个文件描述符,作为epoll的操作句柄

struct eventpoll{
...rb_root rbr(红黑树)...struct list_head rdlist(双向链表)...
}
int epoll_create(int size)

创建一个epoll的句柄.

  • 自从linux2.6.8之后,size参数是被忽略的.
  • 用完之后, 必须调用close()关闭

epoll_ctl

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) 

功能:对内核中的eventpoll 结构体进行操作:epoll采用事件结构方式对描述符进行事件监控;用户定义struct epoll_event描述符事件结构信息;将事件信息可以拷贝到内核添加到eventpoll结构体中的红黑数中

参数说明
  • 它不同于select()是在监听事件时告诉内核要监听什么类型的事件, 而是在这里先注册要监听的事件类型.
  • 第一个参数是epoll_create()的返回值(epoll的句柄).
  • 第二个参数表示动作,用三个宏来表示. 、
  • 第三个参数是需要监听的fd.
  • 第四个参数是告诉内核需要监听什么事. 描述符对应的事件结构信息
第二个参数的取值:
  • EPOLL_CTL_ADD :注册新的fd到epfd中;向红黑数中添加描述符的监控事件结构信息event
  • EPOLL_CTL_MOD :修改已经注册的fd的监听事件;修改描述符在红黑数中的对应事件结构信息event
  • EPOLL_CTL_DEL :从epfd中删除一个fd,从红黑数中移除描述符的监控事件结构信息event

struct epoll_event结构如下

struct epoll_event { 
uint32_t events; /* 用户对描述符进行监控的事件 */ 
epoll_data_t data; /* User data variable */ 
};typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t;

events可以是以下几个宏的集合:

  1. EPOLLIN : 表示对应的文件描述符可以读 (包括对端SOCKET正常关闭);
  2. EPOLLOUT : 表示对应的文件描述符可以写;
  3. EPOLLPRI : 表示对应的文件描述符有紧急的数据可读 (这里应该表示有带外数据到来);
  4. EPOLLERR : 表示对应的文件描述符发生错误;
  5. EPOLLHUP : 表示对应的文件描述符被挂断;
  6. EPOLLET : 将EPOLL设为边缘触发(Edge Triggered)模式, 这是相对于水平触发(Level Triggered)来说的.
  7. EPOLLONESHOT:只监听一次事件, 当监听完这次事件之后, 如果还需要继续监听这个socket的话, 需要 再次把这个socket加入到EPOLL队列里.

epoll_wait

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) 
  • epfd: epoll 操作句柄
  • events:epoll_event 事件结构信息数组的结点数量
  • maxevents : epoll_event事件结构信息数组的结点数量
  • timeout:epoll_wait 监控的等待超时时间
  • 返回值:<0----监控出错 ==0----监控超时 >0----就绪的描述符事件个数
    收集在epoll监控的事件中已经发送的事件
  1. 参数events是分配好的epoll_event结构体数组.
  2. epoll将会把发生的事件赋值到events数组中 (events不可以是空指针,内核只负责把数据复制到这个 events数组中,不会去帮助我们在用户态中分配内存).
  3. maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size.
  4. 参数timeout是超时时间 (毫秒,0会立即返回,-1是永久阻塞).
  5. 如果函数调用成功,返回对应I/O上已准备好的文件描述符数目,如返回0表示已超时, 返回小于0表示函 数失败

epoll_wait 会将就绪的描述符对应事件结构信息拷贝到events结构数组中;相当于直接告诉用户哪个描述符就绪;用户直接就从epoll_event 结构体数组中取出信息,对描述符直接进行相应事件操作

epoll监控流程

在这里插入图片描述

  1. epoll对描述符的事件监控是一个异步操作;epoll_wait发起调用,让操作系统对描述符进行相应事件监控
  2. 操作系统对每个要监控的描述符都定义了就绪事件回调函数;当描述符相应事件就绪的时候,触发事件,调用回调函数(将描述符事件结构信息指针添加到eventpoll的双向链表中)
  3. 但是epoll_wait并没有直接返回(是一个阻塞操作),每隔一会就看一下eventpoll中双向链表是否为空;来判断是否有描述符就绪;若为空,则没有描述符就绪;则等待一会,重新查看
  4. 若双向链表不为空------表示有描述符事件就绪;将这个描述符对应的事件结构信息,拷贝到epoll_wait传入的事件结构数组中后调用返回。

epoll工作原理

struct eventpoll{
...rb_root rbr(红黑树)...struct list_head rdlist(双向链表)...
}

当某一进程调用epoll_create方法时,Linux内核会创建一个eventpoll结构体,这个结构体中有两个成 员与epoll的使用方式密切相关.

  • 每一个epoll对象都有一个独立的eventpoll结构体,用于存放通过epoll_ctl方法向epoll对象中添加进来 的事件.
  • 这些事件都会挂载在红黑树中,如此,重复添加的事件就可以通过红黑树而高效的识别出来(红黑树的插 入时间效率是lgn,其中n为树的高度).
  • 而所有添加到epoll中的事件都会与设备(网卡)驱动程序建立回调关系,也就是说,当响应的事件发生时 会调用这个回调方法.
  • 这个回调方法在内核中叫ep_poll_callback,它会将发生的事件添加到rdlist双链表中.
  • 在epoll中,对于每一个事件,都会建立一个epitem结构体.
    在这里插入图片描述
  • 当调用epoll_wait检查是否有事件发生时,只需要检查eventpoll对象中的rdlist双链表中是否有epitem 元素即可.
  • 如果rdlist不为空,则把发生的事件复制到用户态,同时将事件数量返回给用户. 这个操作的时间复杂度 是O(1).
/*                                                                                                                                                                                                               * 这个程序完成epoll接口的基本封装* bool Init()* bool Add(TcpSocket &sock)* bool Del(TcpSocket &sock)* bool Wait(std::vector<TcpSocket>&list,int timeout_msec)*/#include<iostream>
#include<vector>
#include<sys/epoll.h>
#include"tcpsocket.hpp"#define MAX_EVENTS 10
class Epoll
{public:bool Init(){//int epoll_create(int size)_epfd = epoll_create(1);if(_epfd < 0){ perror("epoll create error");return false;}   return true;}   bool Add(TcpSocket &sock){//int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) int fd = sock.GetFd();struct epoll_event ev; ev.data.fd = fd; ev.events = EPOLLIN;int ret = epoll_ctl(_epfd, EPOLL_CTL_ADD, fd , &ev);if(ret < 0){ perror("epoll add error");return false;}   return true;}   bool Del(TcpSocket &sock){int fd = sock.GetFd();int ret =epoll_ctl(_epfd, EPOLL_CTL_DEL, fd,NULL);if(ret < 0){perror("epoll del error");return false;}return true;}bool Wait(std::vector<TcpSocket>&list,int timeout_msec = 3000){//int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) struct epoll_event evs[MAX_EVENTS];int nfds = epoll_wait(_epfd, evs, MAX_EVENTS, timeout_msec);if(nfds < 0){perror("epoll wait error");}else if(nfds == 0){std::cerr<<"epoll wait timeout";return false;}for(int i =0 ; i < nfds; i++ ){TcpSocket sock;sock.SetFd(evs[i].data.fd);list.push_back(sock);}return true;}private:int _epfd;};int  main()
{TcpSocket lst_sock;CHECK_RET(lst_sock.Socket());CHECK_RET(lst_sock.Bind("0.0.0.0",9000));CHECK_RET(lst_sock.Listen());Epoll epoll;CHECK_RET(epoll.Init());CHECK_RET(epoll.Add(lst_sock));while(1){std::vector<TcpSocket>list;bool ret = epoll.Wait(list);if(ret == false){            continue;}for(int i =0 ;i < list.size();i++){if(lst_sock.GetFd() == list[i].GetFd()){TcpSocket cli_sock;std::string cli_ip;uint16_t cli_port;ret = lst_sock.Accept(cli_sock,cli_ip,cli_port);if(ret == false){continue;}epoll.Add(cli_sock);}else{std::string buf;ret = list[i].Recv(buf);if(ret == false){//接收出错epoll.Del(list[i]);list[i].Close();continue;}std::cout<< "client-say:"<< buf <<std::endl;}}}lst_sock.Close();return 0;
}

epoll事件触发方式:

水平触发–EPOLLT/边缘触发EPOLLET

水平触发方式

可读事件就绪:接受缓冲区数据大小,大于低水位标记(默认1字节)
可写事件就绪:发送缓冲区中空闲大小,大于低水位标记(默认1字节)
只要接受/发送缓冲区中数据/剩余空间大小大于低水平标记就会一直触发事件

边缘触发方式

可读事件就绪:接受缓冲区中,只有新数据到来的时候才会触发一次
可写事件就绪:发送缓冲区中,只有从剩余空间大小从0变为大于0的时候才会触发

注意事项

边缘触发方式中,只有新数据到来的时候,可读事件才会触发一次
需要用户在这一次事件触发中将缓冲区中的数据全部读取完毕(循环读,直到不能读为止)
但是套接字默认recv没有数据的时候会阻塞;为了避免循环读取数据导致程序流程因为阻塞而无法继续推进,因此需要将描述符设置为非阻塞

fcntl
int fcntl(int fd,int cmd, .../*arg*/);fd : 要设置的描述符cmd : 对描述符要进行的操作F_SETFL 通过arg参数设置描述符属性状态F_GETFL 返回描述符的属性状态信息 ,arg被忽略arg:要设置的描述符属性状态信息O_NONBLOCK  将描述符设置为非阻塞

epoll优缺点分析

  1. epoll采用事件结构方式对描述符进行监控,简化了select集合操作的流程
  2. epoll描述符监控数量无上限
  3. 每个epoll监控的描述符事件信息,只需要向内核拷贝一次
  4. epoll_wait使用异步阻塞操作在内核中完成事件监控;事件监控是操作系统通过事件回调的方式就绪描述符事件信息添加到双向链表中;而epoll_wait只是每隔一段时间看一下双向链表是否为空判断是否有描述符就绪(并非轮询遍历)性能不会随着描述符增多而降低
  5. epoll直接通过epoll_wait传入的时间结构数组向用户返回就绪的事件信息;可以直接告诉用户哪些描述符就绪,不需要用户进行空遍历查找

缺点

  1. 不能跨平台

IO多路转接模型的适用场景

对大量描述符进行监控,但是同一时间只有少量描述符活跃的场景

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/383181.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

再写单链表(不带头单链表)

单链表 实际中链表的结构非常多样&#xff0c;以下情况组合起来就有8种链表结构&#xff1a; 单向、双向带头、不带头循环、非循环 虽然有这么多的链表的结构&#xff0c;但是我们实际中最常用还是两种结构&#xff1a; 无头单向非循环链表&#xff1a;结构简单&#xff0…

再写双向循环链表

#pragma once #include<assert.h> #include<malloc.h> #include<stdio.h> typedef int DLDataType;//定义链表结点结构 typedef struct DListNode{DLDataType value;struct DListNode *prev; //指向前一个结点struct DListNode *next; //指向后一个结点 } DL…

链表题目--1 删除链表中所有等于val的值

注意事项 要删除的结点相邻第一个结点就是要删除的结点 /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/struct ListNode* removeElements(struct ListNode* head, int val){if(headNULL){return NULL;}struct …

链表题目--2 求链表的中间结点 和 求链表中倒数第k个结点

求链表的中间结点 思路 一个走两步&#xff0c;一个走一步。一个走到尾&#xff0c;另外一个就走到了中间 /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/struct ListNode* middleNode(struct ListNode* head…

链表题目---3 合并两个有序单链表 和 分割链表

合并两个有序单链表 /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){if(l1 NULL){return l2;}else if(l2 NULL){return l1;}struc…

链表题目---4 删除链表中重复的结点 和 判断链表是否为回文链表

删除链表中重复的结点 /* struct ListNode {int val;struct ListNode *next;ListNode(int x) :val(x), next(NULL) {} }; */ class Solution { public:ListNode* deleteDuplication(ListNode* pHead){if(pHead NULL){return NULL;}ListNode *prev NULL; //用于删除的结点, 是…

链表题目----5 相交链表 和 环形链表 和 返回链表开始入环的第一个节点

相交链表 思路 链表交叉不可能是x型因为有可能两个链表不等长&#xff0c;所以我们必须让他们从同一起跑位置去起跑从同一起跑位置出发&#xff0c;依次比较每个结点的地址是否相同 /*** Definition for singly-linked list.* struct ListNode {* int val;* struct L…

链表题目---6 复制带随机指针的链表

思路 将新结点放在老结点的后面 复制random 将链表拆开 /* // Definition for a Node. class Node { public:int val;Node* next;Node* random;Node() {}Node(int _val, Node* _next, Node* _random) {val _val;next _next;random _random;} }; */ class Solution { publi…

括号匹配问题(c和c++版本实现)

括号匹配问题 思路 遇见左括号入栈&#xff0c;遇见一个右括号弹出栈顶元素右括号入栈前如果栈已经为空&#xff0c;则不匹配如果不为空则读取并弹出&#xff0c;弹出来的元素与右括号做比较&#xff0c;必须匹配&#xff0c;不匹配返回false;如果最后栈里还有元素&#xff0c…

用队列实现栈 AND 用栈实现队列

用队列实现栈 思路 入队列就是入栈出队列的时候&#xff0c;就是把前面size-1个队列中的元素先出&#xff0c;这样最后一个元素就成队首元素了&#xff0c;再把出去的元素再次入队列读栈顶元素&#xff0c;过程和第二步是一样的&#xff0c;就是弹出后&#xff0c;再把它入队列…

最小栈的实现(设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈。)

最小栈的实现 思路 两个栈&#xff0c;左边栈接受元素&#xff0c;右边栈存最小的元素入栈时&#xff0c;先入左边栈&#xff0c;随后进行比较&#xff0c;左边和右边栈顶元素进行比较&#xff0c;如果新元素小&#xff0c;就把新元素放在右边的栈顶位置&#xff0c;如果新元素…

再写循环队列----c++实现

再写循环队列 class MyCircularQueue { public:/** Initialize your data structure here. Set the size of the queue to be k. */MyCircularQueue(int k) {array (int *)malloc(sizeof(int)*k);capacity k;size 0;front 0;rear 0;}/** Insert an element into the circu…

再谈二叉树(二叉树概念,二叉树的性质,二叉树的存储结构)

树的概念 树的概念 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做树是因 为它看起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的。它具有以下的特点&#xff1a;每个…

二叉树题目----1 前序中序后序遍历二叉树并返回相应的遍历(不是打印)

前序遍历 /*** Definition for a binary tree node.* struct TreeNode {* int val;* struct TreeNode *left;* struct TreeNode *right;* };*//*** Note: The returned array must be malloced, assume caller calls free().*/ int *array; int size;void _preorde…

二叉树题目----2 检查两颗树是否相同 和 对称二叉树的判定

检查两颗树是否相同 思路 根要相等 p->val q->val左子树相等 isSameTree(p->left,q->left)右子树也要相等 isSameTree(p->right,q->right) /*** Definition for a binary tree node.* struct TreeNode {* int val;* struct TreeNode *left;* …

二叉树题目---3 另一个树的子树 AND 二叉树最大深度

另一个树的子树 思路 两个数都遍历一遍&#xff0c;找到一个根结点相同时&#xff0c;判断以这个根结点为首的二叉树是否相等 前序遍历判断两棵树是否相同对于返回值的处理是难点 bool isSameTree(struct TreeNode *p, struct TreeNode *q) {if(p NULL && q NULL)…

二叉树题目----4 前序遍历重构二叉树 AND 求二叉树中所有结点的个数

前序遍历重构二叉树 思路 整个二叉树用数组存储因为先序遍历它先遍历根&#xff0c;再遍历左&#xff0c;左边没有跑完是不会去遍历右边的&#xff0c;所以遍历左子树&#xff0c;就是数组元素每回向后一个&#xff0c;个数-1遍历右边时&#xff0c;就是数组起始位置左子树跑到…

二叉树的进阶操作---(求二叉树中所有结点个数,求叶子结点个数,求第k层结点个数;在二叉树中查找某一结点;层序遍历;判断是否为完全二叉树)

typedef struct TreeNode {struct TreeNode *left;struct TreeNode *right;char val; }TreeNode;typedef struct Result{TreeNode * root; //构建的树的根结点int used; //构建过程中用掉的val个数 } Result;求二叉树中所有结点个数 void TreeSize(TreeNode *root, int …

c++中的智能指针详解(RAII, auto_ptr的原理及其改进,unique_ptr的原理,shared_ptr的原理,shared_ptr的缺陷及其改进)

为什么需要智能指针 代码中途退出&#xff0c;也能保证资源的合理释放&#xff0c;在c中没有垃圾回收机制的情况下&#xff0c;智能指针就可以保证我们申请的资源&#xff0c;最后忘记释放的问题&#xff0c;防止内存泄露&#xff0c;也帮我们减少了一定的负担&#xff0c;不用…

二叉树题目----5 平衡二叉树 AND 根据二叉树创建字符串

平衡二叉树 /*** Definition for a binary tree node.* struct TreeNode {* int val;* struct TreeNode *left;* struct TreeNode *right;* };*/int MAX(int a,int b) {return a > b ? a : b; }//求高度 int getHeight(struct TreeNode *root){if(root NULL){…