1. 程式人生 > >EPOLL使用的簡單總結2——ET模式

EPOLL使用的簡單總結2——ET模式

註明!此篇博文全部搬運於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高效。

區別:
大體上如下面的腦圖。
epoll觸發模式

讀操作時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;