欢迎加QQ学习交流群309798848
LT模式
EPOLLIN触发条件:
- 处于可读状态。
- 从不可读状态变为可读状态。
EPOLLOUT触发条件:
- 处于可写状态。
- 从不可写状态变为可写状态。
说白了,LT模式就是能读的时候就可读,能写的时候就可写。这并不是废话。
什么叫可读状态?什么叫不可读状态?
一个水杯里有水就是可读状态,水杯里没水就是不可读状态。读数据相当于喝水,喝(读)到水杯空了就不能喝(读)了。当朋友又给我到了一点水,这时候就从不可读变为可读了。(喝水等于读对方发来的数据)
什么叫可写状态?什么叫不可泄状态?
一个水杯未装满水就是可写状态,装满了水就是不可写状态。写数据相当于给客人倒水,客人水杯满了就不能再给他倒(写)了。当客人喝了一点,这时候就从不可写变了可写了。(倒水等于给对方发送数据)
就绪事件包含EPOLLIN的条件:
- 刚建立连接
- 有数据可读
- 断开连接
就绪事件包含EPOLLOUT的条件:
- 内核写缓冲区未满,可写
ET模式
LT模式比较好理解,关键是ET模式。
EPOLLIN触发条件:
- 从不可读状态变为可读状态。
- 内核接收到新发来的数据。
EPOLLOUT触发条件:
- 从不可写状态变为可写状态。
就绪事件包含EPOLLIN的条件:
- 刚建立连接
- 内核接收到新数据
- 断开连接
就绪事件包含EPOLLOUT的条件:
- 每次注册EPOLLOUT,下一次epoll_wait返回就绪事件包含EPOLLOUT(刚注册当然可写)
- 内核写缓冲区从不可写变成可写
- 同时注册了EPOLLIN和EPOLLOUT,如果接收到新数据时可写则就绪事件既包含EPOLLIN也包含EPOLLOUT
下面的demo用来验证ET模式同时注册了EPOLLIN和EPOLLOUT的结果
#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在读事件触发的同时也触发了写事件。这里就绪事件包含EPOLLOUT不是因为突然从不可写变成可写,而只是通知状态更新。具体参考:Re: [Bug] epoll_wait return EPOLLOUT even with EPOLLET flag