关于 select、poll、epoll 的区别

1. 函数说明

1.1 select

/* According to POSIX.1-2001 */
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

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

void FD_ZERO(fd_set *set);          //清空集合
void FD_CLR(int fd, fd_set *set);   //将一个给定的文件描述符从集合中删除
void FD_SET(int fd, fd_set *set);   //将一个给定的文件描述符加入集合之中
int  FD_ISSET(int fd, fd_set *set); //检查集合中指定的文件描述符是否可以读写 

struct timeval {
   long    tv_sec;         /* seconds 秒*/
   long    tv_usec;        /* microseconds 微妙*/
};

该函数准许进程指示内核等待多个事件中的任何一个发生,并在有一个或多个事件发生或经历一段指定的时间后才被唤醒。

  • 第一个参数 nfds 指定待测试的描述符个数,其值为最大的文件描述符加1,描述符0、1、2...nfds-1均将被测试(因为文件描述符是从0开始的)。
  • 中间的三个参数 readset、writeset、exceptset 指定我们要让内核测试读、写、异常条件的描述符。如果对某一个的条件不感兴趣,就可以把它设为 NULL。struct fd_set 可以理解为一个集合(实际上是一long类型的数组),这个集合中存放的是文件描述符,可通过上面那四个函数进行设置。
  • timeout 告知内核等待所指定描述符中的任何一个就绪最多等待时长。没有等到就绪就会超时。该参数控制三种可能:
    ① 传 NULL 时,一直阻塞直到有感兴趣的描述符上有IO事件就绪。
    ② timeval结构中指定的秒数和微秒数都是0,检查描述符后立即返回,不阻塞。
    ③ timeval结构中指定的秒数和微秒数指定了时长,在指定时长超时之前有 IO事件就绪返回,或者超时返回。

1.2 poll

#include <poll.h>

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

struct pollfd {
    int   fd;         /* file descriptor 文件描述符 */
    short events;     /* requested events 等待的事件 */
    short revents;    /* returned events 实际发生了的事件 */
};

每一个 struct pollfd 结构体指定了一个被监视的文件描述符,可以传递多个结构体,让 poll() 监视多个文件描述符。每个结构体的 events 域是监视该文件描述符的事件掩码,由用户来设置这个域。revents 域是文件描述符的操作结果事件掩码,内核在调用返回时设置这个域。events 域中请求的任何事件都可能在 revents 域中返回。

合法的事件如下:

常量 说明
POLLIN 普通或优先级带数据可读
POLLRDNORM 普通数据可读
POLLRDBAND 优先级带数据可读
POLLPRI 高优先级数据可读
POLLOUT 普通数据可写
POLLWRNORM 普通数据可写
POLLWRBAND 优先级带数据可写
POLLERR 发生错误
POLLHUP 发生挂起
POLLNVAL 描述字不是一个打开的文件

POLLIN | POLLPRI 等价于 select() 的读事件
POLLOUT 等价于 select() 的写事件

① timeout 参数指定等待的毫秒数,在超时之前有 IO 事件就绪或者超时,poll 都会返回
② timeout 为负数值,表示一直阻塞直到一个指定事件发生,poll 才返回。
③ timeout 为0,poll 调用立即返回并列出准备好I/O的文件描述符,但并不等待其它的事件。

返回值和错误代码
成功时:poll() 返回结构体中 revents 域不为0的文件描述符个数。
超时后:如果在超时前没有任何事件发生,poll()返回0。
失败时:poll() 返回-1,并设置errno为下列值之一

EFAULT    指针指向的地址超出进程的地址空间。
EINTR    请求的事件之前产生一个信号,调用可以重新发起。
EINVAL    参数超出 PLIMIT_NOFILE 值。
ENOMEM   可用内存不足,无法完成请求。

1.3 epoll

#include <sys/epoll.h>

int epoll_create(int size);
int epoll_create1(int flags);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

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 */
};

(1)创建 epoll 实例
现在一般使用 epoll_create1(EPOLL_CLOEXEC),原因:

  • 在linux 内核版本大于2.6.8 后,epoll_create(int size) 这个 size 参数就被弃用了,但是传入的值必须大于0。最初实现版本时, size参数的作用告诉内核需要使用多少个文件描述符。内核会使用 size 的大小去申请对应的内存。现在这个size参数不再使用了,内核会动态的申请需要的内存。
  • 使用 epoll_create1() 的优点是它允许你指定标志,指定 EPOLL_CLOEXEC 标记在执行另一个进程时文件描述符会自动关闭。

(2)epoll 的事件注册
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
先注册要监听的事件类型。
第一个参数是 epoll_create1 的返回值
第二个参数表示动作,用三个宏来表示:

EPOLL_CTL_ADD:注册新的 fd 到 epfd 中
EPOLL_CTL_MOD:修改已经注册的 fd 的监听事件
EPOLL_CTL_DEL :从 epfd 中删除一个 fd

