epoll 和select 的区别,epoll和select的实例

epoll是Linux内核为处理高并发而作改进的poll,是Linux下多路复用IO接口select/poll的增强版本。这里主要讲epoll和另外两个的区别,另外再把epoll的一个简单运用实例说说。

(一)epoll 有select,poll的主要区别:

一、相比于select与poll, epoll最大的好处在于它不会随着监听fd数目的增长而降低效率;

二、内核中的select与poll的实现是采用轮询来处理的,轮询的fd数据越多,自然耗时也越多;

三、epoll的实现是基于回调的,如果fd有期望的事件发生就通过回调函数将其加入epoll的就绪对了当中,也就是说它只关心活跃的fd

四、内核/用户空间拷贝问题,当内核把fd消息通知给用户空间时,selec和poll采用了内存拷贝的方法,而epoll采用了共享内存的方式。

(二)epoll 相关系统调用

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

一、(1)int epoll_create(int size);

创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大,这里其实是指定hash表的容量。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。

(2)int epoll_create(int flags);

在比较新的Linux的版本中,出现了 int epoll_create(int flags);  这里的flags 一般选EPOLL_CLOEXEC,表示当进程被替换的时候文件描述符会被关闭。

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

epoll的事件注册函数,即注册要监听的事件类型。

第一个参数是epoll_create()的返回值,

第二个参数表示动作,用三个宏来表示:

EPOLL_CTL_ADD:注册新的fd到epfd中;

EPOLL_CTL_MOD:修改已经注册的fd的监听事件;

EPOLL_CTL_DEL:从epfd中删除一个fd;

第三个参数是需要监听的fd,

第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:

struct epoll_event {
  __uint32_t events;
  epoll_data_t data;
};

typedef union epoll_data {
void *ptr;    //这里是个指针,主要用来存放复杂的文件描述符
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;

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);

等待事件的产生。参数events
用来从内核得到事件的集合,maxevents 告之内核这个events
有多大,这个maxevents 的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1一直等待,即阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。

(三)epoll 实例

这里以一个简单的客户端回射为例,程序的运行环境是debian(linux的一个发行版本),从上到下分别是头文件"net.h",epoll_server.cpp,select_client.cpp

服务器端:epoll实现的,干两件事分别为:1.等待客户端的链接,2.接收来自客户端的数据并且回射;

客户端:select实现,干两件事为:1.等待键盘输入,2.发送数据到服务器端并且接收服务器端回射的数据;

/*****************
@author:shaosli
@data: 2015/7/28
@funtions:network file
***************************/
#include <stdio.h>

#ifndef _NET_L
#define _NET_L

#include <iostream>
#include <vector>
#include <algorithm>

#include <stdio.h>
#include <sys/types.h>
#include <sys/epoll.h>  //epoll ways file
#include <sys/socket.h>
#include <fcntl.h>    //block and noblock

#include <stdlib.h>
#include <error.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <signal.h>

using namespace std;

#define hand_error(msg) do{perror(msg); exit(EXIT_FAILURE);}while(0)
#endif

服务器端代码:

/****************
@data: 2015/7/28
@authour:shaosli
@function: this cpp is used to test epoll
**************************/

#include "../public/net.h"
#define MAX_EVENTS 10000

int setblock(int sock)
{
	int ret =  fcntl(sock, F_SETFL, 0);
	if (ret < 0 )
		hand_error("setblock");
	return 0;
}
int setnoblock(int sock)  //设置非阻塞模式
{
	int ret = fcntl(sock,  F_SETFL, O_NONBLOCK );
	if(ret < 0)
		hand_error("setnoblock");
	return 0;
}

