linux的IO复用技术:select、pool、epool的区别以及epool的原理和使用

select、poll、epoll都是IO多路复用的机制,但是他们的机制有很大的区别

1、select

select机制刚开始的时候,需要把fd_set从用户空间拷贝到内核空间,并且检测的fd数是有限制的,由FD_SETSIZE设置,一般是1024。

检测的时候,根据timeout,遍历fd_set表,把活跃的fd(可读写或者错误),拷贝到用户空间,

再在用户空间依次处理相关的fd。

这个机制是linux内核很早的版本,epool是根据select,pool基础上优化的,缺点比较多。

缺点:

1)每次调用select的时候需要把fd_set从用户空间拷贝到内存空间,比较耗性能。

2)wait时,需要遍历所有的fd,消耗比较大。

3)select支持的文件数大小了,默认只有1024,如果需要增大,得修改宏FD_SETSIZE值,并编译内核(麻烦,并且fd_set中的文件数多的话,每次遍历的成本就很大)。

2. pool

poll的实现和select非常相似,只是描述fd集合的方式不同,poll使用pollfd结构而不是select的fd_set结构,其他的都差不多。

3. epool

epool是select和poll的改进版本,

* 先是使用int epoll_create(int size)在内存中创建一个指定size大小的事件空间,

* 再使用int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);事件注册函数,注册新的fd到epfd的epool对象空间中,并指明event(可读写啊等等),注意:在注册新事件fd的过程中,也再内核中断处理程序里注册fd对应的回调函数callback,告诉内核,一旦这个fd中断了,就把它放到ready队列里面去。

* 再使用int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);在epool对象对应的ready队列里取就绪的fd,并使用内存映射mmap拷贝到用户空间。

* 再在用户空间依次处理相关的fd。

优点:

1)支持一个进程打开大数目的socket描述符

select 一个进程打开FD是有限制的,由FD_SETSIZE设置,默认值是1024。epool可以打开的FD数可以很大,一般1GB的内存有10万多的FD数,具体数目可以cat
/proc/sys/fs/file-max查看。

2) IO效率不随FD数目增加而线性下降

3) 使用mmap加速内核与用户空间的消息传递

epool的使用:

epoll的相关系统调用

epoll只有epoll_create,epoll_ctl,epoll_wait 3个系统调用。

1. int epoll_create(int size);

创建一个epoll的句柄。自从linux2.6.8之后,size参数是被忽略的。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。

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

epoll的事件注册函数,它不同于select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。

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

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

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队列里

3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

收集在epoll监控的事件中已经发送的事件。参数events是分配好的epoll_event结构体数组,epoll将会把发生的事件赋值到events数组中(events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们在用户态中分配内存)。maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。如果函数调用成功,返回对应I/O上已准备好的文件描述符数目,如返回0表示已超时。

用例

写了一个类,封装了epool的使用接口:

#include <sys/epoll.h>
class FDEvent
{
	int evfd;
	struct epoll_event *events;
	int maxfds;
	bool nonblocking;
	int epet;
public:
	typedef int EventType;
	FDEvent(int max_client_num, bool is_nonblocking):maxfds(max_client_num),nonblocking(is_nonblocking)
	{
		if ((evfd = epoll_create(maxfds)) == -1)
		{
			perror("epoll_create error!");
			exit(1);
		}
		if (fcntl(evfd, F_SETFD, FD_CLOEXEC) == -1)
		{
			perror("epoll_create error!");
			exit(1);
		}
		events = new epoll_event[max_client_num];
		epet = 0;//nonblocking?EPOLLET:0;
	}
	~FDEvent()
	{
		delete[] events;
	}
	int wait(int timeout)
	{
		return epoll_wait(evfd, events, maxfds, timeout);
	}
	int add(int fd, bool init = false)
	{
		epoll_event ev;
		if (init)
		{
			ev.events = EPOLLIN;
		}
		else
		{
			ev.events = EPOLLIN|epet;
		}
		ev.data.u64 = 0;
		ev.data.fd = fd;
		if (epoll_ctl(evfd, EPOLL_CTL_ADD, fd, &ev) == -1)
		{
			perror("epoll_add error!");
			return -1;
		}
		return 0;
	}
	int del(int fd)
	{
		epoll_event ev;
		ev.data.fd = fd;
		if (epoll_ctl(evfd, EPOLL_CTL_DEL, fd, &ev) == -1)
		{
			perror("epoll_del error!");
			return -1;
		}
		return 0;
	}
	pair<int, EventType> get(uint32_t i)
	{
		return make_pair(events[i].data.fd, events[i].events);
	}
	bool isEventIn(EventType t)
	{
		return t&EPOLLIN;
	}
};

使用:
int n = fde.wait(-1);
for (int i=0; i<n; i++)
{
        pair<int, FDEvent::EventType> p = fde.get(i);
         if (p.first == listenfd)
        {
                int clientfd = accept(listenfd, (struct sockaddr *)&remote_addr, &adsize);
                fde.add(clientfd);
        }
        else if (fde.isEventIn(p.second))
        {
               fde.del(p.first);
        }
}

参考文章:

1. http://blog.csdn.net/xiajun07061225/article/details/9250579#(这个比较系统的讲epool)

2. http://watter1985.iteye.com/blog/1614039(这个是select, pool和epool源码剖析)

