缘起

面试的时候经常被问的一个很蛋疼的问题,经常被问,但是知识很零散,难记忆,看完就忘

select

作用

可以监视文件描述符是否可以读写,要求监视的文件描述符是非阻塞的

诞生背景

产生与上个世纪80年代的UNIX系统,到1993年写入POSIX1.b规范(一个操作系统的编程接口的规范,你要是写个操作系统想被兼容得遵守这个规范)。由于那个年代还没有多线程(2年后线程相关的内容才写入POSIX1.c规范),还没有什么C10K问题,所以在设计select的时候体现了那个年代的特点。

接口

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

nfds 是参数2,3,4中最大的文件描述符 + 1

readfds 是要检测fd的可读事件,当fd可读的时候select就返回

writefds 是要检测fd的写事件,当fd可写的时候select就返回

excpetfds 是要检测fd的出错事件,当fd出错的时候select就返回,当是NULL的时候,就是检测readfds和writefds的出错事件,所以一般都写NULL

timeout 是一个纳秒级的超时时间

想使用这个函数需要设置个fd_set结构,所以就用到void FD_SET(int fd, fd_set *set);这个宏,用来设置fd_set

使用过程

fd_set fd_in, fd_out;
struct timeval tv; // 初始化fd_set
FD_ZERO( &fd_in );
FD_ZERO( &fd_out ); // 把网络IO复制到fd_set
FD_SET( sock1, &fd_in );
FD_SET( sock1, &fd_out ); // 用户初始化select
int largest_sock = sock1 > sock2 ? sock1 : sock2; // 设置select的超时时间
tv.tv_sec = ;
tv.tv_usec = ; // 调用select 并阻塞在这里等待 IO事件
int ret = select( largest_sock, &fd_in, &fd_out, NULL, &tv ); // 检查返回值的状态
if ( ret == - )
// 异常情况
else if ( ret == )
// 超时或者没有可以监控的fd
else
{
// 检测每个IO事件是否可以读写
if ( FD_ISSET( sock1, &fd_in ) )
// IO可读 if ( FD_ISSET( sock2, &fd_out ) )
// IO可写
}

可以看到使用select的时候,每个fd对应一个fd_set结构,然后调用FD_SET,调用select以后进入polling,等返回以后通过FD_ISSET对每个fd_set检测是否可读可写。

存在问题

是不是会儿还没有现在nginx几万并发的场景,select只能对1024个fd进行监控

select 函数会修改fd_set,所以每次调用完select以后需要重新通过FD_SET设置fd_set

select 返回以后并不知道具体哪个fd可以读写,需要使用FD_ISSET把所有的fd检测一遍才知道具体是哪个可读可写

那年代估计不像现在这么广泛的用多线程,所以select中的fd_set在调用select的时候相当于被独占的

优点

使用简单,POSIX标准所以跨平台比较好

POLL

功能和select相同,但是主要解决select的一些限制

接口

int poll(struct pollfd fds[], nfds_t nfds, int timeout);

fds 是一个pollfd的数组,和select的fd_set差不多,下面具体解释

nfds 是fds数组的长度,可以看到没有select还需要求一个fd最大值再加1那么麻烦

timeout 是超时的毫秒数

pollfd的结构

struct pollfd {
int fd; /* 文件描述符 */
short events; /* 需要监听的事件 */
short revents; /* 返回的事件 */
};

对比一下select,接口上更加优雅,首先,pollfd 通过单独的events来区分了监控的是什么样的事件,而不是像select那样通过参数来区分。

使用过程

// 创建pollfd
struct pollfd fds[]; // 设置pollfd的fd和要监控的事件,sock1监控读,sock2监控写
fds[].fd = sock1;
fds[].events = POLLIN;
fds[].fd = sock2;
fds[].events = POLLOUT; // 10秒超时,开始等待sock1和sock2上的事件
int ret = poll( &fds, , );
// 有事件返回
if ( ret == - )
// 出错了
else if ( ret == )
// 超时
else
{
// 对每个pollfd检测是否有就绪的事件
if ( pfd[].revents & POLLIN )
pfd[].revents = ;
// 可读 if ( pfd[].revents & POLLOUT )
pfd[].revents = ;
// 可写
}

