您的位置:首页 > 运维架构 > Linux

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模式的具体实现】

#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 epoll LT ET