3. http://www.cnblogs.com/Anker/p/3265058.html (这个总结的很好)

时间: 2024-12-10 15:35:42

linux的IO复用技术:select、pool、epool的区别以及epool的原理和使用的相关文章

一次读懂 Select、Poll、Epoll IO复用技术

我们之前采用的多进程方式实现的服务器端,一次创建多个工作子进程来给客户端提供服务.其实这种方式是存在问题的. 可以打个比方:如果我们先前创建的几个进程承载不了目前快速发展的业务的话,是不是还得增加进程数?我们都知道系统创建进程是需要消耗大量资源的,所以这样就会导致系统资源不足的情况. 那么有没有一种方式可以让一个进程同时为多个客户端端提供服务? 接下来要讲的IO复用技术就是对于上述问题的最好解答. 对于IO复用,我们可以通过一个例子来很好的理解它.(例子来自于<TCP/IP网络编程>) 某教室

Libevent的IO复用技术和定时事件原理

Libevent 是一个用C语言编写的.轻量级的开源高性能网络库,主要有以下几个亮点:事件驱动( event-driven),高性能;轻量级,专注于网络,不如 ACE 那么臃肿庞大:源代码相当精炼.易读:跨平台,支持 Windows. Linux. *BSD 和 Mac Os:支持多种 I/O 多路复用技术, epoll. poll. dev/poll. select 和 kqueue 等:支持 I/O,定时器和信号等事件:注册事件优先级. 1 Libevent中的epoll Libevent重

转:Linux网络IO并行化技术概览

转:http://codinginet.com/articles/view/201605-linux_net_parallel?simple=1&from=timeline&isappinstalled=0 Linux网络IO并行化技术概览 By mikewei at 2016-05-21 00:30 阅读(276) 过去的十年中互联网经历了爆发式的增长,这背后有什么技术平台起了最为关键的作用,我认为是Linux,即使在云计算流行的今天,它依然是最重要的一块基石.我们或许经常听到关于什么是

Redis03——Redis之单线程+多路IO复用技术

Redis 是单线程+多路IO复用技术 多路复用:使用一个线程来检查多个文件描述符的就绪状态 如果有一个文件描述符就绪,则返回 否则阻塞直到超时 得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动线程执行(线程池) 阻塞lO:给女神发一条短信, 说我来找你了,然后就默默的一直等着 女神下楼,这个期间除了等待你不会做其他事情,属于备胎做法. 非阻塞IO:给女神发短信,如果不回,接着再发,一直 发到女神下楼,这个期间你除了发短信等待不会做其他事情,属于专-做法. IO多路复用:是找一个宿

IO复用一select, poll, epoll用法说明

三种IO复用类型 Select系统调用 #include<sys/select.h> int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* execptfds,struct timeval* timeout); #nfds表示监听的文件描述符总数: #readfds,writefds,execptfds分别表示对应的fd_set类型的集合 可以如下定义:fd_set readfds,writefds,execptfds

Linux网络编程-IO复用技术

IO复用是Linux中的IO模型之一,IO复用就是进程预先告诉内核需要监视的IO条件,使得内核一旦发现进程指定的一个或多个IO条件就绪,就通过进程进程处理,从而不会在单个IO上阻塞了.Linux中,提供了select.poll.epoll三种接口函数来实现IO复用. 1.select函数 #include <sys/select.h> #include <sys/time.h> int select(int nfds, fd_set *readfds, fd_set *writef

Unix网络编程(六)高级I/O技术之复用技术 select

I/O复用技术 本文将讨论网络编程中的高级I/O复用技术,将从下面几个方面进行展开: a. 什么是复用技术呢? b. 什么情况下需要使用复用技术呢? c. I/O的复用技术的工作原理是什么? d. select, poll and epoll的实现机制,以及他们之间的区别. 下面我们以一个背景问题来开始: 包括在以前的文章中我们讨论的案例都是阻塞式的I/O包括(fgetc/getc, fgets/gets),即当输入条件未满足时进程会阻塞直到满足之后进行读取,但是这样导致的一个 问题是如果此时进

IO复用之——select

一. select 前面提到Linux下的五种IO模型中有一个是IO复用模型,这种IO模型是可以调用一个特殊的函数同时监听多个IO事件,当多个IO事件中有至少一个就绪的时候,被调用的函数就会返回通知用户进程来处理已经ready事件的数据,这样通过同时等待IO事件来代替单一等待一个IO窗口数据的方式,可以大大提高系统的等待数据的效率:而接下来,就要讨论在Linux系统中提供的一个用来进行IO多路等待的函数--select: 二. select函数的用法 首先在使用select之前,要分清在IO事件

IO复用之select poll epoll的总结

I/O复用使得程序能够同时监听多个文件描述符,对于提高程序性能至关重要.I/O复用不仅仅在网络程序中使用,但是我接触到的例子中,TCP网络编程那块使用I/O复用比较多,例如,TCP服务器同时处理监听socket和连接socket. 在了解I/O复用之前,我们需要先了解几个概念. 1,同步I/O与异步I/O 2,LT(水平触发)和ET(边缘触发) POSIX把两个术语定义如下: 同步I/O:导致请求进程阻塞,直到I/O操作完成 异步I/O:  不导致请求进程阻塞 阻塞是进程在等待某种资源,但是不能