socket中的epoll

select的限制

1.一个进程能打开的最大文件描述符是有限的

2.FD_SETSIZE(fd_set)限制,select内部使用一个数据结构fd_set,它的容量最大不能超过FD_SETSIZE。

poll的限制

一个进程能打开的最大文件描述符是有限的

上面的进程能打开的最大文件描述符的个数可通过命令ulimit -n number更改,但也不是无限大,还受到系统所能打开的最大文件描述符个数的限制,这个大小和内存有关


select和poll的另一个主要的缺点是:

内核要遍历所有我们所感兴趣的文件描述符,直到找到发生事件的文件描述符,这样的话,当并发数增长的时候,内核要遍历的文件描述符的个数也随之增大,导致效率下降。这也就是epoll引入的原因。

在linux的网络编程中,很长的时间都在使用select来做事件触发。在linux新的内核中,有了一种替换它的机制,就是epoll。相比于select,epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。因为在内核中的select实现中,它是采用轮询来处理的,轮询的fd数目越多,自然耗时越多。

epoll

epoll_create函数

int epoll_create(intsize);

作用:

该函数生成一个epoll专用的文件描述符
epoll_ctl函数

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

作用:

该函数用于控制某个epoll文件描述符上的事件,可以注册事件,修改事件,删除事件。

参数:

epfd:由 epoll_create 生成的epoll专用的文件描述符;
op:要进行的操作例如注册事件,可能的取值EPOLL_CTL_ADD 注册、EPOLL_CTL_MOD修改、EPOLL_CTL_DEL 删除
fd:关联的文件描述符,加入epoll_ctl进行管理

event:事件,对文件描述符所感兴趣的事件,指向epoll_event的指针;

返回值

如果调用成功返回0,不成功返回-1

说明

typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;

struct epoll_event {
__uint32_t events; //感兴趣的事件是可读还是可写
epoll_data_t data; /* User data variable */这是epoll高效的地方,数据类型是上面的那个共用体   
};

events可以是以下几个宏的集合:

EPOLLIN:触发该事件,表示对应的文件描述符上有可读数据。(包括对端SOCKET正常关闭);
        EPOLLOUT:触发该事件,表示对应的文件描述符上可以写数据;
        EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
        EPOLLERR:表示对应的文件描述符发生错误;
        EPOLLHUP:表示对应的文件描述符被挂断;
        EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。

        EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里。

epoll_wait函数

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

作用:

该函数用于轮询I/O事件的发生

参数:
epfd:由epoll_create 生成的epoll专用的文件描述符;
epoll_event:用于回传代处理事件的数组;
maxevents:每次能处理的事件数;

timeout:等待I/O事件发生的超时值;-1相当于阻塞,0相当于非阻塞。一般用-1即可返回发生事件数。

返回值:

如果函数调用成功,返回对应I/O上已准备好的文件描述符数目

代码

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/epoll.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

#include <vector>
#include <algorithm>

typedef std::vector<struct epoll_event> EventList;

#define ERR_EXIT(m)\
	do\
	{\
		perror(m);\
		exit(EXIT_FAILURE);\
	}while(0)

void activate_nonblock(int fd)
{
	int ret;
	int flags=fcntl(fd,F_GETFL);
	if(flags==-1)
		ERR_EXIT("fcntl");
	ret=fcntl(fd,F_SETFL,flags);
	if(ret==-1)
		ERR_EXIT("fcntl");
}
ssize_t readn(int fd,void *buf,size_t count)
  {
          size_t nleft=count;
          ssize_t nread;
          char *bufp=(char*)buf;

          while(nleft>0)
          {
                  if((nread=read(fd,bufp,nleft))<0)
                  {
                          if(errno==EINTR)
                                  continue;
                          return -1;
                  }
                  else if(nread==0)
                          return count-nleft;

                  bufp+=nread;
                  nleft-=nread;
          }
          return count;
  }
  ssize_t writen(int fd,void *buf,size_t count)
  {
            size_t nleft=count;
            ssize_t nwritten;
            char *bufp=(char*)buf;

            while(nleft>0)
            {
                    if((nwritten=write(fd,bufp,nleft))<0)
                    {
                            if(errno==EINTR)
                                    continue;
                            return -1;
                    }
                    else if(nwritten==0)
                            continue;

                    bufp+=nwritten;
                    nleft-=nwritten;
           }
           return count;
  }

ssize_t recv_peek(int sockfd,void *buf,size_t len)
{
        while(1)
        {
                int ret=recv(sockfd,buf,len,MSG_PEEK);
                if(ret==-1 && errno==EINTR)
                        continue;
                return ret;
        }
}

