欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

epoll读写事件触发的条件

程序员文章站 2022-03-14 12:21:25
...

先上个人理解:

EPOLLIN(读事件)

EPOLLOUT(写事件)

以下触发条件只要满足一点即可。

LT模式:

EPOLLIN触发条件:

  1. 处于可读状态。
  2. 从不可读状态变为可读状态。

EPOLLOUT触发条件:

  1. 处于可写状态。
  2. 从不可写状态变为可写状态。

说白了,LT模式就是能读的时候就可读,能写的时候就可写。这并不是废话。

什么叫可读状态?什么叫不可读状态?

一个水杯里有水就是可读状态,水杯里没水就是不可读状态。读数据相当于喝水,喝(读)到水杯空了就不能喝(读)了。当朋友又给我到了一点水,这时候就从不可读变为可读了。(喝水等于读对方发来的数据)

什么叫可写状态?什么叫不可泄状态?

一个水杯未装满水就是可写状态,装满了水就是不可写状态。写数据相当于给客人倒水,客人水杯满了就不能再给他倒(写)了。当客人喝了一点,这时候就从不可写变了可写了。(倒水等于给对方发送数据)

ET模式:

LT模式比较好理解,关键是ET模式。

EPOLLIN触发条件:

  1. 从不可读状态变为可读状态。
  2. 内核接收到新发来的数据。

EPOLLOUT触发条件:

  1. 从不可写状态变为可写状态。
  2. 实践发现,只要同时注册了EPOLLIN和EPOLLOUT事件,当对端发数据来的时候,如果此时是可写状态,epoll会同时触发EPOLLIN和EPOLLOUT事件。(下面有demo证明)(可以说这种情况写事件触发多亏了读事件触发,如果没有同时注册读写事件,那么即使当前是可写的也不会被epoll_wait通知,因为epoll此时没有关注写事件)
  3. 接受连接后,只要注册了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;
}

输出如图:

epoll读写事件触发的条件

上面的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读写事件触发的条件

触发两次的原因我还是不明白。有大佬明白的麻烦评论区解释一下。

通过这次测试,我明白了两点:

  1. epoll ET模式写事件不是只有在不可写变为可写的时候才触发的,同时注册读写事件和重新注册读写事件都有机会触发写事件。
  2. 网上博客的理论不一定对,实践才是王道。
相关标签: epoll