select poll epoll相关知识速记

缘起

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

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 = 10;
tv.tv_usec = 0;
 
// 调用select 并阻塞在这里等待 IO事件
int ret = select( largest_sock, &fd_in, &fd_out, NULL, &tv );
 
// 检查返回值的状态
if ( ret == -1 )
    // 异常情况
else if ( ret == 0 )
    // 超时或者没有可以监控的fd
else
{
    // 检测每个IO事件是否可以读写
    if ( FD_ISSET( sock1, &fd_in ) )
        // IO可读
 
    if ( FD_ISSET( sock2, &fd_out ) )
        // IO可写
}
View Code

可以看到使用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[2];
 
// 设置pollfd的fd和要监控的事件,sock1监控读,sock2监控写
fds[0].fd = sock1;
fds[0].events = POLLIN;
fds[1].fd = sock2;
fds[1].events = POLLOUT;
 
// 10秒超时,开始等待sock1和sock2上的事件
int ret = poll( &fds, 2, 10000 );
// 有事件返回
if ( ret == -1 )
    // 出错了
else if ( ret == 0 )
    // 超时
else
{
    // 对每个pollfd检测是否有就绪的事件
    if ( pfd[0].revents & POLLIN )
        pfd[0].revents = 0;
        // 可读

    if ( pfd[1].revents & POLLOUT )
        pfd[1].revents = 0;
        // 可写
}
View Code

与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 = { 0 };

// 假设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 ) != 0 )
    // 出错

// 创建一些epoll_event用来在用户态来接收
struct epoll_event pevents[ 20 ];

// 等待可读事件
int ready = epoll_wait( pollingfd, pevents, 20, 10000 );
if ( ret == -1 )
    // 出错
else if ( ret == 0 )
    // 超时
else
{
    // ret是返回了多少个可以读写的事件
    for ( int i = 0; i < ret; i++ )
    {
        // 判断通知到用户这个到底是个什么事件
        if ( pevents[i].events & EPOLLIN )
        {
            // 取到当初我们挂在上面的卫星数据
            Connection * c = (Connection*) pevents[i].data.ptr;
            // 对这个socket进行一些操作
            c->handleReadEvent();
         }
    }
}
View Code

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

原文地址:https://www.cnblogs.com/23lalala/p/4288718.html