Linux网络编程---I/O复用模型之epoll

https://blog.csdn.net/men_wen/article/details/53456474

Linux网络编程—I/O复用模型之epoll

1. epoll模型简介

epoll是Linux多路服用IO接口select/poll的加强版,e对应的英文单词就是enhancement,中文翻译为增强,加强,提高,充实的意思。所以epoll模型会显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。

  • epoll把用户关心的文件描述符上的时间放在内核的一个事件表中,无需像select和poll那样每次调用都重复传入文件描述符集。
  • epoll在获取事件的时候,无需遍历整个被监听的文件描述符集合,而是遍历那些被内核IO事件异步唤醒而加入ready队列的描述符集合。

所以,epoll是Linux大规模高并发网络程序的首选模型。

2.epoll模型的API

epoll使用一组函数来完成任务

2.1 函数epoll_create

创建一个epoll句柄,句柄的英文是handle,相通的意思是把手,把柄。

#include <sys/epoll.h>int epoll_create(int size);
//返回值:若成功,返回一个非负的文件描述符,若出错,返回-1。
  • 该函数返回一个文件描述符,用来唯一标示内核中这个事件表,sizeof参数提示内核要监听的文件描述符个数,这与内存大小有关。
  • 返回的文件描述符将是其他所有epoll系统调用的第一个参数,以指定要访问的内核时间表,所以用该返回的文件描述符相当与其他epoll调用的把手、把柄一样。

查看进程能够打开的最大数目的文件描述符

➜  ~ cat /proc/sys/fs/file-max
1215126
//该值与内存大小有关

修改最大文件描述符限制

➜  ~ sudo vim /etc/security/limits.conf
//重启生效

2.2 函数epoll_ctl

该函数用来操作epoll的内核事件表

#include <sys/epoll.h>int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
//返回值:若成功,返回0,若出错返回-1。
  • epfd就是函数epoll_create创建的句柄。
  • op是指定操作类型,有一下三种 
    • EPOLL_CTL_ADD,向epfd注册fd的上的event
    • EPOLL_CTL_MOD,修改fd已注册的event
    • EPOLL_CTL_DEL,从epfd上删除fd的event 
      1. fd是操作的文件描述符
      2. event指定内核要监听事件,它是struct epoll_event结构类型的指针。epoll_event定义如下:
struct epoll_event {uint32_t     events;      /* Epoll events */epoll_data_t data;        /* User data variable */
};
  • vents成员描述事件类型,将以下宏定义通过位或方式组合

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

typedef union epoll_data {void        *ptr;int          fd;uint32_t     u32;uint64_t     u64;
} epoll_data_t;
  • epoll_data_t是一个联合体,fd指定事件所从属的目标文件描述符。ptr可以用来指定fd相关的用户数据,但两者不能同时使用。

2.3 函数epoll_wait

函数epoll_wait用来等待所监听文件描述符上有事件发生

#include <sys/epoll.h>int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
//返回值:若成功,返回就绪的文件描述符个数,若出错,返回-1,时间超时返回0
  • epfd就是函数epoll_create创建的句柄
  • timeout是超时事件,-1为阻塞,0为立即返回,非阻塞,大于0是指定的微妙
  • events是一个 传入传出参数,它是epoll_event结构的指针,用来从内核得到事件的集合
  • maxevents告知内核events的大小,但不能大于epoll_create()时创建的size

3. LT和ET模式

  • LT(Level Triggered,电平触发):LT模式是epoll默认的工作模式,也是select和poll的工作模式,在LT模式下,epoll相当于一个效率较高的poll。 
    • 采用LT模式的文件描述符,当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序可以不立即处理此事件,当下一次调用epoll_wait是,epoll_wait还会将此事件通告应用程序。
  • ET(Edge Triggered,边沿触发):当调用epoll_ctl,向参数event注册EPOLLET事件时,epoll将以ET模式来操作该文件描述符,ET模式是epoll的高效工作模式. 
    • 对于采用ET模式的文件描述符,当epoll_wait检测到其上有事件发生并将此通知应用程序后,应用程序必须立即处理该事件,因为后续的epoll_wait调用将不在向应用程序通知这一事件。ET模式降低了同意epoll事件被触发的次数,效率比LT模式高。