int main()
{

    int listenfd;
	listenfd = socket( AF_INET, SOCK_STREAM,0 );   //create a socket stream
	if( listenfd < 0 )
		hand_error( "socket_create");
	setnoblock(listenfd);
	int on = 1;
	if( setsockopt( listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))< 0)
		hand_error("setsockopt");

	struct sockaddr_in my_addr;
	memset(&my_addr, 0, sizeof(my_addr));
	my_addr.sin_family = AF_INET;
	my_addr.sin_port = htons(5188);   //here is host  sequeue
	my_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

	if( bind( listenfd, (struct sockaddr *)&my_addr, sizeof(my_addr)) < 0)
		hand_error("bind");

	int lisId = listen(listenfd, SOMAXCONN);
	if( lisId < 0)   //LISTEN
		hand_error("listen");

	struct sockaddr_in peer_addr;   //用来 save client addr
	socklen_t peerlen;
	//下面是一些初始化,都是关于epoll的。
	vector<int> clients;
	int count = 0;
	int cli_sock = 0;
	int epfd = 0;  //epoll 的文件描述符
	int ret_events;  //epoll_wait()的返回值
    struct epoll_event ev_remov, ev, events[MAX_EVENTS];  //events 用来存放从内核读取的的事件
	ev.events = EPOLLET | EPOLLIN;   //边缘方式触发
	ev.data.fd = listenfd;

	epfd = epoll_create(MAX_EVENTS);   //create epoll,返回值为epoll的文件描述符
	//epfd = epoll_create1(EPOLL_CLOEXEC);  //新版写法
	if(epfd < 0)
		hand_error("epoll_create");
	int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);   //添加时间
	if(ret < 0)
		hand_error("epoll_ctl");

	while(1)
	{
		ret_events = epoll_wait(epfd, events, MAX_EVENTS, -1);   //类似于select函数,这里是等待事件的到来。
		if(ret_events == -1)
		{
			cout<<"ret_events = "<<ret_events<<endl;
			hand_error("epoll_wait");
		}

		if( ret_events == 0)
		{
			cout<<"ret_events = "<<ret_events<<endl;
			continue;
		}

		cout<<"ret_events = "<<ret_events<<endl;
		for( int num = 0; num < ret_events; num ++)
		{
			cout<<"num = "<<num<<endl;
			if(events[num].events == listenfd)   //client connect;
			{
				cout<<"listen sucess and listenfd = "<<listenfd<<endl;
				cli_sock = accept(listenfd, (struct sockaddr*)&peer_addr, &peerlen);
				if(cli_sock < 0)
					hand_error("accept");
				cout<<"count = "<<count++;
				printf("ip=%s,port = %d\n", inet_ntoa(peer_addr.sin_addr),peer_addr.sin_port);
				clients.push_back(cli_sock);
				setnoblock(cli_sock);   //设置为非阻塞模式
				ev.data.fd = cli_sock;
				ev.events = EPOLLIN | EPOLLET ;
				if(epoll_ctl(epfd, EPOLL_CTL_ADD, cli_sock, &ev)< 0)
					hand_error("epoll_ctl");
			}

			else if( events[num].events & EPOLLIN)
			{
				cli_sock = events[num].data.fd;
				if(cli_sock < 0)
					hand_error("cli_sock");
				char recvbuf[1024];
				memset(recvbuf, 0 , sizeof(recvbuf));
				int num = read( cli_sock, recvbuf, sizeof(recvbuf));
				if(num == -1)
					hand_error("read have some problem:");
				if( num == 0 )  //stand of client have exit
					{
						cout<<"client have exit"<<endl;
						close(cli_sock);
						ev_remov = events[num];
						epoll_ctl(epfd, EPOLL_CTL_DEL, cli_sock, &ev_remov);
						clients.erase(remove(clients.begin(), clients.end(), cli_sock),clients.end());
					}
					fputs(recvbuf,stdout);
					write(cli_sock, recvbuf, strlen(recvbuf));
			}
		}
	}

	return 0;
}

客户端代码:

/****************
@data: 2015/7/18
@authour:shaosli
@function: this is a client
**************************/

#include "../public/net.h"

int main()
{
    int sock;
	sock = socket( AF_INET, SOCK_STREAM,0 );   //create a socket stream
	if( sock< 0 )
		hand_error( "socket_create");

	struct sockaddr_in my_addr;

	//memset my_addr;
	memset(&my_addr, 0, sizeof(my_addr));
	my_addr.sin_family = AF_INET;
	my_addr.sin_port = htons(5188);   //here is host sequeue
	//my_addr.sin_addr.s_addr = htonl( INADDR_ANY );
	my_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

	int conn = connect(sock, (struct sockaddr *)&my_addr, sizeof(my_addr)) ;
	if(conn != 0)
		hand_error("connect");

	char recvbuf[1024] = {0};
	char sendbuf[1024] = {0};
	fd_set rset;
	FD_ZERO(&rset);     

	int nready = 0;
	int maxfd;
	int stdinof = fileno(stdin);
	if( stdinof > sock)
		maxfd = stdinof;
	else
		maxfd = sock;

	while(1)
	{
		//select返回后把原来待检测的但是仍没就绪的描述字清0了。所以每次调用select前都要重新设置一下待检测的描述字
		FD_SET(sock, &rset);
		FD_SET(stdinof, &rset);
		nready = select(maxfd+1, &rset, NULL, NULL, NULL);
		cout<<"nready = "<<nready<<endl;
		if(nready == -1 )
			break;
		else if( nready == 0)
			continue;
		else
		{
			if( FD_ISSET(sock, &rset) )  //检测sock是否已经在集合rset里面。
			{
				int ret = read( sock, recvbuf, sizeof(recvbuf));  //读数据
				if( ret == -1)
					hand_error("read");
				else if( ret == 0)
				{
					cout<<"sever have close"<<endl;
					close(sock);
					break;
				}
				else
				{
					fputs(recvbuf,stdout);    //输出数据
					memset(recvbuf, 0, strlen(recvbuf));
				}
			}

			if( FD_ISSET(stdinof, &rset))   //检测stdin的文件描述符是否在集合里面
			{
				if(fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
				{
					int num = write(sock, sendbuf, strlen(sendbuf));   //写数据
					cout<<"sent num = "<<num<<endl;
					memset(sendbuf, 0, sizeof(sendbuf));
				}
			}
		}
	}
	return 0;
}

代码在我自己的系统上测试过。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 10-31

epoll 和select 的区别,epoll和select的实例的相关文章

epoll 和 select 的区别

面试linux的时候,很多面试官都会问的一个问题是epoll和select的区别,而我总是答的不够完整,今天总结一下epoll和select的区别. 首先select是posix支持的,而epoll是linux特定的系统调用,因此,epoll的可移植性就没有select好,但是考虑到epoll和select一般用作服务器的比较多,而服务器中大多又是linux,所以这个可移植性的影响应该不会很大. 其次,select可以监听的文件描述符有限,最大值为1024,而epoll可以监听的文件描述符则是系

Apache select和Nginx epoll模型区别

部分内容摘自跟老男孩学Linux运维:Web集群实战(运维人员必备书籍) http://oldboy.blog.51cto.com/2561410/1752270 1.select 和epoll模型区别 1.1.网络IO模型概述 通常来说,网络IO可以抽象成用户态和内核态之间的数据交换.一次网络数据读取操作(read),可以拆分成两个步骤:1)网卡驱动等待数据准备好(内核态)2)将数据从内核空间拷贝到进程空间(用户态).根据这两个步骤处理方式不一样,我们通常把网络IO划分成阻塞IO和非阻塞IO.

[转]IO模型及select、poll、epoll和kqueue的区别

(一)首先,介绍几种常见的I/O模型及其区别,如下: blocking I/O nonblocking I/O I/O multiplexing (select and poll) signal driven I/O (SIGIO) asynchronous I/O (the POSIX aio_functions)—————异步IO模型最大的特点是 完成后发回通知. 阻塞与否,取决于实现IO交换的方式.      异步阻塞是基于select,select函数本身的实现方式是阻塞的,而采用sel

Apache select与Nginx epoll模型区别

1.select 和epoll模型区别1.1.网络IO模型概述通常来说,网络IO可以抽象成用户态和内核态之间的数据交换.一次网络数据读取操作(read),可以拆分成两个步骤:1)网卡驱动等待数据准备好(内核态)2)将数据从内核空间拷贝到进程空间(用户态).根据这两个步骤处理方式不一样,我们通常把网络IO划分成阻塞IO和非阻塞IO.·阻塞IO.用户调用网络IO相关的系统调用时(例如read),如果此时内核网卡还没有读取到网络数据,那么本次系统调用将会一直阻塞,直到对端系统发送的数据到达为止.如果对

