注明!此篇博文全部搬运于ChinaUnix网站大佬的系列博文,主要摘自:
·彻底学会epoll(3)——ET读操作实例分析
http://blog.chinaunix.net/uid-28541347-id-4288802.html
·彻底学会epoll(4)——ET写操作实例分析
http://blog.chinaunix.net/uid-28541347-id-4296180.html
·彻底学会epoll(5)——ET模式下注意事项
http://blog.chinaunix.net/uid-28541347-id-4308612.html
·彻底学会epoll(6)——关于ET若干问题总结
http://blog.chinaunix.net/uid-28541347-id-4324338.html
0. 接着上一篇的博文,为什么要用ET不用LT。
当然很多时候也用LT(编码简单所以安全)。如果想让程序更为高效,就可以使用ET模式了。
1. ET与LT的区别
所以问题来了,ET比LT高效在哪里?
从内核内核实现分析:
在内核的实现中,俩者这是在返回时有区别。
再读操作时,
LT模式下,如果读操作就绪(内核读缓冲区有数据,EPOLLIN事件)就会把内核中的rlist拷贝一次,如果一次没有处理完读缓存的数据就还会读操作就绪(依然触发EPOLLIN事件),又会把rlist拷贝一次。
ET模式下,读操作就绪只拷贝一次(触发一次EPOLLIN事件)。
所以在LT模式下在内核拷贝的次数是n(n>=1),ET模式是1。
上篇博文讲道了性能杀手包括数据拷贝,而且还发生在内核。
所以ET模式在一定条件下比LT高效。
区别:
大体上如下面的脑图。
读操作时EP和LT触发EPOLLIN事件的区别:
http://blog.chinaunix.net/uid-28541347-id-4288802.html
写操作时ET和LT触发EPOLLOUT的区别:
http://blog.chinaunix.net/uid-28541347-id-4296180.html
·对于读取操作:
(1) 当buffer由不可读状态变为可读的时候,即由空变为不空的时候。
(2) 当有新数据到达时,即buffer中的待读内容变多的时候。
另外补充一点:
(3) 当buffer中有数据可读(即buffer不空)且用户对相应fd进行epoll_mod IN事件时。
·l 对于写操作:
(1) 当buffer由不可写变为可写的时候,即由满状态变为不满状态的时候。
(2) 当有旧数据被发送走时,即buffer中待写的内容变少得时候。
另外补充一点:
(3) 当buffer中有可写空间(即buffer不满)且用户对相应fd进行epoll_mod OUT事件时。
2. 实现过程中的区别(c++, 面向过程)
结构与上一篇博文一样,只做一些改动。
也可以看大佬的博文中的代码:
http://blog.chinaunix.net/uid-28541347-id-4324338.html
#include "myepollserver.h"
int main()
{
int listenfd;
listenfd = Socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); // fei zu se IO fu yong
struct sockaddr_in serveraddr;
bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
serveraddr.sin_port = htons(8000);
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
Bind(listenfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
Listen(listenfd, 20);
// clinet init date
struct sockaddr_in clientaddr;
socklen_t clientlen;
int connfd;
//epoll
typedef std::vector<struct epoll_event> EpollList;
int epollfd;
epollfd = epoll_create1(EPOLL_CLOEXEC);
//Creates a handle to epoll, the size of which tells the kernel how many listeners there are.
/*ET模式的下的第一处改动, epfd.events = EPOLLIN | EPOLLET ; 增加EPOLLET事件*/
struct epoll_event epfd;
epfd.data.fd = listenfd;
epfd.events = EPOLLIN | EPOLLET ;
epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &epfd);
EpollList events(16);//You can listen for 16 at first
int nready;
while(1)
{
nready = epoll_wait(epollfd, &*events.begin(), static_cast<int>(events.size()), -1);
if (nready == -1)
{
if(errno == EINTR)
continue;
perror("epoll_wait");
}
if(nready == 0)
continue;
if ((size_t)nready == events.size())
{
events.resize(events.size() * 2);
}
for(int i=0; i < nready; ++i)
{
/*ET模式的下的第二处改动,把读写操作分开后,重新确定一些变量的作用域*/
char buf[100];
bzero(buf, sizeof(buf));
int n=0, nread, nwrite;
if (events[i].data.fd == listenfd)
{
clientlen = sizeof(clientaddr);
connfd = Accept4(listenfd, (struct sockaddr*)&clientaddr, &clientlen,
SOCK_NONBLOCK | SOCK_CLOEXEC);// fei zu se IO fu yong
std::cout << connfd << "is come!" << std::endl;
/*ET模式的下的第三处改动,监听与用户到来时添加EPOLLET事件,epfd.events = EPOLLIN | EPOLLET;*/
epfd.data.fd = connfd;
epfd.events = EPOLLIN | EPOLLET;
epoll_ctl(epollfd, EPOLL_CTL_ADD, connfd, &epfd);
}else if (events[i].events & EPOLLIN)
{
connfd = events[i].data.fd;
if (connfd < 0)
{
continue;
}
/*ET模式的下的第四处改动,修改读操作,一次性读完*/
while((nread = read(connfd, buf, 100)) > 0) //read to over
{
n += nread;
}
if (n > 0)
{
std::cout << "::" << connfd <<" Date: ["<< buf <<"]" << std::endl;
/*ET*/
epfd.data.fd = connfd;
epfd.events = events[i].events | EPOLLOUT;
epoll_ctl(epollfd, EPOLL_CTL_MOD, connfd, &epfd);
}else if (n == 0)
{
std::cout << connfd << "is go" << std::endl;
close(connfd);
epfd = events[i];
epoll_ctl(epollfd, EPOLL_CTL_DEL, connfd, &epfd);
}
}
/*ET模式的下的第五处改动,读写分开,一次性写完*/
if (events[i].events & EPOLLOUT)
{
while(n > 0)//write ot over
{
nwrite = write(connfd, buf, n);
n -= nwrite;
}
}
}
}
return 0;