与select相同,都是创建结构,设置,开始polling,逐个检测事件

相比于SELECT的改进

对于可以监控fd的数量没有限制,而不是像select那样最大才1024个

每次poll之后不需要重新设置pollfd,而不像fd_set需要重新设置

兼容性

vista之前的windows上没有poll

#if defined (WIN32)
static inline int poll( struct pollfd *pfd, int nfds, int timeout) { return WSAPoll ( pfd, nfds, timeout ); }
#endif

EPOLL

linux平台上最新的polling技术,出现与linux2.6版本,linux2.6发布是在2003年(居然epoll出现已经12年了)。

接口

int epoll_create(int size);

用于创建一个size大小的epoll,返回一个epfd的描述符

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

修改某个文件描述符的状态

epfd 是创建的epoll

op 是修改的操作类型,可以是EPOLL_CTL_ADD 或者 EPOLL_CTL_DEL,代表添加和删除

fd 是要操作的文件描述符

event 是文件描述符fd上挂的一个context,是一个epoll_event结构体,下面是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 */
};

其中epoll_event.events 和 pollfd中的events 差不多,不过事件更加丰富,data是对于文件描述符上可以挂的卫星数据,也更加灵活。

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

epdf 是epoll_create时候返回的

events 是polling 返回的结果会赋值在events

maxevents 是一次通知用户最大的events数量,一般就是events的数组长度

timeout 是超时时间

相比select/poll,epoll_wait 只返回可读写的事件,而不是全部返回

关于几个size的理解

epoll_create 时候的size是指 epoll在核心态监控fd的最大数量

epoll_wait 时候的events 是指一次通知的数据,这个数量认为是一次批量,肯定是小于等于epoll_create时候的大小,比这个再大也没用了

epoll_wait 时候的maxevents 是为了防止一次通知events溢出的一个边界,如果设置的比events的数组长度小,那就相当于批量变小,比这个大会溢出,所以应该是相等就可以了

使用过程

// 创建
int pollingfd = epoll_create( 0xCAFE ); // 创建一个epoll_event 用来一会儿epoll_ctl的时候EPOLL_CTL_ADD用
struct epoll_event ev = { }; // 假设sock1是个网络连接
int sock1 = pConnection1->getSocket(); // 给这个sock1挂一点卫星数据,这里可以是任意的东西,我们就放个他的connection
ev.data.ptr = pConnection1; // 来监控sock1的可读事件
ev.events = EPOLLIN | EPOLLONESHOT; // 把设置好的epoll_event 添加到创建的epollfd
if ( epoll_ctl( epollfd, EPOLL_CTL_ADD, sock1, &ev ) != )
// 出错 // 创建一些epoll_event用来在用户态来接收
struct epoll_event pevents[ ]; // 等待可读事件
int ready = epoll_wait( pollingfd, pevents, , );
if ( ret == - )
// 出错
else if ( ret == )
// 超时
else
{
// ret是返回了多少个可以读写的事件
for ( int i = ; i < ret; i++ )
{
// 判断通知到用户这个到底是个什么事件
if ( pevents[i].events & EPOLLIN )
{
// 取到当初我们挂在上面的卫星数据
Connection * c = (Connection*) pevents[i].data.ptr;
// 对这个socket进行一些操作
c->handleReadEvent();
}
}
}

epoll的使用过程还是比select和poll复杂不少的,首先你得创建一个epoll,然后创建和设置epoll_event,再通过epoll_ctl添加到epoll,最后epoll_wait,遍历通知过来的events

比select和poll的改进

