并发服务器epoll

Select两个限制:
1,一个进程能够打开的文件描述符个数室友限制的
2, FD_SETSIZE限制
Poll限制:
1,一个进程能够打开的最大文件描述符是有限的
通过ulimit –n numbe可以更改这个上限,但是系统能够打开的最大文件描述的个数是有限制的(内存有关),通过cat /proc/sys/fs/file-max可以查看最大限制。
Select和poll共同点:
内核要遍历所有文件描述符,直达找到发生事件的文件描述符。
当并发数很高时,因为遍历要花费大量时间,效率并不会很高,因此需要使用epoll来改进。

int epoll_create(int size)需要指定最大的并发数,内部用哈希表实现,size其实是哈希表容量
Int epoll_create1(int flags)内部用红黑树实现,容量更大
Int epoll_ctl(int epollfd, int op, int fds, epoLL_event);//控制某个opoll监控的文件描述符上的事件,注册,修改,删除,其值是如下的宏:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,struct 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 */
};

events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。
epoll好处
1. 相比于select与poll,epoll最大的好处就在于它不会随着监听数fd数目的增长而降低效率。
2. 内核中select与poll的实现是采用轮询来处理的,轮询的fd数目越多,自然耗时越多。
3. Epoll的实现是基于回调的,如果fd有期望的事件就通过毁掉函数将其加入到eploo就绪队列中,也就是说它只关心活跃的fd,与fd数目无关。
4. 内核用户空间内存拷贝问题,select/poll采用了内存拷贝方式,而epoll采用的是共享内存方式。
5. Epoll不仅会告诉应用程序有IO事件到来,还会告诉应用程序相关的信息,这些信息是应用程序填充的,因此根据这些信息应用程序就能直接定位到事件,而不必遍历整个fd集合。
Epoll的两种模式:
1. 水平触发,默认模式。

2. EPOLLLT边沿触发,这种模式靠内核中的epoll驱动,应用程序只需要处理从epoll_wait返回的fds,这些fds我们认为它们处于就绪状态。在此模式下,系统仅仅通知了那些fds变成了就绪状态,一旦fd变成就绪状态,epoll将不再关注这个fd的任何信息(从epoll队列移除),直到应用程序通过读写操作触发EAGAIN状态,epoll认为这个fd又变为空闲状态,那么epoll又重新关注这个fd的状态变化(重新加入epoll队列)。随着epoll_wait的返回,队列中的fds是在减少的,所以在大并发系统中,EPOLLET更有优势,但是对程序员的要求也更高。在此模式下,如果一次未处理完数据,就会被阻塞出问题,假设有个可读事件,对方发送了2K数据,但是一次read只读了1K,然后调用epoll_wait去监听状态,就会无法处理那个事件,因为没有新的可读可写去触发该事件,这个需要程序员来维护。

服务器程序:

#include<sys/epoll.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/wait.h>
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<signal.h>
#include<vector>
#include<algorithm>
#define ERR_EXIT(m) do{perror(m);exit(EXIT_FAILURE);}while(0)
typedef std::vector<struct epoll_event>Eventlist;
void do_service(int connfd)
{
	char recvbuf[1024];
	int n;
	while (1)
	{
		memset(&recvbuf, 0, sizeof(recvbuf));
		int ret = read(connfd, recvbuf, sizeof(recvbuf));
		if (ret == -1)
		{
			ERR_EXIT("read");
		}
		else if (ret == 0)
		{
			printf("client cloase\n");
			break;
		}
		fputs(recvbuf, stdout);
		write(connfd, recvbuf, strlen(recvbuf));
	}
}
void handle_sigchild(int sig)
{
//  wait(NULL);
   while(waitpid(-1,NULL,WNOHANG)>0);//
}
void handle_sigpipe(int sig)
{
        printf("recv a sig=%d\n",sig);
}
int main(void)
{
      //  signal(SIGPIPE,SIG_IGN);
    signal(SIGPIPE,handle_sigpipe);
	//signal(SIGCHLD, SIG_IGN);
	signal(SIGCHLD,handle_sigchild);
        int listenfd;
	if ((listenfd = socket(AF_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);
	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("listten");
	/*while (1)
	{
		int connfd;
		struct sockaddr_in peeraddr;
		socklen_t peerlen = sizeof(peeraddr);
		if ((connfd = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen)) < 0)ERR_EXIT("accept");
		printf("ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
		pid_t pid = fork();
		if (pid == -1)ERR_EXIT("fork");
		if (pid == 0)
		{
			close(listenfd);
			do_service(connfd);
			exit(EXIT_SUCCESS);
		}
		if (pid > 0)
		{
			close(connfd);
		}
	}*/
	std::vector<int>clients;
	int epollfd = epoll_create1(EPOLL_CLOEXEC);
	struct epoll_event event;
	event.data.fd = listenfd;
	event.events = EPOLLIN | EPOLLET;
	epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &event);
	Eventlist events(16);
	int nready = 0;
	while (1)
	{
		nready = epoll_wait(epollfd, &*events.begin(), static_cast<int>(events.size()), -1);
		if (nready == -1)
		{
			if (errno == EINTR)continue;//被信号中断
			ERR_EXIT("select");
		}
		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)//有新的套接口了
			{
				int connfd;
				struct sockaddr_in peeraddr;
				socklen_t peerlen = sizeof(peeraddr);
				if ((connfd = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen)) < 0)ERR_EXIT("accept");
				printf("ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
				clients.push_back(connfd);
				event.data.fd = connfd;
				event.events = EPOLLIN | EPOLLET;
				epoll_ctl(epollfd, EPOLL_CTL_ADD, connfd, &event);
			}
			else if (events[i].events&EPOLLIN)
			{
				int connfd = events[i].data.fd;
				if (connfd < 0)continue;
				char recvbuf[1024];
				memset(&recvbuf, 0, sizeof(recvbuf));
				int ret = read(connfd, recvbuf, sizeof(recvbuf));
				if (ret == -1)
				{
					ERR_EXIT("read");
				}
				else if (ret == 0)
				{
					printf("client close\n");
                    close(connfd);
					event = events[i];
					epoll_ctl(epollfd, EPOLL_CTL_DEL, connfd, &event);
					clients.erase(std::remove(clients.begin(), clients.end(), connfd), clients.end());
                }
				fputs(recvbuf, stdout);
				write(connfd, recvbuf, strlen(recvbuf));
				if (--nready <= 0)break;//检测到的事件已经处理完了,重新监听
			}
		}
	}
	close(listenfd);
}





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值