epoll与select、poll区别

1、用select实现的并发服务器,能达到的并发数,受两个方面的限制

 (1)一个进程能打开的最大文件描述符限制。这个可以通过调整内核参数完成。(可用ulimit -n number命令进行修改)

  (2) select中的fd_set集合容量的限制(FD_SETSIZE),这需要重新编译内核



2、用poll实现的并发服务器,能达到的并发数,受一个方面的限制

     (1)一个进程能打开的最大文件描述符限制。这个可以通过调整内核参数完成。(可用ulimit -n numbern命令进行修改)



注意:系统所有打开的最大文件描述个数也是有限的。可以用命令cat  /proc/sy/fs/file-max 个数是49041(我当前内存是512M,如果是1G最大个数将达到10万个左右,10G的话井竟达到百万左右)


select和poll的共同特点是:两者内核都要遍历所有文件描述,直到找到发生事件的文件描述符。(随着描述符的增多,遍历的效率也会降低,或成指数降低或成线性降低)


epoll的接口非常简单,一共就三个函数:

1、epoll_create函数
函数声明:int epoll_create(int size)
该 函数生成一个epoll专用的文件描述符。它其实是在内核申请一空间,用来存放你想关注的socket fd上是否发生以及发生了什么事件。size就是你在这个epoll fd上能关注的最大socket fd数。随你定好了。只要你有空间。可参见上面与select之不同2.

2、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:关联的文件描述符;
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 events */
epoll_data_t data; /* User data variable */
};


如:
struct epoll_event ev;
//设置与要处理的事件相关的文件描述符
ev.data.fd=listenfd;
//设置要处理的事件类型
ev.events=EPOLLIN|EPOLLET;
//注册epoll事件
epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);


常用的事件类型:
EPOLLIN :表示对应的文件描述符可以读;
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET:表示对应的文件描述符有事件发生;


3、epoll_wait函数
函数声明:int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout)
该函数用于轮询I/O事件的发生;
参数:
epfd:由epoll_create 生成的epoll专用的文件描述符;
epoll_event:用于回传代处理事件的数组;
maxevents:每次能处理的事件数;
timeout:等待I/O事件发生的超时值(单位我也不太清楚);-1相当于阻塞,0相当于非阻塞。一般用-1即可
返回发生事件数。

epoll模式的模式分为EPOLLLT和EPOLLET

EPOLLLT

1、完全靠kernel epoll驱动,应用程序只需要处理从epoll_wait返回的fds,这些fds我们认为它们处于就绪状态

EPOLLET

1、此模式下,系统仅仅通知应用程序哪些fds变成了就绪状态,一旦fd变成就绪状态,epoll将不再关注这个fd的任何状态信息,(从epoll队列移除)直到应用程序通过读写操作触发EAGAIN状态,epoll认为这个fd又变为空闲状态,那么epoll又重新关注这个fd的状态变化(重新加入epoll队列)。
2、随着epoll_wait的返回,队列中的fds是在减少的,所以在大并发的系统中,EPOLLET更有优势。但是对程序员的要求也更高。

//下面是具体事例代码


<span style="font-size:18px;">#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <poll.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <vector>
#include <algorithm>
#include <sys/epoll.h>
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 = 0;
	int flags = fcntl(fd, F_GETFL);
	if (flags == -1)
		ERR_EXIT("fcntl");
	flags |= O_NONBLOCK;
	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, const 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 echo_srv(int conn) {
	char recvbuf[1024];
	while (1) {
		memset(recvbuf, 0, sizeof(recvbuf));
		int ret = readline(conn, recvbuf, 1024);
		if (ret == -1)
			ERR_EXIT("readline");
		if (ret == 0) {
			printf("client close\n");
			break;
		}

		fputs(recvbuf, stdout);
		writen(conn, recvbuf, strlen(recvbuf));
	}
}

void handle_sigchld(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) {
	int count = 0;
	signal(SIGPIPE, handle_sigpipe);
	signal(SIGCHLD, handle_sigchld);
	int listenfd;
	if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 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("listen");

	std::vector<int> clients;
	int epollfd = 0;
	epollfd = epoll_create1(EPOLL_CLOEXEC);
	//声明epoll_event结构体的变量,event用于注册事件,
	struct epoll_event event;
	//设置与要处理的事件相关的文件描述符
	event.data.fd = listenfd;
	 //设置要处理的事件类型
	event.events = EPOLLIN | EPOLLET;
	epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &event);

	EventList events(16);
	struct sockaddr_in peeraddr;
	socklen_t peerlen;
	int conn = 0;
	int i = 0;
	int nready = 0;
	while (1) {
		 //等待epoll事件的发生
		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 (i = 0; i < nready; i++) {
			//如果新监测到一个SOCKET用户连接到了绑定的SOCKET端口,建立新的连接。
			if (events[i].data.fd == listenfd) {
				peerlen = sizeof(peeraddr);
				//accept这个连接
				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);
				 //把socket设置为非阻塞方式
				activate_nonblock(conn);
				event.data.fd = conn;
				event.events = EPOLLIN | EPOLLET;
				//将新的fd添加到epoll的监听队列中
				epoll_ctl(epollfd, EPOLL_CTL_ADD, conn, &event);

			} else if (events[i].events & EPOLLIN) {//接收到数据,读socket
				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 close\n");
					close(conn);
					event = events[i];
					//删除sockfd上要处理的事件为EPOLIN
					epoll_ctl(epollfd, EPOLL_CTL_DEL, conn, &event);
					clients.erase(
							std::remove(clients.begin(), clients.end(), conn),
							clients.end());
				}

				fputs(recvbuf, stdout);
				writen(conn, recvbuf, strlen(recvbuf));
			}
		}
	}
	return 0;
}
</span>




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值