Linux_linux中LT和ET的区别
2016-12-02 22:17
471 查看
select、poll和epoll同为linux中系统调用函数,相比较,epoll的效率相对较高,但是也不能一概而论。epoll作为linux特有的I/O复用函数,在实现上与select、poll有很大差异,epoll通过一组函数来完成任务,而不是单个的函数,另外epoll在处理文件描述符的方式上也有所不同,有如下两种方式:LT模式(水平触发)和ET模式(边沿触发)。下面将分析一下两者的区别。
【简单理解】
LT和ET模式是epoll操作文件描述符的两种方式。LT模式是默认的工作模式,相当于效率较高的poll;ET模式是epoll的高效模式。
【LT模式的具体实现】
【ET模式的具体实现】
【两者的区别】
对于LT和ET模式的区别,课本上是这样写道的:“对于采用LT工作模式的文件描述符,当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序可以不立即处理该事件。当应用程序下次调用epoll_wait的时候,epoll_wait还会向应用程序通知此事件,直到该事件被处理。而对于采用ET工作模式的文件描述符,当epoll_wait检测到其上有事件发生并通知应用程序后,应用程序必须立即处理该事件,因为后续的epoll_wait将不在向应用程序通知此事件。可见epoll_wait很大程度上降低了同一个epoll事件被触发的次数,因此效率较高”。
但是这样的表达有些晦涩难懂。但是,我们可以从上边的代码中可以看出,当程序开始执行并接收到事件,此时会进入main函数的while(1)循环,并调用响应的lt函数或者et函数,但是我们发现,lt模式下当缓冲区中的数据没有被读完的时候,lt函数会退出并继续调用epoll_wait(),epoll_wait()发现其中还有数据,则继续进行往返调用直到数据读写完毕。但是在et模式下,epoll_wait第一次检测到有就绪事件的时候,会调用et函数;et函数内部有一个while(1)循环,直至读完数据,返回并epoll_wait,此时已经没有了数据,所以为了不让epoll_wait阻塞,应设置文件描述符为非阻塞,即调用setnonblocking()函数。
【简单理解】
LT和ET模式是epoll操作文件描述符的两种方式。LT模式是默认的工作模式,相当于效率较高的poll;ET模式是epoll的高效模式。
【LT模式的具体实现】
#include #include #include #include #include #include #include #include #include #include #include #include #include #define BUFFSIZE 10 #define MAXEVENTNUM 1024 //将fd中的读事件注册到epoll内核事件表中 void addfd(int epollfd, int fd) { epoll_event event; event.events = EPOLLIN; event.data.fd = fd; if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event) == -1) { perror("epoll_ctl_add error!\n"); } } void delfd(int epollfd, int fd) { if (epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, NULL) == -1) { perror("epoll_ctl_del error!\n"); } } //LT模式 void lt(epoll_event* events, int num, int epollfd, int listenfd) { char buff[BUFFSIZE]; int i = 0; for (;i < num;++i) { //方便读写 int sockfd = events[i].data.fd; //说明sockfd是监听套接字 if (sockfd == listenfd) { struct sockaddr_in cli; unsigned int len = sizeof(cli); int connfd = accept(listenfd, (struct sockaddr* )&cli, &len); if (connfd < 0) { continue; } addfd(epollfd, connfd); } else if(events[i].events & EPOLLIN) { //只要socket读缓存中还有未读出的数据,这段代码就被触发 printf("event trigger once\n"); memset(buff, 0, BUFFSIZE); /* * recv(int sockfd, void *buff, size_t len, int flags) * 返回值:返回实际读取到的数据的长度,可能小于实际的长度len; * 返回0,意味着对方关闭链接;返回-1,出错。 */ int ret = recv(sockfd, buff, BUFFSIZE, 0); if (ret <= 0) { delfd(epollfd, sockfd); close(sockfd); continue; } printf("get %d bytes of content: %s\n",ret,buff); send(listenfd, "OK", 3, 0); } else { printf("something else happened!\n"); } } } int main() { struct sockaddr_in address; address.sin_family = AF_INET; address.sin_port = htons(8000); address.sin_addr.s_addr = inet_htons("127.0.0.1"); //创建socket int listenfd = socket(PF_INET, SOCK_STREAM, 0); assert(listenfd >= 0); //命名socket ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address)); assert(ret != -1); //监听socket ret = listen(listenfd, 5); assert(ret != -1); //创建内核事件表 epoll_event events[MAXEVENTNUM]; int epollfd = epoll_create(5); assert(epollfd != -1); //需要多次添加,所以进行封装 //向内核事件表中添加描述符和事件 addfd(epollfd, listenfd); while (1) { //-1代表永久阻塞,直到有一个事件就绪 int ret = epoll_wait(epollfd, events, MAXEVENTNUM, -1); if (ret < 0) { perror("epoll failure!\n"); break; } lt(events, ret, epollfd, listenfd); } close(listenfd); return 0; }
【ET模式的具体实现】
#include #include #include #include #include #include #include #include #include #include #include #include #include #define BUFFSIZE 10 #define MAXEVENTNUM 1024 //将文件描述符设置成非阻塞 int setnonblocking(int fd) { int old_option = fcntl(fd, F_GETFL); int new_option = old_option | O_NONBLOCK; fcntl(fd, F_SETFL, new_option); return old_option; } //将fd中的读事件注册到epoll内核事件表中 void addfd(int epollfd, int fd) { epoll_event event; event.events = EPOLLIN; event.data.fd = fd; if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event) == -1) { perror("epoll_ctl_add error!\n"); } setnonblocking(fd); } //删除内核事件表中的事件 void delfd(int epollfd, int fd) { if (epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, NULL) == -1) { perror("epoll_ctl_del error!\n"); } } //ET模式 void et(epoll_event* events, int num, int epollfd, int listenfd) { char buff[BUFFSIZE]; int i = 0; for (;i < num;++i) { int sockfd = events[i].data.fd; if (sockfd == listenfd) { struct sockaddr_in cli; unsigned int len = sizeof(cli); int connfd = accept(listenfd, (struct sockaddr*)&cli, &len); addfd(epollfd, connfd, 1); } else if(events[i].events & EPOLLIN) { printf("event trigger once!\n"); //=========================================================================// //epoll_wait()在et模式下只想应用程序通知该事件一次(即使数据没有读完),即调 // //用一次epoll_wait(),所以要想读完缓冲区中的数据,则需要用内层循环来读取 // //=========================================================================// //lt模式是一直通过epoll_wait()的调用来提醒应用程序来处理事件(也就是缓冲区中// //没有读完的数据 // //=========================================================================// while (1) { memset(buff, 0, BUFFSIZE); //没有数据,并且描述符非阻塞,返回值ret == -1 int ret = recv(sockfd, buff, BUFFSIZE, 0); if (ret < 0) { /* * 对于非阻塞I/O,下面条件成立,数据已经全部读取完毕。 * 此后epoll能再次触发sockfd上的EPOLLIN事件,以驱动下 * 一次读操作 */ if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) { printf("read later\n"); break; } send(sockfd, "OK", 3, 0); break; } else if (ret == 0) { delfd(epollfd, sockfd); close(sockfd); break; } else { printf("get %d bytes of content: %s\n",ret,buff); } } } else { printf("something else happened!\n"); } } } int main() { struct sockaddr_in address; address.sin_family = AF_INET; address.sin_port = htons(8000); address.sin_addr.s_addr = inet_htons("127.0.0.1"); //创建socket int listenfd = socket(PF_INET, SOCK_STREAM, 0); assert(listenfd >= 0); //命名socket ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address)); assert(ret != -1); //监听socket ret = listen(listenfd, 5); assert(ret != -1); //创建内核事件表 epoll_event events[MAXEVENTNUM]; int epollfd = epoll_create(5); assert(epollfd != -1); //需要多次添加,所以进行封装 //向内核事件表中添加描述符和事件 addfd(epollfd, listenfd); while (1) { //-1代表永久阻塞,直到有一个事件就绪 int ret = epoll_wait(epollfd, events, MAXEVENTNUM, -1); if (ret < 0) { perror("epoll failure!\n"); break; } lt(events, ret, epollfd, listenfd); } close(listenfd); return 0; }
【两者的区别】
对于LT和ET模式的区别,课本上是这样写道的:“对于采用LT工作模式的文件描述符,当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序可以不立即处理该事件。当应用程序下次调用epoll_wait的时候,epoll_wait还会向应用程序通知此事件,直到该事件被处理。而对于采用ET工作模式的文件描述符,当epoll_wait检测到其上有事件发生并通知应用程序后,应用程序必须立即处理该事件,因为后续的epoll_wait将不在向应用程序通知此事件。可见epoll_wait很大程度上降低了同一个epoll事件被触发的次数,因此效率较高”。
但是这样的表达有些晦涩难懂。但是,我们可以从上边的代码中可以看出,当程序开始执行并接收到事件,此时会进入main函数的while(1)循环,并调用响应的lt函数或者et函数,但是我们发现,lt模式下当缓冲区中的数据没有被读完的时候,lt函数会退出并继续调用epoll_wait(),epoll_wait()发现其中还有数据,则继续进行往返调用直到数据读写完毕。但是在et模式下,epoll_wait第一次检测到有就绪事件的时候,会调用et函数;et函数内部有一个while(1)循环,直至读完数据,返回并epoll_wait,此时已经没有了数据,所以为了不让epoll_wait阻塞,应设置文件描述符为非阻塞,即调用setnonblocking()函数。
相关文章推荐
- linux 中IO多路复用epoll函数的ET和LT工作模式详解
- linux epoll ET和LT触发深入分析
- linux 中IO多路复用epoll函数的ET和LT工作模式详解
- (转) epoll的LT和ET模式的区别
- I/O多路复用之select、epoll的实现和区别 ,ET与LT模式
- epoll中ET和LT模式的区别
- ET(边缘触发)LT(水平触发)区别
- epoll的LT和ET的区别
- epoll两种类型ET和LT区别(结合实际例子)
- 【服务器编程】EPOLL的LT和ET模式的区别和理解
- linux >和>>的区别,<号使用
- <iostream> 和 <iostream.h>的区别 及 Linux下编译iostream.h的方法
- Linux ---#include <time.h>和 #include <sys/time.h>的区别
- 【服务器编程】EPOLL的LT和ET模式的区别和理解
- 简述epoll下电平触发LT和边沿触发ET的区别
- epoll 的 Et与lt模式的区别
- 唯快不破:linux 中IO多路复用epoll函数的ET和LT工作模式详解
- linux 中IO多路复用epoll函数的ET和LT工作模式详解
- linux >和>>的区别,<号使用
- epoll下电平触发LT和边沿触发ET的区别