使用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;
}







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