作用

Epoll 和 Select 的作用都是为了多I/O同步复用的问题,利用Epoll、Poll或Select函数指定内核监听多个I/O的读、写、异常事件,避免每一个I/O都指定一个处理线程,导致开销过大。

Select

需要指定最大监听描述符的值,还要传入对应监听事件的fd_set(read\write\exception),每一次Select函数触发都会将集合内容修改,所以开发者要设置缓存区来记录所有文件描述符,每次调用函数的时候都要重新初始化监听集合。

在Select触发后开发者需要去将缓存区的文件描述符与监听集合去匹配,例如描述符100在read set中,说明100有数据写入,可以立刻去读取,然后丢给worker线程去处理:

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <wait.h>
#include <signal.h>
#include <errno.h>
#include <sys/select.h>
#include <sys/time.h>
#include <unistd.h> #define MAXBUF 256 void child_process(void)
{
sleep(2);
char msg[MAXBUF];
struct sockaddr_in addr = {0};
int n, sockfd,num=1;
srandom(getpid());
/* Create socket and connect to server */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
addr.sin_family = AF_INET;
addr.sin_port = htons(2000);
addr.sin_addr.s_addr = inet_addr("127.0.0.1"); connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)); printf("child {%d} connected \n", getpid());
while(1){
int sl = (random() % 10 ) + 1;
num++;
sleep(sl);
sprintf (msg, "Test message %d from client %d", num, getpid());
n = write(sockfd, msg, strlen(msg)); /* Send message */
} } int main()
{
char buffer[MAXBUF];
int fds[5];
struct sockaddr_in addr;
struct sockaddr_in client;
int addrlen, n,i,max=0;;
int sockfd, commfd;
fd_set rset;
for(i=0;i<5;i++)
{
if(fork() == 0)
{
child_process();
exit(0);
}
} sockfd = socket(AF_INET, SOCK_STREAM, 0);
memset(&addr, 0, sizeof (addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(2000);
addr.sin_addr.s_addr = INADDR_ANY;
bind(sockfd,(struct sockaddr*)&addr ,sizeof(addr));
listen (sockfd, 5); for (i=0;i<5;i++)
{
memset(&client, 0, sizeof (client));
addrlen = sizeof(client);
fds[i] = accept(sockfd,(struct sockaddr*)&client, &addrlen);
if(fds[i] > max)
max = fds[i];
} while(1){
FD_ZERO(&rset);
for (i = 0; i< 5; i++ ) {
FD_SET(fds[i],&rset);
} puts("round again");
select(max+1, &rset, NULL, NULL, NULL); for(i=0;i<5;i++) {
if (FD_ISSET(fds[i], &rset)){
memset(buffer,0,MAXBUF);
read(fds[i], buffer, MAXBUF);
puts(buffer);
}
}
}
return 0;
}

Select 要求开发者去统计最大监听范围,fd_set是一个长度为32的整数数组,每一个fd对应一个bit位。内核会遍历传入集合的每一bit到最大描述符对应的bit。同时用户进程在处理返回结果的时候仍然要遍历集合,在用户进程中处理事件的效率是O(n)。它主要的优点是跨平台比较方便windows和linux都支持该函数。

同时需要注意的是,如果正被select监听的文件描述符在另一个线程被关闭,实际上这个文件描述符不会被真正的关闭。

Select 在处理超时精度上要比Epoll和Poll高,Epoll和Poll只能处理一毫秒的精度[4]。

Poll

在使用的时候要去传入一个pollfd数组,其中包含每一个需要监听的文件描述符。pollfd中包含监听事件和返回事件,不需要每次都去构建。

struct pollfd {
int fd;
short events;
short revents;
};

与Select类似,用户在处理返回结果的时候要轮寻检查revents,看看是否和期待的事件一致。在内核中也需要遍历文件描述符的列表来找到受监视的对象,然后再遍历文件描述符列表去设置事件。它也具备Select的缺陷,即无法在其他线程中关闭正在监听的文件描述符[4]:

int poll (struct pollfd *fds, unsigned int nfds, int timeout);

for (i=0;i<5;i++)
{
memset(&client, 0, sizeof (client));
addrlen = sizeof(client);
pollfds[i].fd = accept(sockfd,(struct sockaddr*)&client, &addrlen);
pollfds[i].events = POLLIN;
}
sleep(1);
while(1){
puts("round again");
poll(pollfds, 5, 50000); for(i=0;i<5;i++) {
if (pollfds[i].revents & POLLIN){
pollfds[i].revents = 0;
memset(buffer,0,MAXBUF);
read(pollfds[i].fd, buffer, MAXBUF);
puts(buffer);
}
}
}

Poll 与 Select相比:

  • 不需要统计最大监听范围
  • 更适合大值的文件描述符监听
  • 文件描述符监听集合不用每次都重建,但仍需要自己维护
  • Select移植性更好,超时监听更加灵敏

Epoll

epoll 会在内核中帮我们创建一个context用于记录要监听的文件描述符,可以在等待I/O事件的同时去添加或移除文件描述符。每次只会返回就绪的文件描述符,用户进程在处理返回结果上更加方便,时间复杂度为O(1)。但由于Epoll只在linux中支持,不支持跨平台,也是最晚出现的方法。

  struct epoll_event events[5];
int epfd = epoll_create(10); //指定监听数量,只是建议内核初始化分配空间,并不影响监听数量上限。
...
...
for (i=0;i<5;i++)
{
static struct epoll_event ev;
memset(&client, 0, sizeof (client));
addrlen = sizeof(client);
ev.data.fd = accept(sockfd,(struct sockaddr*)&client, &addrlen);
ev.events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, ev.data.fd, &ev);
} while(1){
puts("round again");
nfds = epoll_wait(epfd, events, 5, 10000); for(i=0;i<nfds;i++) {
memset(buffer,0,MAXBUF);
read(events[i].data.fd, buffer, MAXBUF);
puts(buffer);
}
}

因为监听的描述符记录在了内核中,所以即使用户进程被关闭了,内核也会去监视对应的对象,可以用于边缘触发等场景。

Epoll vs Select

  • 可以在等待的时候添加或移除监听对象
  • 只返回准备就绪的文件描述符
  • 用户进程处理事件效率更高(Epoll是O(1),Select是O(n))
  • Epoll只在Linux中支持,移植性差
  • Select超时监听更加灵敏
  • Epoll使用起来更复杂

Epoll vs Poll

  • 可以在等待的时候添加或移除监听对象
  • 只返回准备就绪的文件描述符
  • Epoll使用起来更复杂
  • 用户进程处理事件效率更高(Epoll是O(1),Poll是O(n))
  • Epoll使用起来更复杂

总结

Select、Epoll、Poll都是I/O多路复用的一种技术,都属于同步I/O的范畴,用户进程同时监听多个I/O事件[2]。

Select与Poll比较类似,每次调用函数都需要将监听的集合传入内核中,即要copy一次,返回的结果要轮寻匹配。不过相较Select而言Poll没有最大监听范围限制(Select在linux中最大监听范围为1024,此值可以修改),在没有变动的情况下无需每次调用时重新构建监听集合,而Select在跨平台性上和处理超时精度上是三者中最优的[4]。

Epoll是Select和Poll的优化版,它采用一个内核context来维护监听集合,如果在不需要变动的情况下,监听描述符设置只需要从用户进程向内核中copy一次。但读者要注意,每调用epoll_ctl函数去更新监听描述符的时候都是一次copy,由于不支持批量操作,每一个描述符都需要执行一次copy,所以如果是频繁的短链接,epoll的效率未必比poll好[4]。每次直接返回准备态的文件描述符集合,不需要轮寻匹配,用户进程处理事件的效率是最高的。

参考地址

Select 、Poll 和 Epoll的更多相关文章

  1. Linux下select, poll和epoll IO模型的详解

    http://blog.csdn.net/tianmohust/article/details/6677985 一).Epoll 介绍 Epoll 可是当前在 Linux 下开发大规模并发网络程序的热 ...

  2. I/O复用中的 select poll 和 epoll

    I/O复用中的 select poll 和 epoll: 这里有一些不错的资料: I/O多路复用技术之select模型: http://blog.csdn.net/nk_test/article/de ...

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

    Linux下select, poll和epoll IO模型的详解 原文:http://blog.csdn.net/tianmohust/article/details/6677985 一).Epoll ...

  4. linux select poll and epoll

    这里以socket文件来阐述它们之间的区别,假设现在服务器端有100 000个连接,即已经创建了100 000个socket. 1 select和poll 在我们的线程中,我们会弄一个死循环,在循环里 ...

  5. Select,poll,epoll复用

    Select,poll,epoll复用 1)select模块以列表的形式接受四个参数,分别是可读对象,可写对象,产生异常的对象,和超时设置.当监控符对象发生变化时,select会返回发生变化的对象列表 ...

  6. 聊聊select, poll 和 epoll

    聊聊select, poll 和 epoll 假设项目上需要实现一个TCP的客户端和服务器从而进行跨机器的数据收发,我们很可能翻阅一些资料,然后写出如下的代码. 服务端 void func(int s ...

  7. [转载] select, poll和epoll的区别

    源地址:http://sheepxxyz.blog.163.com/blog/static/61116213201022003513530/ 随着2.6内核对epoll的完全支持,网络上很多的文章和示 ...

  8. Linux中select poll和epoll的区别

    在Linux Socket服务器短编程时,为了处理大量客户的连接请求,需要使用非阻塞I/O和复用,select.poll和epoll是Linux API提供的I/O复用方式,自从Linux 2.6中加 ...

  9. Linux select/poll和epoll实现机制对比

    关于这个话题,网上已经介绍的比较多,这里只是以流程图形式做一个简单明了的对比,方便区分. 一.select/poll实现机制 特点: 1.select/poll每次都需要重复传递全部的监听fd进来,涉 ...

  10. select,poll 和 epoll ??

    其实所有的 I/O 都是轮询的方法,只不过实现的层面不同罢了. 其中 tornado 使用的就是 epoll 的. selec,poll 和 epoll 区别总结 基本上 select 有 3 个缺点 ...

随机推荐

  1. BroadcastReceiver(接收广播)

    Broadcast Receiver用于接收并处理广播通知(broadcast announcements).多数的广播是系统发起的,如地域变换.电量不足.来电来信等.程序也能够播放一个广播. 程序能 ...

  2. .Net core 使用NPOI 直接导入Excel到数据库(即不先将Excel保存到服务器再读取文件到数据库)

    /// <summary> /// 导入信息 /// </summary> /// <param name="file"></param& ...

  3. JAVA 8 新特性 __ Optional 类

    Optional 类是一个可以作为null容器的对象,若值存在调用isPresent()就返回 true,调用get()会返回该对象. Optional是一个容器,可以保存类型T的值,或者仅仅保存nu ...

  4. Oracle startup的四个阶段

    shutdown->nomount->mount->open: 1.shutdown:数据库关闭 2.nomount: 1)$ORACLE_HOME/dbs下找初始化参数文件 a.s ...

  5. DB数据源之SpringBoot+Mybatis踏坑过程实录系列(一)

    DB数据源之SpringBoot+MyBatis踏坑过程(一) liuyuhang原创,未经允许进制转载 系列目录 DB数据源之SpringBoot+Mybatis踏坑过程实录(一) DB数据源之Sp ...

  6. ABAP术语-V1 Module

    V1 Module 原文;http://www.cnblogs.com/qiangsheng/archive/2008/03/21/1115707.html Function module creat ...

  7. lrzsz Linux服务器Windows互传文件工具

    lrzsz是一款在linux里可代替ftp上传和下载的程序,但只限于较小的文件,如果是目录需要打包成单个文件在实现下载. 条件:需要使用SecureCRT或者Xshell等客户端工具连接Linux 下 ...

  8. Centos7下python3.7的pipSSLError问题

    在Centos7下成功安装了python3.7, 但是在使用pip3的时候出现了以下错误 中间试过了网上大家给出的好多种办法,一开始我只是在编译的时候 ./configure --with-ssl(p ...

  9. java 一个数字的位数不够怎么在前面加0

    import java.text.DecimalFormat; //(1).如果数字1是字符串,如下处理: String str1="1"; DecimalFormat df=ne ...

  10. Delphi XE7调用Java Class,JAR

    Delphi XE5,XE6需要用户手工编译并将Classes.Dex加入到包中,不过Delphi XE7可以省掉这些工作了. 如何在XE7中调用Java,具体步骤如下: 1.将jar文件添加到XE7 ...