I/O多路复用之select,poll,epoll的区别

一.关于select,poll,epoll 三种IO模型,都属于多路IO就绪通知,提供了对大量文件描述符就绪检查的高性能方案,只不过实现方式有所不同: select原理概述: 调用select时,会发生以下事情: (1)从用户空间拷贝fd_set到内核空间: (2)注册回调函数__pollwait: (3)遍历所有fd,对全部指定设备做一次poll(这里的poll是一个文件操作,它有两个参数,一个是文件fd本身,一个是当设备尚未就绪时调用的回调函数__pollwait,这个函数把设备自己特有的等

Linux下select, poll和epoll IO模型的详解(转)

http://blog.csdn.net/tianmohust/article/details/6677985 一).Epoll 介绍 Epoll 可是当前在 Linux 下开发大规模并发网络程序的热门人选, Epoll 在 Linux2.6 内核中正式引入,和 select 相似,其实都 I/O 多路复用技术而已 ,并没有什么神秘的.其实在 Linux 下设计并发网络程序,向来不缺少方法,比如典型的 Apache 模型( Process Per Connection ,简称 PPC ), TP

select,poll 和 epoll ??

其实所有的 I/O 都是轮询的方法,只不过实现的层面不同罢了. 其中 tornado 使用的就是 epoll 的. selec,poll 和 epoll 区别总结 基本上 select 有 3 个缺点: 1.连接数受限   2.查找配对速度慢   3.数据由内核拷贝到用户态 poll 改善了第一个缺点 epoll 改了三个缺点. 原文地址:https://www.cnblogs.com/lmh001/p/9739507.html

I/O复用的 select poll和epoll的简单实现

一个tcp的客户端服务器程序 服务器端不变,客户端通过I/O复用轮询键盘输入与socket输入(接收客户端的信息) 服务器端: 1 /*selcet服务器客户端模型: 2 1.客户端关闭后,服务器再向客户端发送信息,第一次会收到一个RST复位报文,第二次会收到SIGPIPE信号,导致服务器关闭,必须对这个信号进行处理: 3 1.在服务器对read返回值为0的情况进行处理,不向客户端发送信息 4 2.signal函数: signal(SIGPIPE, handle) 或者直接忽略signal(SI

阻塞、非阻塞、异步、同步以及select/poll和epoll

针对IO,总是涉及到阻塞.非阻塞.异步.同步以及select/poll和epoll的一些描述,那么这些东西到底是什么,有什么差异? 一般来讲一个IO分为两个阶段: 等待数据到达 把数据从内核空间拷贝到用户空间 现在假设一个进程/线程A,试图进行一次IO操作. A发出IO请求,两种情况: 1)立即返回 2)由于数据未准备好,需要等待,让出CPU给别的线程,自己sleep 第一种情况就是非阻塞,A为了知道数据是否准备好,需要不停的询问,而在轮询的空歇期,理论上是可以干点别的活,例如喝喝茶.泡个妞.