使用epoll模型

epoll模型可以說是select模型和poll模型的升級版,但epoll要求在linux內核版本2.6以上。

相對於,selectpoll來說,epoll更加靈活,沒有描述符限制。相比於selectpollepoll不會因爲監聽的描述符數目變多而導致輪詢過多(耗時太多),不會因爲fd的數目增大而降低響應效率。

 

另外,對於select的最大描述符,linux會有限制,linux/posix_types.h頭文件有這樣的聲明:
           #define __FD_SETSIZE    1024


表示select最多同時監聽1024fd,當然,可以通過修改頭文件再重編譯內核來擴大這個數目,但這似乎並不治本。

 

epoll使用一個描述符去管理多個套接字(多個描述符),其實這個管理其他套接字的描述符就相當於一個handle,將用戶關係的文件描述符的事件存放到內核的一個事件表中,這樣在用戶空間和內核結構之間的數據拷貝只需一次。

 

使用epoll模型需要包含頭文件

#include <sys/epoll.h>

 

epoll提供了3個函數

1)  int epoll_create(int size);

epoll_create創建了一個epoll句柄(handle,或稱爲實例),這個實例本質上也是一個文件描述符。參數size是通知內核要監聽的描述符數量,但自從linux 內核2.6.8後,這個參數是忽略的,只有設置爲大於0就可以。

 

參數size的監聽數量與select的第一個參數有所不同,select的第一個參數要求是監聽的描述符中最大值+1當創建好epoll句柄後,它就是會佔用一個fd值,在linux下如果查看/proc/進程id/fd/,是能夠看到這個fd的,所以在使用完epoll後,必須調用close()關閉,否則可能導致fd被耗盡(其實就是必須銷燬句柄的意思)

 

epoll_create返回-1時,表示epoll_create失敗,同時會去設置errno,返回非負值,也就是文件描述符,表示epoll_create成功。

 

 

2)   int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

epoll的事件註冊函數epoll_ctl的主要功能是添加描述符及爲相關描述符的註冊事件。

它不同select()是在監聽事件時告訴內核要監聽什麼類型的事件,而是在這裏先註冊要監聽的事件類型。

   第一個參數 epfd就是epoll_create創建出的epoll句柄

   第二個參數op屬於操作宏,有三個類型:

a.   EPOLL_CTL_ADD

註冊新的fd(第三個參數)epfd中;

 

 

  b.  EPOLL_CTL_MOD

改變fd的已經註冊過的事件。(給fd設置一個新的事件)     

  c.  EPOLL_CTL_DEL

  epoll句柄管理的描述符集中刪除fd

 

第三個參數fd就是操作的文件描述符。

 

第四個參數eventstruct epoll_event *結構體指針。

 

      typedef union epoll_data {

       void    *ptr;

       int     fd;

       uint32_t     u32;

       uint64_t     u64;

   } epoll_data_t;

 

   struct epoll_event {

       uint32_t     events;  /* Epoll events */

       epoll_data_t data;  /* User data variable */

   };

   struct   epoll_event 結構體成員events可以是以下幾個宏的集合:
EPOLLIN :表示對應的文件描述符可以讀(包括對端SOCKET正常關閉);
EPOLLOUT:表示對應的文件描述符可以寫;
EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這裏應該表示有帶外數據到來);
EPOLLERR:表示對應的文件描述符發生錯誤;
EPOLLHUP:表示對應的文件描述符被掛斷;
EPOLLET: 將EPOLL設爲邊緣觸發(Edge Triggered)模式,這是相對於水平觸發(Level Triggered)來說的。
EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之後,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL隊列裏

 

epoll_ctl返回值:

成功返回值爲0,失敗返回值爲-1.

 

 

3int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

 

epoll_wait靈活等待事件的到來,類似於select()調用。參數events用來從內核得到事件的集合(傳入還是傳出的)maxevents告知內核這個events有多大,這個 maxevents的值不能大於創建epoll_create()時的size,參數timeout是超時時間(單位毫秒,0會立即返回,-1永久阻塞)。該函數返回需要處理的事件數目,如返回0表示已超時。


然後詳細解釋ET, LT:

LT(level triggered)是缺省的工作方式,並且同時支持blockno-block socket.在這種做法中,內核告訴你一個文件描述符是否就緒了,然後你可以對這個就緒的fd進行IO操作。如果你不作任何操作,內核還是會繼續通知你的,所以,這種模式編程出錯誤可能性要小一點。傳統的select/poll都是這種模型的代表.