4. LT和ET的服务端和客户端代码

4.1 服务器端

#include <sys/epoll.h>
#include <fcntl.h>
#include "wrap.h"#define MAX_EVENT_NUM           1024
#define BUFFER_SIZE             10
#define true                    1
#define false                   0int setnonblocking(int fd)
{int old_opt = fcntl(fd, F_GETFD);int new_opt = old_opt | O_NONBLOCK;fcntl(fd, F_SETFD, new_opt);return old_opt;
}//将文件描述符设置为非阻塞的void addfd(int epollfd, int fd, int enable_et)
{struct epoll_event event;event.data.fd = fd;event.events = EPOLLIN;if(enable_et){event.events |= EPOLLET;}epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
//      setnonblocking(fd);
}//将文件描述符fd的EPOLLIN注册到epollfd指示的epoll内核事件表中,enable_et表示是否对fd启用ET模式void lt(struct epoll_event *events, int num, int epollfd, int listenfd)
{char buf[BUFFER_SIZE];for(int i = 0; i < num; i++){int sockfd = events[i].data.fd;if(sockfd == listenfd){struct sockaddr_in clientaddr;socklen_t clilen = sizeof(clientaddr);int connfd = Accept(listenfd, (struct sockaddr *)&clientaddr, &clilen);addfd(epollfd, connfd, false);//对connfd使用默认的lt模式}else if(events[i].events & EPOLLIN){//只要socket读缓存中还有未读的数据,这段代码就会触发printf("event trigger once\n");memset(buf, '\0', BUFFER_SIZE);int ret = recv(sockfd, buf, BUFFER_SIZE-1, 0);if(ret <= 0){Close(sockfd);continue;}printf("get %d bytes of content:%s\n", ret, buf);}else{printf("something else happened\n");}}
}void et(struct epoll_event *event, int num, int epollfd, int listenfd)
{char buf[BUFFER_SIZE];for(int i = 0; i < num; i++){int sockfd = event[i].data.fd;if(sockfd == listenfd){struct sockaddr_in clientaddr;int clilen = sizeof(clientaddr);int connfd = Accept(listenfd, (struct sockaddr *)&clientaddr, &clilen);addfd(epollfd, connfd, true);//多connfd开启ET模式}else if(event[i].events & EPOLLIN){printf("event trigger once\n");while(1){//这段代码不会重复触发,所以要循环读取数据memset(buf, '\0', BUFFER_SIZE);int ret = recv(sockfd, buf, BUFFER_SIZE-1, 0);if(ret < 0){if((errno == EAGAIN) || (errno == EWOULDBLOCK)){printf("read later\n");break;}Close(sockfd);break;}else if(ret == 0){Close(sockfd);}else{printf("get %d bytes of content:%s\n", ret, buf);}}}else{printf("something else happened \n");}}
}int start_ser(char *ipaddr, char *port)
{int sock = Socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in serveraddr;bzero(&serveraddr, sizeof(serveraddr));serveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(atoi(port));inet_pton(AF_INET, ipaddr, &serveraddr.sin_addr);Bind(sock, (struct sockaddr *)&serveraddr, sizeof(serveraddr));Listen(sock, 128);return sock;
}int main(int argc, char *argv[])
{int listenfd = start_ser(argv[1], argv[2]);struct epoll_event events[MAX_EVENT_NUM];int epollfd = epoll_create(5);if(epollfd < 0){perr_exit("epoll_create err");}addfd(epollfd, listenfd, true);while(1){int ret = epoll_wait(epollfd, events, MAX_EVENT_NUM, -1);if(ret < 0){printf("epoll failure\n");break;}lt(events, ret, epollfd, listenfd);//lt模式//et(events, ret, epollfd, listenfd);//et模式}Close(listenfd);return 0;
}
//warp.h文件是将socket,bind,listen等函数封装为第一个字母大写的头文件

4.2 客户端

#include "wrap.h"                                                            int main(int argc, char *argv[])
{int connfd;struct sockaddr_in serveraddr;char buf[1024];connfd = Socket(AF_INET, SOCK_STREAM, 0);bzero(&serveraddr, sizeof(serveraddr));serveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(atoi(argv[2]));inet_pton(AF_INET, argv[1], &serveraddr.sin_addr);Connect(connfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));while(fgets(buf, 1024, stdin) != NULL){Write(connfd, buf, strlen(buf));}Close(connfd);return 0;
}

4.3 两种模式结果对比

ET模式

LT模式 
当发送超过缓冲区大小的数据量,LT会多次调用epoll_wait函数接受数据,则打印了多次“event level once”,而ET则是循环读取数据知道读完,打印了一次“event trigger once”。

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

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

相关文章

POJ 1741tree-点分治入门

学习了一下点分治&#xff0c;如果理解有误还请不吝赐教。 为了快速求得树上任意两点之间距离满足某种关系的点对数&#xff0c;我们需要用到这种算法。 点分治是树上的一种分治算法&#xff0c;依靠树和子树之间的关系进行分治从而降低复杂度。 和其他树上的算法有一些区别…

基于单链表的生产者消费者问题

『生产者与消费者问题分析』「原理」生产者生产产品&#xff0c;消费者消费产品。产品如果被消费者消费完了&#xff0c;同时生产者又没有生产出产品&#xff0c;消费者 就必须等待。同样的&#xff0c;如果生产者生产了产品&#xff0c;而消费者没有去消费&#x…

C++智能指针(二)模拟实现三种智能指针

https://blog.csdn.net/nou_camp/article/details/70186721在上一篇博客中提到了Auto_ptr(C智能指针&#xff08;一&#xff09;)&#xff0c;下面进行模拟实现Auto_ptr 采用类模板实现 #include<iostream> using namespace std; template<class T> class Autoptr …

C++智能指针(三)总结

https://blog.csdn.net/nou_camp/article/details/70195795 在上一篇博客中&#xff08;C智能指针&#xff08;二&#xff09;&#xff09;模拟实现了三种智能指针。 其中最好的就是shared_ptr,但是这并不代表它就是最完美的&#xff0c;它也有问题&#xff0c;这个问题就是循环…

c++11 你需要知道这些就够了

https://blog.csdn.net/tangliguantou/article/details/50549751c11新特性举着火把寻找电灯今天我就权当抛砖引玉&#xff0c;如有不解大家一起探讨。有部分内容是引用自互联网上的内容&#xff0c;如有问题请联系我。T&& 右值引用 std::move 右值引用出现之前我们只能…

c++仿函数 functor

https://www.cnblogs.com/decade-dnbc66/p/5347088.html内容整理自国外C教材先考虑一个简单的例子&#xff1a;假设有一个vector<string>&#xff0c;你的任务是统计长度小于5的string的个数&#xff0c;如果使用count_if函数的话&#xff0c;你的代码可能长成这样&#…

Ubuntu软件更新失败

刚安装好Ubuntu以后需要将系统的软件都更新一下&#xff0c;但是遇到一个问题就是下载仓库信息失败&#xff0c;大概是这个样子的错误&#xff1a; 经国遇到这样的问题可以试一下下面这个命令&#xff1a; sudo rm -rf /var/lib/apt/lists/* sudo apt-get update参考网址&…

getsockname函数与getpeername函数的使用

https://www.tuicool.com/articles/V3Aveygetsockname和getpeername函数 getsockname函数用于获取与某个套接字关联的本地协议地址 getpeername函数用于获取与某个套接字关联的外地协议地址 定义如下&#xff1a;[cpp] view plaincopy#include<sys/socket.h> int gets…

Linux命令【一】基本命令

shell命令和bash命令相同&#xff0c;指的是命令解析器 快捷键 history 所有的历史命令ctrl P 向上滚动命令 ctrl N 向下滚动命令 ctrlB将光标向前移动 ctrlF将光标向后移动 ctrlA移动到命令行头部 ctrlE移动到命令行尾部 光标删除操作&#xff1a;删除光标前面字符ctrlh或…

剑指offer面试题:替换空格

https://blog.csdn.net/yanxiaolx/article/details/52235212题目&#xff1a;请实现一个函数&#xff0c;把字符串中的每个空格替换成“%20”。例如输入“We are happy.”&#xff0c;则输出“We%20are%20happy.”。解析&#xff1a;时间复杂度为O(n)的解法。完整代码及测试用例…

数据库原理及应用【一】引言

什么是数据库&#xff1a;一个大规模的集成的数据集合 作用&#xff1a;描述现实世界的实体(entities)以及实体之间的关系 管理数据库的系统软件&#xff1a;DBMS 文件是一个平滑的字符流&#xff0c;无法完成信息的检索和管理 数据&#xff08;data&#xff09;:用来描述现…

用c++模拟实现一个学生成绩管理系统

https://blog.csdn.net/yanxiaolx/article/details/53393437题目&#xff1a;用c模拟实现一个学生成绩的信息管理系统&#xff0c;要求能添加、删除、修改、查看和保存学生的信息等功能 源代码如下:[cpp] view plaincopy#define _CRT_SECURE_NO_WARNINGS #include<iostr…

Python3列表

操作&#xff1a;索引、切片、加、乘、检查成员、确定序列长度、确定最大最小元素 定义&#xff1a; 列表名 [元素]下标列表名[x] 截取:列表名[x:y] 更新&#xff1a; list[x]y 或者使用append()方法添加列表项删除&#xff1a; del list[x]常用操作&#xff1a; 截取与…

Linux惊群效应详解(最详细的了吧)

https://blog.csdn.net/lyztyycode/article/details/78648798?locationNum6&fps1 linux惊群效应详细的介绍什么是惊群&#xff0c;惊群在线程和进程中的具体表现&#xff0c;惊群的系统消耗和惊群的处理方法。1、惊群效应是什么&#xff1f;惊群效应也有人叫做雷鸣群体效应…

epoll原理详解(最清晰)

https://blog.csdn.net/lyztyycode/article/details/79491419我只是把内容搬运过来做个记录&#xff0c;方便自己以后回头看。第一部分&#xff1a;select和epoll的任务关键词&#xff1a;应用程序 文件句柄 用户态 内核态 监控者要比较epoll相比较select高效在什么地方&#x…

Ubuntu卸载软件

用过使用dpkg软件管理工具得到所有已经安装的软件&#xff0c;如果不清楚软件的全名可以使用grep命令进行查找 然后再使用sudo apt-get remove --purge 软件名卸载软件&#xff08;--purge参数会删除配置文件&#xff0c;删的干净一些&#xff09; 例如&#xff1a;

一个重要且实用的signal---SIGCHLD

https://blog.csdn.net/lyztyycode/article/details/78150805SIGCHLD(修改)因为笔者之前的文章里面有错误&#xff0c;今天发现&#xff0c;立马做个修改。在下面我的一段关于sigchld信号相对于直接调用wait函数的好处时&#xff0c;我说调用wait函数要一直检测子进程是否执行完…

Python3函数和代码复用

函数的定义 def 函数名([参数列表]):注释函数体注意事项 函数形参不需要声明类型&#xff0c;可以使用return语句在结束函数执行的同时返回任意类型的值&#xff0c;函数返回值类型与return语句返回表达式i的类型一致 即使该函数不需要接受任何参数&#xff0c;也必须保留一堆…

一文说尽C++赋值运算符重载函数(operator=)

http://www.cnblogs.com/zpcdbky/p/5027481.html在前面&#xff1a;关于C的赋值运算符重载函数(operator)&#xff0c;网络以及各种教材上都有很多介绍&#xff0c;但可惜的是&#xff0c;内容大多雷同且不全面。面对这一局面&#xff0c;在下在整合各种资源及融入个人理解的基…

Python a和a[:]的区别

简单来讲a[:]是深复制&#xff0c;a是浅复制&#xff0c;相当于赋值a的话是赋值了指针&#xff0c;赋值a[:]相当于复制了a对应的那段空间 例如&#xff1a; a [1,1,1,1,1,1]for x in a:if x1:a.remove(x)print(a)运行结果&#xff1a; remove操作是移除序列中第一个x元素。…