第三个参数是需要监听的 fd
第四个参数是告诉内核需要监听什么事,struct epoll_event 结构见上面代码
events 可以是以下几个宏的集合:

常量 说明
EPOLLIN 表示对应的文件描述符可以读(包括对端SOCKET正常关闭)
EPOLLOUT 表示对应的文件描述符可以写
EPOLLPRI 表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)
EPOLLERR 表示对应的文件描述符发生错误
EPOLLHUP 表示对应的文件描述符被挂断
EPOLLET 将 EPOLL 设为边缘触发 (Edge Triggered) 模式,这是相对于水平触发 (Level Triggered) 来说的,LT是缺省的工作方式
EPOLLONESHOT 只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个 socket 加入到 EPOLL 队列里

LT (Level Triggered) 水平触发:默认的模式。内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的 fd 进行 IO 操作。如果你不作任何操作,内核还是会继续通知你。

ET (Edge Triggered) 边缘触发:“高速”模式。内核只会提示一次,直到下次再有数据流入之前都不会再提示了,无论 fd 中是否还有数据可读。在ET模式下,read一个fd的时候一定要把数据读完。

(3)等待事件的产生
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
等待事件的产生,类似于 select() 调用。
参数 events 用来从内核得到事件的集合
参数 maxevents 告之内核这个 events 有多大
参数 timeout 是超时时间(毫秒,0会立即返回,-1一直阻塞直到有IO事件就绪)
该函数返回需要处理的事件数目,如返回0表示已超时。

2. 三者之间的关联和区别

关联

select,poll,epoll 都是 I/O 多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。
select,poll,epoll 本质上都是同步 I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的。而异步 I/O 则无需自己负责进行读写,异步 I/O 的实现会负责把数据从内核拷贝到用户空间。

区别

关于最大连接数先理清楚2个概念:
⑴ 一个进程能打开的最大文件描述符,使用 ulimit -n 或者 cat /proc/进程号/limits |grep "Max open file"
❶ 使用 ulimit -n xxx 修改(只针对当前session有效),
❷ 通过 setrlimit 系统调用修改(只对当前进程有效),
❸ 修改 /etc/security/limits.conf 在该文件中添加以下两行:

    *      soft    nofile     100000
    *      hard    nofile     100000

这个值是可以修改的,但是最大不要超过系统所能打开的最大数,超过了也没有什么意义。

⑵ 一个系统所能打开的最大数也是有限的,跟内存大小有关,可以通过cat /proc/sys/fs/file-max 查看。

  • 注意:对于服务器程序来说并不是“理论”最大只能打开65535个 socket 连接。65535 只是linux系统的最大可用socket端口,比如在一台服务器(这里指主机而非服务程序)上“理论”上最多只能发起65535个客户端连接(实际要小于)这是对的,因为每发起一个连接都需要一个端口。服务器程序只需监听几个端口就够了,它的上限是系统所能打开的最大文件描述符的数量。不要搞混了这两个概念。
> 支持一个进程所能打开的最大连接数
  • select 最大连接数有一定限制的,由 FD_SETSIZE 值决定的,默认值是2048。可以对进行修改,需要重新编译内核,但是性能可能会受到影响。
  • poll 最大连接数上限是系统能最大可以打开文件的数目,一般来说这个数目受限于系统内存,1GB内存的机器上大约是10万左右,具体数目可以 cat /proc/sys/fs/file-max。
  • epoll 与 poll 一样,最大连接数上限是系统能最大可以打开文件的数目。
> 连接数剧增后带来的 IO 效率问题
  • select 每次调用都对所有的连接进行线性遍历,所以随着连接数的增加会造成遍历速度的线性下降的性能问题。
  • poll 与 select 有相同的问题
  • epoll 是事件驱动的,内核中的实现是根据每个连接 fd 上的 callback 函数来实现的,只有活跃的 socket 才会主动调用 callback,所以在活跃 socket 较少的情况下,使用 epoll 没有前面两者的线性下降的性能问题,但是所有 socket 都很活跃的情况下,可能也会有性能问题。

epoll 如何实现只处理活跃连接
epoll实现了eventpoll数据结构
数据结构中rdlist将活跃连接存储在链表中,当网卡发送报文时,增加节点,当读取一个事件后,链表删除节点,需要得到活跃连接就只需要遍历链表
数据结构中rdr使用红黑树(自平衡二叉树)将事件存储,例如:当有读事件时,就新增节点,事件复杂度为logN

一般来说编写高并发服务器程序都会首先 epoll 因为这种环境下其性能最好,但是在连接数少并且连接都十分活跃的情况下,select 和 poll 的性能可能比 epoll 好,毕竟 epoll 的通知机制需要很多函数回调。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容