最大的改进就是不需要在遍历所有事件了,不需要FD_ISSET,也不需要遍历所有pollfd.revents,取而代之的是,内核帮我们把active的fd赋值到epoll_wait的events上

pollfd封装了一个event,而不是像select的fd_set只有一个fd属性,epoll_event 比pollfd又多了一个data的卫星数据,可以放任意的东西上去

select和poll一旦进入polling阶段,就没法对fd做修改了,但是epoll_ctl可以在任意的线程里在任意事件动态的添加,删除epoll_event

缺点

改变epoll中fd的监听事件类型需要epoll_ctl的系统调用,而在poll中只需要在用户态做BITMASK

只能在linux上用,虽然有libevent这种东西

epoll的api比select和poll复杂

该如何选择

如果连接数很低小于1024,epoll对比select和poll是没有性能提升的,选择select还是poll就看个人喜好了,一般select就行,比如fpm,epoll早就出了fpm也没改,PHP很少有人能worker开1000以上

如果连接是短连接,经常accept出一些fd添加到epoll中的系统调用开销需要考虑,具体性能还需要再综合考虑,比如nginx,虽然都是短连接,但是有高并发,几万并发select每次遍历一遍所有fd更耗

如果是长连接,并且都是idle的,例如一些聊天的服务器,一个连接,半天才说一句话,都是挂机的,但是连接几十万,那有个人说句话,服务区需要读,你遍历几十万个fd就不值了

如果你的应用是多线程来处理网络的,那么为了利用多线程还是使用epoll比较好,可以用多线程配合边缘触发(如果可读只通知一次,不管读完没读完,水平触发没读完就一直通知,所以效率会比边缘触发低一些),这也是边缘触发推荐的使用方式。

为什么epoll高效

简单来说是这样的select和poll当检测到fd就绪以后,就通知到用户态了,函数也就返回了。而epoll在add的时候就开始监听,发现他就绪以后就放到一个就绪表里,epoll_wait只是定时查看一下这个就绪表里的数据。

参考文章

https://cs.uwaterloo.ca/~brecht/papers/getpaper.php?file=ols-2004.pdf

http://www.ulduzsoft.com/2014/01/select-poll-epoll-practical-difference-for-system-architects/

http://www.unix.org/what_is_unix/history_timeline.html

http://en.wikipedia.org/wiki/Asynchronous_I/O

http://en.wikipedia.org/wiki/POSIX

http://blog.csdn.net/vividonly/article/details/7539342

select poll epoll相关知识速记的更多相关文章

  1. select, poll, epoll的实现分析

    select, poll, epoll都是Linux上的IO多路复用机制.知其然知其所以然,为了更好地理解其底层实现,这几天我阅读了这三个系统调用的源码. 以下源代码摘自Linux4.4.0内核. 预 ...

  2. 转--select/poll/epoll到底是什么一回事

    面试题:说说select/poll/epoll的区别. 这是面试后台开发时的高频面试题,属于网络编程和IO那一块的知识.Android里面的Handler消息处理机制的底层实现就用到了epoll. 为 ...

  3. Python之路-python(Queue队列、进程、Gevent协程、Select\Poll\Epoll异步IO与事件驱动)

    一.进程: 1.语法 2.进程间通讯 3.进程池 二.Gevent协程 三.Select\Poll\Epoll异步IO与事件驱动 一.进程: 1.语法 简单的启动线程语法 def run(name): ...

  4. Python自动化 【第十篇】:Python进阶-多进程/协程/事件驱动与Select\Poll\Epoll异步IO

    本节内容: 多进程 协程 事件驱动与Select\Poll\Epoll异步IO   1.  多进程 启动多个进程 进程中启进程 父进程与子进程 进程间通信 不同进程间内存是不共享的,要想实现两个进程间 ...

  5. select.poll,epoll的区别与应用

    先讲讲同步I/O的五大模型 阻塞式I/O, 非阻塞式I/O, I/O复用,信号驱动I/O(SIGIO),异步I/O模型 而select/poll/epoll属于I/O复用模型 select函数 该函数 ...

  6. select poll epoll三者之间的比较

    一.概述 说到Linux下的IO复用,系统提供了三个系统调用,分别是select poll epoll.那么这三者之间有什么不同呢,什么时候使用三个之间的其中一个呢? 下面,我将从系统调用原型来分析其 ...

  7. 笔记-select,poll,epoll

    笔记-select,poll,epoll 1.      I/O多路复用 I/O多路复用是指:通过一种机制或一个进程,可以监视多个文件描述符,一旦描述符就绪(写或读),能够通知程序进行相应的读写操作. ...

  8. Linux内核中网络数据包的接收-第二部分 select/poll/epoll

    和前面文章的第一部分一样,这些文字是为了帮别人或者自己理清思路的.而不是所谓的源代码分析.想分析源代码的,还是直接debug源代码最好,看不论什么文档以及书都是下策. 因此这类帮人理清思路的文章尽可能 ...

  9. Linux IO模式以及select poll epoll详解

    一 背景 同步IO和异步IO,阻塞IO和非阻塞IO分别是什么,到底有什么区别?不同的人在不同的上下文下给出的答案是不同的.所以先限定一下本文的上下文. 本文讨论的背景是Linux环境下的network ...