ssize_t readline(int sockfd,void *buf,size_t maxline)
{
        int ret;
        int nread;
        char *bufp=(char *)buf;
        int nleft=maxline;
        while(1)
        {
                ret=recv_peek(sockfd,bufp,nleft);
                if(ret<0)
                        return ret;
                else if(ret==0)
                        return ret;

                nread=ret;
                int i;
                for(i=0;i<nread;i++)
                {
                        if(bufp[i]=='\n')
                        {
                                ret=readn(sockfd,bufp,i+1);
                                if(ret!=i+1)
                                        exit(EXIT_FAILURE);
                                return ret;
                        }
                }

                if(nread>nleft)
                        exit(EXIT_FAILURE);

                nleft-=nread;
                ret=readn(sockfd,bufp,nread);
                if(ret!=nread)
                        exit(EXIT_FAILURE);

                bufp+=nread;
        }
        return -1;
}

void handle_sigchld(int sig)
{
	while(waitpid(-1,NULL,WNOHANG)>0);
}

void handle_sigpipe(int sig)
{
	printf("recv a sig=%d\n",sig);
}

int main(void)
{
        int count=0;
        //signal(SIGPIPE,SIG_IGN);
        signal(SIGPIPE,handle_sigpipe);//演示确实收到了一个信号handle_sigpipe
/*      signal(SIGCHLD,SIG_IGN);*/
        signal(SIGCHLD,handle_sigchld);
        int listenfd;
        if((listenfd=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0)
                /*if((listenfd=socket(PF_INET,SOCK_STREAM,0))<0) */
                ERR_EXIT("socket");

        struct sockaddr_in servaddr;
        memset(&servaddr,0,sizeof(servaddr));
        servaddr.sin_family=AF_INET;
        servaddr.sin_port=htons(5188);
        servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
        /*servaddr.sin_addr.s_addr=inet_addr("127.0.0.1");*/
        /*inet_aton("127.0.0.1,&servaddr.sin_addr");*/

        int on=1;
        if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0)//设置一个选项地址重复利用
                ERR_EXIT("setsockopt");
        if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)//绑定
                ERR_EXIT("bind");
        if(listen(listenfd,SOMAXCONN)<0)//监听
                ERR_EXIT("listen");
        
	//以下就是epoll独有的,上面的都是copy poll而来的
 
	//clients保存的是客户端的套接字
	std::vector<int> clients;
	int epollfd;
	epollfd=epoll_create1(EPOLL_CLOEXEC);//创建一个epoll实例

	//通过man epoll 可了解下面各个步骤

	struct epoll_event event;
	//感兴趣的一个文件描述符
	event.data.fd=listenfd;
	//感兴趣的事件是EPOLLIN,以及以边沿的方式触发
	event.events=EPOLLIN | EPOLLET;

	//将感兴趣的加入epoll进行管理(将监听套接口以及感兴趣的事件加入)
	epoll_ctlz(epollfd,EPOLL_CTL_ADD,listenfd,&event);

	EventList events(16);
	struct sockaddr_in peeraddr;
	socklen_t peerlen;
	int conn;
	int i;

	int nready;
	while(1)
	{
		//检测所返回的事件,哪些I/O产生了事件
		//返回的事件保存在events当中
		nready=epoll_wait(epollfd,&*events.begin(),static_cast<int>(events.size()),-1);
		if(nready==-1)
		{
			if(errno==EINTR)
				continue;
			ERR_EXIT("epoll_wait");
		}
		if(nready==0)
			continue;
		//容量不够了,扩容
		if((size_t)nready==events.size())
			events.resize(events.size()*2);

		for(int i=0;i<nready;i++)
		{
			if(events[i].data.fd==listenfd)
			{
				peerlen=sizeof(peeraddr);
				conn=accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen);

				if(conn==-1)
					ERR_EXIT("accept");

				printf("ip=%s port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));
				printf("count=%d\n",++count);
				//把已连接套接字保存起来
				clients.push_back(conn);
				//并且设置为非阻塞模式
				activate_nonblock(conn);

				//这个新获得的套接字下一次也要监听它的可读事件

				event.data.fd=conn;
				//感兴趣的是可读事件并且以边沿的方式触发
				event.events=EPOLLIN | EPOLLET;
				//加入epoll进行管理
				epoll_ctl(epollfd,EPOLL_CTL_ADD,conn,&event);
			}
			//下面就是已连接套接字产生的可读事件
			else if(events[i].events & EPOLLIN)
			{
				conn=events[i].data.fd;
				if(conn<0)
					continue;

				char recvbuf[1024]={0};
				int ret=readline(conn,recvbuf,1024);
				if(ret==-1)
					ERR_EXIT("readline");
				if(ret==0)
				{
					printf("client closr\n");
					close(conn);

					event=events[i];
					//从epollfd中删除
					epoll_ctl(epollfd,EPOLL_CTL_DEL,conn,&event);
					//clients保存的是已连接的套接字,所以删除掉这个断开的
					clients.erase(std::remove(clients.begin(),clients.end(),conn),clients.end());
				}
				fputs(recvbuf,stdout);
				writen(conn,recvbuf,strlen(recvbuf));
			}
		}
	}
	return 0;
}

  





























猜你喜欢

转载自blog.csdn.net/wk_bjut_edu_cn/article/details/80669310