Linux epoll的用法

Linux epoll的用法

epollfd_create函数

#include <sys/epoll.h>int epoll_create (int __size)
参数含义
__size此参数从Linux 2.6.8后就不再使用了,但必须设置成大于零的值
返回值含义
>0可用的epollfd
-1调用失败

epollfd_ctl函数

有了epollfd,我们需要将要检测事件的fd绑定到这个epollfd上,或者修改或者移除,使用epollfd_ctl完成

int epoll_ctl (int __epfd, int __op, int __fd,struct epoll_event *__event) 
参数含义
__epfd上文中的epollfd
__op操作类型1.(EPOLLFD_CTL_ADD)添加2.(EPOLLFD_CTL_MOD)修改3.(EPOLLFD_CTL_DEL)移除
__fd需要被操作的描述符fd
epoll_event *__event这是一个epollfd_event结构体地址,下文解释
struct epoll_event
{uint32_t events;	/* 需要检测fd事件标志 */epoll_data_t data;	/* 用户自定义的数据 */
}

其中的epoll_data_t

typedef union epoll_data
{void *ptr;int fd;uint32_t u32;uint64_t u64;
} epoll_data_t;
返回值含义
0成功
-1失败

epollfd_wait函数

int epoll_wait (int __epfd, struct epoll_event *__events,int __maxevents, int __timeout);
参数含义
epoll_event *__events输出参数,在函数调用成功后,events中存放的是与就绪事件相关的epoll_event结构体数组
__maxevents上述数组中元素的个数
返回值含义
>0有事件的fd的数量
0超时
-1失败

示例:

int main()
{epoll_event epoll_events[1024];int n = epoll_wait(epollfd,epoll_events,1024,1000);if(n<0) {//被信号中断if(errno == EINTR){//...}}else if(n ==0){//...}for (size_t i = 0; i < n; ++i) {if(epoll_events[i].events & EPOLLIN){//处理可读事件} else if (epoll_events[i].events & EPOLLOUT){//处理可写事件} else if (epoll_events[i].events & EPOLLERR){//处理出错事件}}
}

pollepoll_wait函数的区别

image-20210708153552180

边缘触发模式(ET) 和 水平触发模式 (LT)

水平触发模式:一个事件只要有,就会一直触发

边缘触发模式:一个事件从无到有才会触发

想不出好例子,摘抄一个吧

水平触发
儿子:妈妈,我收到了500元的压岁钱。
妈妈:嗯,省着点花。
儿子:妈妈,我今天花了200元买了个变形金刚。
妈妈:以后不要乱花钱。
儿子:妈妈,我今天买了好多好吃的,还剩下100元。
妈妈:用完了这些钱,我可不会再给你钱了。
儿子:妈妈,那100元我没花,我攒起来了
妈妈:这才是明智的做法!
儿子:妈妈,那100元我还没花,我还有钱的。
妈妈:嗯,继续保持。
儿子:妈妈,我还有100元钱。
妈妈:…

接下来的情形就是没完没了了:只要儿子一直有钱,他就一直会向他的妈妈汇报。LT模式下,只要内核缓冲区中还有未读数据,就会一直返回描述符的就绪状态,即不断地唤醒应用进程。在上面的例子中,儿子是缓冲区,钱是数据,妈妈则是应用进程了解儿子的压岁钱状况(读操作)。

边缘触发
儿子:妈妈,我收到了500元的压岁钱。
妈妈:嗯,省着点花。
(儿子使用压岁钱购买了变形金刚和零食。)
儿子:
妈妈:儿子你倒是说话啊?压岁钱呢?

这个就是ET模式,儿子只在第一次收到压岁钱时通知妈妈,接下来儿子怎么把压岁钱花掉并没有通知妈妈。即儿子从没钱变成有钱,需要通知妈妈,接下来钱变少了,则不会再通知妈妈了。在ET模式下, 缓冲区从不可读变成可读,会唤醒应用进程,缓冲区数据变少的情况,则不会再唤醒应用进程。

/** * 验证epoll的LT与ET模式的区别,epoll_server.cpp* zhangyl 2019.04.01*/
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/epoll.h>
#include<poll.h>
#include<iostream>
#include<string.h>
#include<vector>
#include<errno.h>
#include<iostream>int main()
{//创建一个监听socketint listenfd = socket(AF_INET, SOCK_STREAM, 0);if (listenfd == -1){std::cout << "create listen socket error" << std::endl;return -1;}//设置重用ip地址和端口号int on = 1;setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (char*)&on, sizeof(on));setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT, (char*)&on, sizeof(on));//将监听socker设置为非阻塞的int oldSocketFlag = fcntl(listenfd, F_GETFL, 0);int newSocketFlag = oldSocketFlag | O_NONBLOCK;if (fcntl(listenfd, F_SETFL, newSocketFlag) == -1){close(listenfd);std::cout << "set listenfd to nonblock error" << std::endl;return -1;}//初始化服务器地址struct sockaddr_in bindaddr;bindaddr.sin_family = AF_INET;bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);bindaddr.sin_port = htons(3000);if (bind(listenfd, (struct sockaddr*)&bindaddr, sizeof(bindaddr)) == -1){std::cout << "bind listen socker error." << std::endl;close(listenfd);return -1;}//启动监听if (listen(listenfd, SOMAXCONN) == -1){std::cout << "listen error." << std::endl;close(listenfd);return -1;}//创建epollfdint epollfd = epoll_create(1);if (epollfd == -1){std::cout << "create epollfd error." << std::endl;close(listenfd);return -1;}epoll_event listen_fd_event;listen_fd_event.data.fd = listenfd;listen_fd_event.events = EPOLLIN;//取消注释掉这一行,则使用ET模式//listen_fd_event.events |= EPOLLET;//将监听sokcet绑定到epollfd上去if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &listen_fd_event) == -1){std::cout << "epoll_ctl error" << std::endl;close(listenfd);return -1;}int n;while (true){epoll_event epoll_events[1024];n = epoll_wait(epollfd, epoll_events, 1024, 1000);if (n < 0){//被信号中断if (errno == EINTR) continue;//出错,退出break;}else if (n == 0){//超时,继续continue;}for (size_t i = 0; i < n; ++i){//事件可读if (epoll_events[i].events & EPOLLIN){if (epoll_events[i].data.fd == listenfd){//侦听socket,接受新连接struct sockaddr_in clientaddr;socklen_t clientaddrlen = sizeof(clientaddr);int clientfd = accept(listenfd, (struct sockaddr*)&clientaddr, &clientaddrlen);if (clientfd != -1){int oldSocketFlag = fcntl(clientfd, F_GETFL, 0);int newSocketFlag = oldSocketFlag | O_NONBLOCK;if (fcntl(clientfd, F_SETFL, newSocketFlag) == -1){close(clientfd);std::cout << "set clientfd to nonblocking error." << std::endl;}else{epoll_event client_fd_event;client_fd_event.data.fd = clientfd;client_fd_event.events = EPOLLIN;//取消注释这一行,则使用ET模式//client_fd_event.events |= EPOLLET; if (epoll_ctl(epollfd, EPOLL_CTL_ADD, clientfd, &client_fd_event) != -1){std::cout << "new client accepted,clientfd: " << clientfd << std::endl;}else{std::cout << "add client fd to epollfd error" << std::endl;close(clientfd);}}}}else{std::cout << "client fd: " << epoll_events[i].data.fd << " recv data." << std::endl;//普通clientfdchar ch;//每次只收一个字节int m = recv(epoll_events[i].data.fd, &ch, 1, 0);if (m == 0){//对端关闭了连接,从epollfd上移除clientfdif (epoll_ctl(epollfd, EPOLL_CTL_DEL, epoll_events[i].data.fd, NULL) != -1){std::cout << "client disconnected,clientfd:" << epoll_events[i].data.fd << std::endl;}close(epoll_events[i].data.fd);}else if (m < 0){//出错if (errno != EWOULDBLOCK && errno != EINTR){if (epoll_ctl(epollfd, EPOLL_CTL_DEL, epoll_events[i].data.fd, NULL) != -1){std::cout << "client disconnected,clientfd:" << epoll_events[i].data.fd << std::endl;}close(epoll_events[i].data.fd);}}else{//正常收到数据std::cout << "recv from client:" << epoll_events[i].data.fd << ", " << ch << std::endl;}}}else if (epoll_events[i].events & POLLERR){// TODO 暂不处理}}}close(listenfd);return 0;
}

image-20210708163455964

现在采用的是一个水平模式,只要有数据可读,就会触发事件

现在采用边缘触发模式

image-20210708163804287

采用边缘触发模式,只有有新数据到来才会触发,所以就有了上面的现象

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

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

相关文章

windows网络编程

windows网络编程 TCP编程 服务端 这里我们有几点需要注意: 使用WSAStartup初始化网络库,即将与socket函数相关dll文件加载到进程地址空间中退出时,使用WSACleanup()卸载相关dll文件与Linux使用close函数关闭socket不同,windows需要使用closesocket函数关闭socket WSAStart…

TCP服务器epoll的多种实现

TCP服务器epoll的多种实现 对于网络IO会涉及到两个系统对象 用户空间中进程或者线程操作系统内核 比如发生read操作时就会经历两个阶段 等待数据就绪将数据从内核缓冲区拷贝到用户缓冲区 由于各个阶段多有不同的情况,一组合么就产生了多种网络 IO 模型 阻塞IO 在Linux中…

侯捷面向对象高级编程(二)

侯捷面向对象高级编程(二) 转换函数 转换函数没有返回值,返回值就是double即函数名,不需要自己写因为转换函数一般不会改变其中内容,所以要加const限定 两条路都可以走,就回产生歧义,报错 explict禁止自动转换,于是4无法转转换为Fraction pointer-like cliasses ->作用之后…

拒绝了对对象 'sp_sdidebug'(数据库 'master',所有者 'dbo')的 EXECUTE 权限

在.net中调用时出现“拒绝了对对象 sp_sdidebug&#xff08;数据库 master&#xff0c;所有者 dbo&#xff09;的 EXECUTE 权限”的错误的解决办法。该问题是我在用指定的URL启动项目后&#xff0c;再“附加进程”后运行程序时出现的。该问题主要是.net2005的调试机制引起的&am…

ASP.NET MVC 音乐商店 - 6. 使用 DataAnnotations 进行模型验证

在前面的创建专辑与编辑专辑的表单中存在一个问题&#xff1a;我们没有进行任何验证。字段的内容可以不输入&#xff0c;或者在价格的字段中输入一些字符&#xff0c;在执行程序的时候&#xff0c;这些错误会导致数据库保存过程中出现错误&#xff0c;我们将会看到来自数据库的…

EF Code First学习笔记:数据库创建(转)

控制数据库的位置 默认情况下&#xff0c;数据库是创建在localhost\SQLEXPRESS服务器上&#xff0c;并且默认的数据库名为命名空间context类名&#xff0c;例如我们前面的BreakAway.BreakAwayContext。 有几种方法可以改变这种默认约定。 利用配置文件 在配置文件中新加一个连接…

ASP.NET 2.0+Atlas编写鼠标拖放程序

作者&#xff1a;朱先忠编译摘要 本文将详细探讨Atlas中的声明性编程与强制性编程之间的关系&#xff0c;及如何用之在一个web客户端实现拖放功能。下图为本文相应示例程序运行结果快照。运行结果一. 简介  本文旨在帮助读者理 解微软的Atlas技术的某些方面的工作原理。Atlas…

C++11异步操作

C11异步操作 C 11 提供了异步操作相关的类,主要有std::future std::promise std::package_task std::future作为异步结果的传输通道,获取线程函数的返回值; std::promise用来包装一个值,将数据和std::future绑定; std::package用来包装一个对象,将数据和future绑定起来,以方…

美国Palmbeach大学服务器整合改造案例

位于美国佛罗里达州的palmbeach大学&#xff0c;有4万9千多在校学生和2000多名教工。据学校的信息主管t parziale介绍&#xff0c;目前学校正在进行一个投资160多万美元的关于信息中心服务器、存储、网络等3部分整合改造并简化管理的项目。 该项目主要改造内容是&#xff1a;用…