select和poll和epoll
什么是I/O复用:使应用程序同时管理多个文件描述符,以提高程序的性能。
select
在一段指定时间内,监听用户感兴趣的文件描述符上的事件(可读、可写、异常)。
//select API:
#include <sys/select.h>
int select(int nfds,
fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
strucut timeval *timeout);
<1>nfds:指定被监听的文件描述符的总数,通常为select监听的所有文件描述符+1,因为文件描述符是从0开始计数的。
<2>readfds、writefds、exceptfds:分别指向可读、可写和异常事件对应的文件描述符的集合,调用select函数时,通过这三个参数传入 自己感兴趣的文件描述符。select调用返回时,内核将修改它们来通知应用程序哪些文件描述符已经就位。
#include <typesizes.h>
#define __FD_SETSIZE 1024 //fd_set能容纳的文件描述符的数量由它决定
#include <sys/select.h>
#define FD_SETSIZE __FD_SETSIZE
typedef long int __fd_mask
#undef __NFDBITS
#define __NFDBITS (8 * (int)sizeof( __fd_mask ) )
typedef struct
{
#ifdef__USE_XOPEN
__fd_mask fds_bits[__FD_SETSIZE/__NFDBITS];//数组
#define __FDS_BITS(set) ((set)->fds_bits)//设置
#else
__fd_mask __fds_bits[__FD_SETSIZE/__NFDBITS];//数组
#define __FDS_BITS(set) ((set)->__fds_bits)//设置
#endif
}fd_set;//fd_set结构体仅包含一个整形数组,该数组的每一位标记一个文件描述符
//使用宏来访问fd_set结构体中的位
#include <sys/select.h>
FD_ZERO(fd_set *fdset);//清除fdset的所有位
FD_SET(int fd, fd_set *fdset);//设置fdset的位fd
FD_CLR(int fd, fd_set *fdset);//清除fdset的位fd
int FD_ISSET(int fd, fd_set *fdset);//测试fdset的位fd是否被设置
<3>timeout:设置select函数的超时时间。它是一个timeval结构体类型的指针,采用指针可以使内核修改它以告诉应用程序select等待了多久。函数调用失败时,timeout的值是不确定的。
struct timeval
{
long tv_sec;//秒数
long tv_usec;//微妙数
};//为0立即返回;为NULL,select将一直阻塞直到某个文件描述符就绪。
<4>select调用成功,返回就绪的文件描述符总数。失败时返回-1并设置errno。如果在select等待期间,程序接收到信号,则select立即返回-1,并设置errno为EINTR。
文件描述符就绪条件
可读:
<1>socket内核接收缓冲区中的字节大于或者等于其低水位标记SO_RCVLOWAT。
<2>socket通信的对方关闭连接。
<3>监听socket上有新的连接请求。
<4>socket上有未处理的错误。使用getsckopt来读取和清除该错误。
可写:
<1>socket内核发送缓冲区中的可用字节大于或等于其低水位标记SO_SNDLOWAT。
<2>socket的写操作被关闭。对写操作被关闭的socket执行写操作将触发一个SIGPIPE信号。
<3>socket使用非阻塞connect连接成功或者失败之后。
<4>socket上有未处理的错误。使用getsckopt来读取和清除该错误。
异常:socket上收到带外数据。
带外数据:当socket上接收到普通数据和带外数据都将使select返回,但socket处于不同的就绪状态,前者处于可读状态,后者处于异常状态。
//对于可读事件,采用普通的recv函数读取数据
if(FD_ISSET(connfd, &read_fds))
{
ret = recv(connfd, buffer, sizeof(buffer)-1, 0);
}
//对于异常事件,采用带MSG_OOB标志的recv函数处理
if(FD_ISSET(connfd, &exception_fds))
{
ret = recv(connfd,buffer, sizeof(buffer)-1, MSG_OOB);
}
poll
在指定的时间内轮询一定数量的文件描述符,以测试其是否就绪。
//pool API:
#include <poll.h>
int poll(struct poolfd *fds, nfds_t nfds, int timeout);
<1>fds:是一个pollfd结构体类型的数组,它指定所有我们感兴趣的文件描述符上发生的可读、可写、和异常事件。
struct poolfd
{
int fd;//文件描述符
short events;//注册的事件,即poll监听fd上的哪些事件,它是一系列事件的按位或
short revents;//实际发生的事件,由内核填充,通知应用程序fd上实际发生了哪些事件
};
<2>nfds:指定被监听事件集合fds的大小。
typedef unsigned long int nfds_t;
<3>timeout:指定poll的超时时间,单位是毫秒。为-1时,poll永远阻塞,直到某个时间发生。为0时,立即返回。
epoll
epoll是linux特有的I/O复用函数。epoll使用一组函数来完成任务。epoll把用户关心的文件描述符上的事件放在内核里的一个事件表中,从而无需像select和poll那样每次调用都要重复传入文件描述符集或事件集。但是epoll需要使用一个额外的文件描述符,来唯一标识内核中的这个事件表(Linux下一切皆文件)。
//创建
#include <sys/epoll.h>
int epoll_create(int size);
//size只是给内核一个提示,告诉它事件表需要多大
//返回的文件描述符将作为其他所有epoll系统调用的第一个参数,指定访问内核事件表
//操作epoll的内核事件表
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);//成功返回0
<1>epfd是需要操作的文件描述符。
<2>op指定操作类型。
//EPOLL_CTL_ADD,往事件表中注册事件
//EPOLL_CTL_MOD,修改fd上注册的事件
//EPOLL_CTL_DEL,删除fd上的注册事件
<3>event指定事件。
struct epoll_event
{
_unit32_t events;//epoll事件,描述事件类型,和poll的宏相同只需添加E POLLIN-->EPOLLIN
//epoll的额外事件类型:EPOLLET和EPOLLONESHOT
epoll_data_t data;//存储用户数据
typedef union epoll_data
{
void *ptr;//指定和fd相关的用户数据
int fd;//文件描述符
unit32_t u32;
unit64_t u64;
}epoll_data_t;
};
//在一段超时时间内等待一组文件描述符上的事件
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
<1>epoll_create()返回的值。
<2>epoll_wait如果检测到就绪事件,就将所有的就绪事件从内核事件表复制到events指定的数组中。这个数组只用于输出epoll_wait检测到的就绪事件,而不像select和poll那样既用于传入用户注册的事件,又用于输出epoll_wait检测到的就绪事件。这就极大提高了应用程序检索就绪文件描述符的效率。
//索引poll返回的就绪文件描述符
int ret = poll(fds, MAX_EVENT_NUMBER, -1);
for(int i = 0; i < MAX_EVENT_NUMBER; ++i)//MAX_EVENT_NUMBER
{
if(fds[i].revents & POLLIN)
{
int sockfd = fds[i].fd;
//处理sockfd
}
}
//索引epoll返回的就绪文件描述符
int ret = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);
for(int i = 0; i < ret; ++i)//ret
{
int sockfd = events[i].data.fd;
//处理sockfd
}
<3>maxevents指定最多监听多少个事件,必须大于0。
<4>timeout和poll的相同。
<5>成功时返回就绪的文件描述符的个数。
LT和ET模式
LT模式:默认工作模式,这种模式下相当于一个高效的poll。
采用LT模式的文件描述符,当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序可以不立即处理该事件,这样,当应用程序下一次调用 epoll_wait时,epoll_wait还会再次向应用程序告知此事件,直到该事件被处理。
ET模式:当往epoll内核事件表中注册一个文件描述符上的EPOLLET事件时,epoll将以ET模式来操作该文件描述符,ET模式是epoll的高效工作模式。
采用ET模式的文件描述符,当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序必须立即处理该事件,因为后续的epoll_wait调用将不再向应用程序通知这一事件。ET模式在很大程度上降低了同一个epoll事件被重复触发的次数。因此效率较高。
EPOLLONESHOT事件
即使我们使用ET模式,一个socket上的某个事件还是可能会被触发多次,这在并发的程序中就会引起一个问题。如:一个线程在读取完某个socket上的数据后开始处理这些数据,而在数据的处理过程中该socket上又有新数据可读(EPOLLIN再次被触发),此时另外一个线程被唤醒来读取这些新的数据。于是就出现了两个线程同时操作一个socket的局面。我们期望一个socket连接在任一时刻都只能被一个线程处理。可用epoll的EPOLLONESHOT事件实现。对于注册了EPOLLONESHOT事件的文件描述符,操作系统最多触发其上的一个可读、可写或者异常事件。
select、poll和epoll的比较
这三组I/O复用系统调用都能同时监听多个文件描述符。它们将等待由timeout参数指定的超时时间,直到一个或多个文件描述符上有事件发生返回。
<1>select的 fd_set 结构体没有将文件描述符和事件绑定,仅仅是一个文件描述符的集合,因此需要三个参数来分别传入可读、可写和异常事件,这使得select不能处理更多类型的事件。由于内核对fd_set的在线修改,应用程序下次调用select前,必须重置这个3个fd_set集合。
<2>poll的pollfd把文件描述符和事件定义在一起,任何事件都被统一的处理,从而使编程接口变得更加简洁。内核每次修改的是结构体内的revents成员,而events成员保持不变,因此下次调用无需重置结构体的事件集参数。
<3>epoll则采用与select和poll完全不同的方式来管理用户注册的事件。它在内核维护一个事件表,并提供了一个独立的系统调用epoll_ctl来控制往其中添加、删除和修改事件。这样,每次epol_wait调用都直接从该内核事件表中取得用户注册的时间,而无需反复从用户空间读入这些事件。而且epoll_wait系统调用直接返回就绪的事件,这使得应用程序索引就绪文件描述符的时间复杂度为O(1)。而select和poll调用都返回整个用户注册的事件集合,所以应用程序索引就绪文件描述符的时间复杂度为O(n)。
<4>select和poll都采用的是轮询的方式,每次调用都要扫描整个注册文件描述符集合,将其中的文件描述符返回给用户程序,因此他们检测就绪事件的算法的时间复杂度都为O(n)。而epoll_wait采用回调的方式,内核检测到就绪的文件描述符时,将触发回调函数,回调函数就将该文件描述符上对应的事件插入内核就绪事件队列。内核最后在适当的时机将该就绪事件队列中的内容拷贝到用户空间。
<5>epoll和poll分别用nfds和maxevents参数指定最多监听的文件描述符和事件,这两个数目都为65535(cat/proc/sys/fs/file-max)。select允许监听的文件描述符数量通常有限制,用户可以修改。
<6>select和poll都只能工作在相对低效的LT模式,而epoll则可以采用ET高效模式,而且还支持EPOLLONESHOT事件,该事件能进一步减少可读、可写和异常事件被触发的次数。
上一篇: 有没有可能在 PHP 中实现多线程?
下一篇: jQuery基础知识点
推荐阅读
-
insert into select和select into的使用和区别介绍
-
使用iframe作为日历的载体,不再被select和flash等控件挡住的日期输入框
-
Vue中Table组件Select的勾选和取消勾选事件详解
-
mssql insert into 和insert into select性能比较
-
sql 语句插入结果为select和值混合示例
-
select into 和 insert into select 两种表复制语句
-
10.Go-goroutine,waitgroup,互斥锁,channel和select
-
sql server中Select count(*)和Count(1)的区别和执行方式
-
Select count(*)、Count(1)和Count(列)的区别及执行方式
-
jquery获取select,option所有的value和text的实例