随机推荐

  1. python 可迭代对象,迭代器和生成器,lambda表达式

    分页查找 #5.随意写一个20行以上的文件(divmod) # 运行程序,先将内容读到内存中,用列表存储. # l = [] # 提示:一共有多少页 # 接收用户输入页码,每页5条,仅输出当页的内容 ...

  2. CSAPP阅读笔记-gcc常用参数初探-来自第三章3.2的笔记-P113

    gcc是一种C编译器,这次我们根据书上的代码尝试着使用它. 使用之前,先补充前置知识.编译器将源代码转换为可执行代码的流程:首先,预处理器对源代码进行处理,将#define指定的宏进行替换,将#inc ...

  3. svn server配置与TortoiseSVN、Ankhsvn+VS使用

    Svn服务器与客户端安装 1.      下载安装VisualSvn-Server服务端.(过程略)http://subversion.apache.org/packages.html 2.      ...

  4. 原生态hadoop2.6平台搭建

     hadoop2.6平台搭建 一.条件准备 软件条件: Ubuntu14.04 64位操作系统,jdk1.7 64位,Hadoop 2.6.0 硬件条件: 1台主节点机器,配置:cpu 8个,内存32 ...

  5. TOJ 1883 Domino Effect

    Description Did you know that you can use domino bones for other things besides playing Dominoes? Ta ...

  6. Android中的ListView点击时的背景颜色设置

    想设置listview中每行在点击.选中等不同状态下有不同的背景颜色,或者背景图片. 这可以用Android的Selector来实现.它可以定义组件在不同状态下的显示方式. 新建一个xml文件list ...

  7. web前端与后台数据访问的对象封装

    前言:通常情况下,在不使用angularJS/nodeJS/react等这类完整性的解决方案的js时,前端与后台的异步交互都是使用Ajax技术进行解决 一:作为java web开发工程师可能以下代码是 ...

  8. flask-SQLAlchemy的ORM

    1.创建表 import datetime from sqlalchemy import create_engine from sqlalchemy.ext.declarative import de ...

  9. git本地分支关联远程分支

    问题描述: 从远程master克隆下来以后, 在本地创建wf_dev分支, 此时执行git pull 操作出现图中问题. 这是因为:本地的wf_dev分支还没有和远程的wf_dev进行关联. 执行:  ...

  10. 【Linux】安装配置Tomcat7

    第一步:下载Tomcat安装包 下载地址:https://tomcat.apache.org/download-70.cgi [root@localhost ~]# wget http://mirro ...