ET(edge-triggered)是高速工作方式,只支持no-block socket。在這種模式下,當描述符從未就緒變爲就緒時,內核通過epoll告訴你。然後它會假設你知道文件描述符已經就緒,並且不會再爲那個文件描述符發送更多的就緒通知,直到你做了某些操作導致那個文件描述符不再爲就緒狀態了(比如,你在發送,接收或者接收請求,或者發送接收的數據少於一定量時導致了一個EWOULDBLOCK 錯誤)。但是請注意,如果一直不對這個fdIO操作(從而導致它再次變成未就緒),內核不會發送更多的通知(only once),不過在TCP協議中,ET模式的加速效用仍需要更多的benchmark確認(這句話不理解)


使用epoll

服務器端代碼:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>		  /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>  
#include <arpa/inet.h>  
#include <netdb.h>  
#include <errno.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/epoll.h>


extern int errno;

#define  MaxConnectNum (5)

#define  EPOLL_SIZE (1000)
#define  EPOLL_EVENT_SIZE (100)

int main()
{
	int domain = AF_INET;
	int type = SOCK_STREAM;
	int protocol = 0;
	int ret  = -1;
	int nListenFd = -1;
	int nNewClientFd = -1;
	short int  port = 2000; 
	struct sockaddr_in addr_in;
	int backlog = 128; // 默認是128
	int len = 0;
	char chBuffer[1024] = {0};
	int flags = 0;
	int nMaxFd = -1;
	int i = 0;
	static int s_nCountClient = 0;
	int epoll_handle_fd = -1;
	struct epoll_event stuEventTmp;
	struct epoll_event stuEventArray[EPOLL_EVENT_SIZE];
	
	nListenFd = socket( domain,  type,  protocol);
	if(nListenFd < 0)
	{
		printf("\n socket failed ! errno[%d]  err[%s]\n", errno, strerror(errno));
		return -1;
	}

	memset(&addr_in, 0, sizeof(struct sockaddr_in));
	addr_in.sin_family = AF_INET;
	addr_in.sin_port = htons(port);//htons的返回值是16位的網絡字節序整型數   htons尾的字母s代表short
	addr_in.sin_addr.s_addr = htonl(INADDR_ANY);

	ret = bind(nListenFd, ( struct sockaddr * )(&addr_in), sizeof(struct sockaddr_in));
    if(ret < 0)
    {
    	printf("\n bind failed ! errno[%d]  err[%s]\n", errno, strerror(errno));
    	close(nListenFd); //避免資源泄漏
		return -1;
	}

    ret = listen(nListenFd, backlog);
    if(ret < 0)
    {
		printf("\n listen failed ! errno[%d]	err[%s]\n", errno, strerror(errno));
		close(nListenFd); //避免資源泄漏
		return -1;
	}

	epoll_handle_fd = epoll_create(EPOLL_SIZE);
	
	if(-1 == epoll_handle_fd)
	{
		printf("\n epoll_create  failed ! errno[%d]  err[%s]\n", errno, strerror(errno));
    	close(nListenFd); //避免資源泄漏
		return -1;
	}

	//添加listenfd

	//  int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

	memset(&stuEventTmp, 0, sizeof(struct epoll_event));

	stuEventTmp.data.fd = nListenFd;
	stuEventTmp.events = EPOLLIN ;
	ret = epoll_ctl(epoll_handle_fd, EPOLL_CTL_ADD, nListenFd,&stuEventTmp );
	if(-1 == ret)
	{
		printf("\n epoll_ctl  failed ! errno[%d]  err[%s]  __LINE__[%d]\n", errno, strerror(errno),__LINE__);
    	close(nListenFd); //避免資源泄漏
		return -1;
	}
	
	while(1)
	{
		int num = 0;
		int timeout = 5000;
		num = epoll_wait(epoll_handle_fd, stuEventArray, EPOLL_EVENT_SIZE,timeout);//int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
		if(num < 0)
		{
			break;
		}
		else if(num == 0)
		{
			printf("\n epoll time out\n");
			continue;
		}
		else
		{
			for(i=0; i < num; i++)
			{
				if(stuEventArray[i].data.fd == nListenFd)
				{
					nNewClientFd = accept(nListenFd, ( struct sockaddr *)NULL, NULL); //阻塞模式  
					if(nNewClientFd < 0)  
					{  
					    printf("\n accept failed ! errno[%d]    err[%s]\n", errno, strerror(errno));  
					    //close(nListenFd); //避免資源泄漏  
					    break;  
					}  

					//添加
					memset(&stuEventTmp, 0, sizeof(struct epoll_event));
					stuEventTmp.data.fd = nNewClientFd;
					stuEventTmp.events = EPOLLIN ;
					ret = epoll_ctl(epoll_handle_fd, EPOLL_CTL_ADD, nNewClientFd,&stuEventTmp );
					if(-1 == ret)
					{
						printf("\n epoll_ctl  failed ! errno[%d]  err[%s]  __LINE__[%d]\n", errno, strerror(errno),__LINE__);
				    	break;
					}
					
				}
				else if(  stuEventArray[i].data.fd != -1)
				{
					if((stuEventArray[i].events & EPOLLIN))
					{

						//READ  DATA
						len = recv(stuEventArray[i].data.fd , chBuffer, sizeof(chBuffer) , flags);//flags爲0,阻塞模式  
	                    if(len <= 0)  
	                    {  
	                        printf("\n recv failed ! errno[%d]  err[%s] len[%d]\n", errno, strerror(errno),len);  
	                     
	                     
							ret = epoll_ctl(epoll_handle_fd, EPOLL_CTL_DEL, stuEventArray[i].data.fd,&stuEventArray[i] );
							if(-1 == ret)
							{
								printf("\n epoll_ctl  failed ! errno[%d]  err[%s]  __LINE__[%d]\n", errno, strerror(errno),__LINE__);
						    	break;
							}
							close(stuEventArray[i].data.fd); 
		                    continue;
		                }  

						printf("\n recv [%s] len[%d]\n", chBuffer,len);  
						stuEventArray[i].events = EPOLLOUT ;
						ret = epoll_ctl(epoll_handle_fd, EPOLL_CTL_MOD, stuEventArray[i].data.fd,&stuEventArray[i] );
						if(-1 == ret)
						{
							printf("\n epoll_ctl  failed ! errno[%d]  err[%s]  __LINE__[%d]\n", errno, strerror(errno),__LINE__);
					    	break;
						}
						continue;
					}
					#if 1
					if(stuEventArray[i].events & EPOLLOUT )
					{
						//SEND DATA 

						len = send(stuEventArray[i].data.fd , chBuffer, sizeof(chBuffer) , flags);//flags爲0,阻塞模式  
	                    if(len <= 0)  
	                    {  
	                        printf("\n send failed ! errno[%d]  err[%s] len[%d]\n", errno, strerror(errno),len);  
	                        close(stuEventArray[i].data.fd);  
	                     
							ret = epoll_ctl(epoll_handle_fd, EPOLL_CTL_DEL, stuEventArray[i].data.fd,&stuEventArray[i] );
							if(-1 == ret)
							{
								printf("\n epoll_ctl  failed ! errno[%d]  err[%s]  __LINE__[%d]\n", errno, strerror(errno),__LINE__);
						    	break;
							}
		                    continue;
		                }  

						printf("\n send [%s] len[%d]\n", chBuffer,len); 
						stuEventArray[i].events = EPOLLIN ;
						ret = epoll_ctl(epoll_handle_fd, EPOLL_CTL_MOD, stuEventArray[i].data.fd,&stuEventArray[i] );
						if(-1 == ret)
						{
							printf("\n epoll_ctl  failed ! errno[%d]  err[%s]  __LINE__[%d]\n", errno, strerror(errno),__LINE__);
					    	break;
						}
						continue;
					}
					#endif
				}
				
			}

			if(i != num )
			{
				printf("\n 	if(i != num ) \n");
				break;
			}
		}
	}
	
	return 0;
}






