epoll读写事件触发的条件
先上个人理解:
EPOLLIN(读事件)
EPOLLOUT(写事件)
以下触发条件只要满足一点即可。
LT模式:
EPOLLIN触发条件:
- 处于可读状态。
- 从不可读状态变为可读状态。
EPOLLOUT触发条件:
- 处于可写状态。
- 从不可写状态变为可写状态。
说白了,LT模式就是能读的时候就可读,能写的时候就可写。这并不是废话。
什么叫可读状态?什么叫不可读状态?
一个水杯里有水就是可读状态,水杯里没水就是不可读状态。读数据相当于喝水,喝(读)到水杯空了就不能喝(读)了。当朋友又给我到了一点水,这时候就从不可读变为可读了。(喝水等于读对方发来的数据)
什么叫可写状态?什么叫不可泄状态?
一个水杯未装满水就是可写状态,装满了水就是不可写状态。写数据相当于给客人倒水,客人水杯满了就不能再给他倒(写)了。当客人喝了一点,这时候就从不可写变了可写了。(倒水等于给对方发送数据)
ET模式:
LT模式比较好理解,关键是ET模式。
EPOLLIN触发条件:
- 从不可读状态变为可读状态。
- 内核接收到新发来的数据。
EPOLLOUT触发条件:
- 从不可写状态变为可写状态。
- 实践发现,只要同时注册了EPOLLIN和EPOLLOUT事件,当对端发数据来的时候,如果此时是可写状态,epoll会同时触发EPOLLIN和EPOLLOUT事件。(下面有demo证明)(可以说这种情况写事件触发多亏了读事件触发,如果没有同时注册读写事件,那么即使当前是可写的也不会被epoll_wait通知,因为epoll此时没有关注写事件)
- 接受连接后,只要注册了EPOLLOUT事件,那么就会马上触发EPOLLOUT事件。
对于ET模式,刚开始注册了EPOLLOUT事件就会马上触发可写。然后是很重要的一点,因为网上博客很少说到,就是上面的第二点:由于同时注册了EPOLLIN和EPOLLOUT事件,那么在对端发数据来的时候会顺带触发可写事件(前提是当前写缓冲区可写)
下面的demo用来证明这点:
#include <sys/socket.h>
#include <sys/epoll.h>
#include <iostream>
#include <vector>
#include <stdlib.h>
#include <netinet/in.h> //sockaddr_in
#include <arpa/inet.h> //inet_addr
#include <unistd.h> //read
#include <errno.h> //errno
#include <fcntl.h> //fcntl
#include <memory.h> //memset
using namespace std;
static const short s_listenPort = 6666;
#define enableEpollEtMode 1
#define exitif(s, errStr) do { \
if(s) \
{ \
if(listenFd != -1) \
{ \
close(listenFd); \
listenFd = -1; \
} \
if(epollfd != -1) \
{ \
close(epollfd); \
epollfd = -1; \
} \
cout << errStr << endl; \
exit(0); \
} \
} while(0);
void errorHanding(const string& errStr)
{
cout << errStr << endl;
exit(0);
}
int listenFd = -1;
int epollfd = -1;
int main()
{
listenFd = ::socket(AF_INET, SOCK_STREAM, 0);
exitif(listenFd < 0, "socket");
//复用ip和端口
int on = 1;
setsockopt(listenFd, SOL_SOCKET, SO_REUSEADDR, (char*)& on, sizeof on);
setsockopt(listenFd, SOL_SOCKET, SO_REUSEPORT, (char*)& on, sizeof on);
//将fd设为非阻塞
int oldFlag = fcntl(listenFd, F_GETFL, 0);
int r = fcntl(listenFd, F_SETFL, oldFlag | O_NONBLOCK);
exitif(r, "fcntl");
sockaddr_in localAddr;
socklen_t addrLen = sizeof(localAddr);
localAddr.sin_family = AF_INET;
//localAddr.sin_addr.s_addr = inet_addr();
string ip = "0.0.0.0";
inet_pton(AF_INET, ip.c_str(), &localAddr.sin_addr);
localAddr.sin_port = htons(s_listenPort);
r = bind(listenFd, (struct sockaddr*) & localAddr, addrLen);
exitif(r, "bind");
r = listen(listenFd, SOMAXCONN);
exitif(r, "listen");
epollfd = epoll_create1(EPOLL_CLOEXEC);
exitif(epollfd < 0, "epoll_create1");
epoll_event listenFdInterestEvent;
listenFdInterestEvent.events = EPOLLIN;
#ifdef enableEpollEtMode
listenFdInterestEvent.events |= EPOLLET;
#endif //enableEpollEtMode
listenFdInterestEvent.data.fd = listenFd;
r = epoll_ctl(epollfd, EPOLL_CTL_ADD, listenFd, &listenFdInterestEvent);
exitif(r, "epoll_ctl");
vector<epoll_event> activeEventList(16);
int timeout = 1;
int nActive = -1;
int acceptFd = -1;
char readBuf[1024] = { 0 };
char sendBuf[1024] = { 0 };
while (1)
{
nActive = epoll_wait(epollfd, &activeEventList[0], activeEventList.size(), timeout);
if (nActive < 0)
{
if (errno == EINTR)
continue;
exitif(1, "epoll_wait");
}
else if (nActive == 0)
{
continue;
}
else
{
for (size_t i = 0; i < nActive; ++i)
{
if (activeEventList[i].data.fd == listenFd)
{
//SOCK_NONBLOCK使新fd为非阻塞
acceptFd = accept4(listenFd, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC);
exitif(acceptFd < 0, "accept4");
epoll_event connFdEvent;
//同时监听读写事件。此处注册了写事件,待会马上就会触发写事件
connFdEvent.events = EPOLLIN | EPOLLOUT;
#ifdef enableEpollEtMode
connFdEvent.events |= EPOLLET;
#endif //enableEpollEtMode
connFdEvent.data.fd = acceptFd;
r = epoll_ctl(epollfd, EPOLL_CTL_ADD, acceptFd, &connFdEvent);
exitif(r, "epoll_ctl");
cout << "new connection" << endl;
}
else
{
if (activeEventList[i].events & EPOLLIN)
{
int connFd = activeEventList[i].data.fd;
memset(readBuf, 0, 1024);
//read one byte one time
r = read(connFd, readBuf, 1024);
if (r < 0)
{
if (errno != EAGAIN && errno != EINTR)
{
r = epoll_ctl(epollfd, EPOLL_CTL_DEL, connFd, NULL);
exitif(r, "epoll_ctl");
close(connFd);
errorHanding("read");
}
}
if (r == 0)
{
cout << "peer closed" << endl;
r = epoll_ctl(epollfd, EPOLL_CTL_DEL, connFd, NULL);
exitif(r, "epoll_ctl");
close(connFd);
}
else
{
cout << "EPOLLIN triggered,read data: " << readBuf << endl;
}
}
if (activeEventList[i].events & EPOLLOUT)
{
cout << "EPOLLOUT triggered" << endl;
}
//if
//{
// cout << "other event" << endl;
//}
}
}
}
}
close(listenFd);
close(epollfd);
return 0;
}
输出如图:
上面的demo在读事件触发的同时也触发了写事件。证明了上述的epoll ET模式的EPOLLOUT事件的触发条件2。
最后再来看demo2,demo2对demo做的修改是:在读事件触发的时候,重新注册读和写事件(EPOLL_CTL_MOD)。每次
读事件触发后又触发了两次写事件。
#include <sys/socket.h>
#include <sys/epoll.h>
#include <iostream>
#include <vector>
#include <stdlib.h>
#include <netinet/in.h> //sockaddr_in
#include <arpa/inet.h> //inet_addr
#include <unistd.h> //read
#include <errno.h> //errno
#include <fcntl.h> //fcntl
#include <memory.h> //memset
using namespace std;
static const short s_listenPort = 6666;
#define enableEpollEtMode 1
#define exitif(s, errStr) do { \
if(s) \
{ \
if(listenFd != -1) \
{ \
close(listenFd); \
listenFd = -1; \
} \
if(epollfd != -1) \
{ \
close(epollfd); \
epollfd = -1; \
} \
cout << errStr << endl; \
exit(0); \
} \
} while(0);
void errorHanding(const string& errStr)
{
cout << errStr << endl;
exit(0);
}
int listenFd = -1;
int epollfd = -1;
int main()
{
listenFd = ::socket(AF_INET, SOCK_STREAM, 0);
exitif(listenFd < 0, "socket");
//复用ip和端口
int on = 1;
setsockopt(listenFd, SOL_SOCKET, SO_REUSEADDR, (char*)& on, sizeof on);
setsockopt(listenFd, SOL_SOCKET, SO_REUSEPORT, (char*)& on, sizeof on);
//将fd设为非阻塞
int oldFlag = fcntl(listenFd, F_GETFL, 0);
int r = fcntl(listenFd, F_SETFL, oldFlag | O_NONBLOCK);
exitif(r, "fcntl");
sockaddr_in localAddr;
socklen_t addrLen = sizeof(localAddr);
localAddr.sin_family = AF_INET;
//localAddr.sin_addr.s_addr = inet_addr();
string ip = "0.0.0.0";
inet_pton(AF_INET, ip.c_str(), &localAddr.sin_addr);
localAddr.sin_port = htons(s_listenPort);
r = bind(listenFd, (struct sockaddr*) & localAddr, addrLen);
exitif(r, "bind");
r = listen(listenFd, SOMAXCONN);
exitif(r, "listen");
epollfd = epoll_create1(EPOLL_CLOEXEC);
exitif(epollfd < 0, "epoll_create1");
epoll_event listenFdInterestEvent;
listenFdInterestEvent.events = EPOLLIN;
#ifdef enableEpollEtMode
listenFdInterestEvent.events |= EPOLLET;
#endif //enableEpollEtMode
listenFdInterestEvent.data.fd = listenFd;
r = epoll_ctl(epollfd, EPOLL_CTL_ADD, listenFd, &listenFdInterestEvent);
exitif(r, "epoll_ctl");
vector<epoll_event> activeEventList(16);
int timeout = 1;
int nActive = -1;
int acceptFd = -1;
char readBuf[1024] = { 0 };
char sendBuf[1024] = { 0 };
while (1)
{
nActive = epoll_wait(epollfd, &activeEventList[0], activeEventList.size(), timeout);
if (nActive < 0)
{
if (errno == EINTR)
continue;
exitif(1, "epoll_wait");
}
else if (nActive == 0)
{
continue;
}
else
{
for (size_t i = 0; i < nActive; ++i)
{
if (activeEventList[i].data.fd == listenFd)
{
//SOCK_NONBLOCK使新fd为非阻塞
acceptFd = accept4(listenFd, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC);
exitif(acceptFd < 0, "accept4");
epoll_event connFdEvent;
//同时监听读写事件
connFdEvent.events = EPOLLIN | EPOLLOUT;
//connFdEvent.events = EPOLLOUT;
#ifdef enableEpollEtMode
connFdEvent.events |= EPOLLET;
#endif //enableEpollEtMode
connFdEvent.data.fd = acceptFd;
r = epoll_ctl(epollfd, EPOLL_CTL_ADD, acceptFd, &connFdEvent);
exitif(r, "epoll_ctl");
cout << "new connection" << endl;
}
else
{
if (activeEventList[i].events & EPOLLIN)
{
int connFd = activeEventList[i].data.fd;
memset(readBuf, 0, 1024);
//read one byte one time
r = read(connFd, readBuf, 1024);
if (r < 0)
{
if (errno != EAGAIN && errno != EINTR)
{
r = epoll_ctl(epollfd, EPOLL_CTL_DEL, connFd, NULL);
exitif(r, "epoll_ctl");
close(connFd);
errorHanding("read");
}
}
if (r == 0)
{
cout << "peer closed" << endl;
r = epoll_ctl(epollfd, EPOLL_CTL_DEL, connFd, NULL);
exitif(r, "epoll_ctl");
close(connFd);
}
else
{
cout << "read data: " << readBuf << endl;
epoll_event connFdEvent;
//重新注册读写事件
connFdEvent.events = EPOLLIN | EPOLLOUT;
#ifdef enableEpollEtMode
connFdEvent.events |= EPOLLET;
#endif //enableEpollEtMode
connFdEvent.data.fd = connFd;
r = epoll_ctl(epollfd, EPOLL_CTL_MOD, connFd, &connFdEvent);
exitif(r, "epoll_ctl");
}
}
if (activeEventList[i].events & EPOLLOUT)
{
cout << "EPOLLOUT triggered" << endl;
}
//else
//{
// cout << "other event" << endl;
//}
}
}
}
}
close(listenFd);
close(epollfd);
return 0;
}
触发两次的原因我还是不明白。有大佬明白的麻烦评论区解释一下。
通过这次测试,我明白了两点:
- epoll ET模式写事件不是只有在不可写变为可写的时候才触发的,同时注册读写事件和重新注册读写事件都有机会触发写事件。
- 网上博客的理论不一定对,实践才是王道。
上一篇: 绝不找你的麻烦
下一篇: php打造智能化的柱状图程序,用于报表等