客戶端代碼:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>		  /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>  
#include <arpa/inet.h>  
#include <netdb.h>  
#include <errno.h>
#include <unistd.h>

extern int errno;

int main()
{
	int domain = AF_INET;//AF_INET
	int type = SOCK_STREAM;
	int protocol = 0;
	int ret  = -1;
	int nClientFd = -1;
	short int  port = 2000; 
	struct sockaddr_in addr_in;
	int len = 0;
	char chBuffer[1024] = {0};
	int flags = 0;
	char * pchServerIP = "192.168.1.211";
	
	nClientFd = socket( domain,  type,  protocol);
	if(nClientFd < 0)
	{
		printf("\n socket failed ! errno[%d]  err[%s]\n", errno, strerror(errno));
		return -1;
	}

    memset(&addr_in, 0, sizeof(struct sockaddr_in));
	addr_in.sin_family = AF_INET;
	addr_in.sin_port = htons(port);//htons的返回值是16位的網絡字節序整型數   htons尾的字母s代表short
	//addr_in.sin_addr.s_addr = htonl(inet_addr(pchServerIP)); //錯誤的做法
	addr_in.sin_addr.s_addr = inet_addr(pchServerIP); 
	ret = connect(nClientFd, ( struct sockaddr * )(&addr_in), sizeof(struct sockaddr_in));
    if(ret < 0)
    {
    	printf("\n connect failed ! errno[%d]  err[%s]\n", errno, strerror(errno));
    	close(nClientFd); //避免資源泄漏
		return -1;
	}

	printf("\n  connect success ! \n");
	for(;;)
	{
		len = send(nClientFd, "2", sizeof("2"), flags); 
		sleep(2);
	}
	
	close(nClientFd);

	return 0;
}







發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章