select函数/poll函数/epoll函数

内容总结自卷一及网络博客。

1. select()简介

#include<sys/select.h>
#include<sys/time.h>
int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);

返回值:错误时返回-1,超时返回0,有描述符就绪返回就绪的描述符个数;

入参:

①maxfdp1:待测试的最大描述符+1,例如我们关心的描述符为1,5,7,那么maxfdp1的值就为8;

typedefstruct
{
#ifdef__USE_XOPEN
    _fd_maskfds_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_SETSIZE一般为1024,__ZFDBITS为32,也就是数组有128个元素,每个描述符占用一个bit,所以一般描述符总数应该为(1024/32)*32=1024(0~1023);

fd_set *readset, fd_set *writeset, fd_set *exceptset

分别为读、写和异常条件的描述符,;

描述符集用以下三个宏进行操作:

void FD_ZERO(fd_set* fdset);
void FD_SET(int fd, fd_set *fdset);
void FD_CLR(int fd, fd_set *fdset);
int  FD_ISSET(int fd, fd_set *fdset);
如果我们队三种描述符的某个不关心,可以将其设置为NULL;

这三个参数都是值-结果型参数,也就是说select会修改这三个参数,修改为已就绪的描述符;所以每次调用select前,必须重新设置这三个描述符集;


const struct timeval *timeout
这个参数表示超时时间,也就是等待有描述符就绪的最长时间;

struct timeval
{
    long tv_sec;<span style="white-space:pre">	</span>/* 秒 */
    long tv_usec;<span style="white-space:pre">	</span><span style="font-family:Arial, Helvetica, sans-serif;">/* 毫秒 */</span>
};
所以如果将②中的三个描述符集全置为NULL时,select()就是一个比sleep(以s为单位)精度更高的定时器;
如果timeout为NULL,表示等待直到有描述符准备好;

如果timeout结构中的时间均设为0,表示检查描述符集后立即返回;

2. 描述符就绪条件

2.1 满足以下四个条件之一表示一个套接字准备好读

1)该套接字接收缓冲区中的数据字节数大于等于套接字接收缓冲区低水位标记时;

可以使用SO_RCVLOWAT套接字选项设置该套接字的低水位标记;

2)该连接的读半部关闭,也就是收到了FIN报文;

收到了FIN表示对端已经不会再往该连接写数据了,所以此时本端调用读会立即返回0(EOF);

3)该套接字时一个监听套接字,并且已完成的连接数不为0(即so_q队列不为空);

对此套接字调用accept()一般不会阻塞;

4)其上有一个错误待处理;


2.2 满足以下四个条件之一表示一个套接字准备好写

1)该套接字上的发送缓冲区中的可用空间字节数大于等于套接字发送缓冲区低水位标记时,并且该套接字已连接或者不需要连接(UDP);

可以使用SO_SNDLOWAT套接字选项设置该套接字的低水位标记;

2)该连接的写半部以关闭;

3)使用非阻塞式connect的套接字已建立连接,或者connect已经以失败告终;

4)其上有一个错误待处理;


当某个套接字发生错误时,将被select标记为既可读又可写;也就是返回的readset和writeset都会置该套接字对应位;


3. poll

#include<poll.h>
struct pollfd
{
	int fd;
	short events;
	short revents;
};
int poll(struct pollfd *fdarray, unsigned long nfds, int timeout);

参数描述:

(1)fdarray:结构体数组第一个元素的指针,struct pollfd结构中,fd指定的是描述符,events表示这个描述符上关心的事件,revents表示这个描述符上发生的事件;

如果不关心某个fd时,可以把数组中该fd对应元素的fd值置为负值;

(2)nfds:结构体数组的元素个数;

(3)timeout:poll函数返回前的等待时间;

timeout值

说明

INFTIM(一般为负值)

永远等待,直到有事件发生

0

立即返回,不阻塞进程

>0

等待的毫秒数


返回值:
出错时返回-1;
定时器到时,返回就绪描述符的个数;


要测试的条件由events指定,函数在相应的revents成员中返回该描述符的状态;

类型

是否可以作为events的输入

是否可以作为revents的输入

含义

处理输入

POLLIN

(POLLRDNORM

| POLLRDBAND)

Y

Y

普通或优先级带数据可读

POLLRDNORM

Y

Y

普通数据可读

POLLRDBAND

Y

Y

优先级带数据可读

POLLRDPRI

Y

Y

高优先级数据可读

处理输出

POLLOUT

(POLLWRNORM)

Y

Y

普通数据可写

POLLWRNORM

Y

Y

普通数据可写

POLLWRPRI

Y

Y

优先级带数据可写

处理错误

POLLERR

 

Y

发生错误

POLLHUP

 

Y

发生挂起

POLLNVAL

 

Y

描述符不是一个打开的文件

 

4. epoll

4.1 epoll的系统调用包括三个:

(1) epoll_create()

int epoll_create(int size)
该操作返回一个epoll句柄,size是这个epoll fd上能监听的最大socket fd数;

epoll句柄占用一个fd,所以使用完后需要调用close()关闭;

(2) epoll_ctl()

int epoll_ctl(int epoll_fd, int op, int fd, struct epoll_event* event)
epoll的事件注册函数;

参数:

a) epoll_fd:epoll_create的返回值,epoll句柄;

b) op:动作;

说明

EPOLL_CTL_ADD

注册新的FD到EPOLLFD中

EPOLL_CTL_MOD

修改已经注册的FD的监听事件

EPOLL_CTL_DEL

从EPOLLFD中删除一个FD


c) fd:要监听的FD;

d) 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_data_t是一个联合,大小为8字节;ptr和fd当然是只有一个有效,ptr所值得内容由使用者自己控制,如果只需要设置fd,使用联合中的fd即可,如果还有其他数据,可以使用ptr,然后定义自己的结构,让ptr指向它即可;

events的取值如下:

说明

EPOLLIN

可读

EPOLLOUT

可写

EPOLLPRI

有紧急数据可读

EPOLLERR

有错误

EPOLLHUP

挂起

EPOLLET

边缘触发模式

EPOLLONESHOT

只监听一次事件,需要再次监听时,需要将socket fd重新加入EPOLL队列中


(3) epoll_wait()

int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout)
参数:

a) epfd:epoll的描述符;

b) events:内核把发生的事件复制到该数组中,用户自己负责内存的分配;

c) maxevents:告知内核events的大小,maxevents的值不能大于epoll_create的参数size;
也即events数组的大小;

返回值:

已就绪的描述符个数;


(4) timeout

超时时间,单位毫秒;值及含义同poll;

注册在epfd的socket fd的事件发生时,则将对应的socket fd和事件放到events数组中。

通知注册在该epfd的socket fd的事件会被清除,所以需要调用epoll_ctl再次设置关心的事件,再次设置时使用的操作是EPOLL_CTL_MOD,而不是EPOLL_CTL_ADD,因为这个socket fd已经注册过了,只是关心的事件被清除了;

4.2 epoll的两种工作方式

LT和ET两种方式简述:

(1)LT模式

水平触发,是epoll缺省的工作方式;可同时支持阻塞和非阻塞的socket;

该模式下,如果一个socket fd就绪后,内核通知该fd就绪,如果你不对该fd进行操作,则内核会继续通知,也就不会丢失事件;

(2)ET模式

边缘触发,高速的工作方式,只支持非阻塞的socket;

该模式下,内核只会在描述符由非就绪到就绪时通知,后续不会有更多的通知;

4.3 epoll的优点

1. 支持一个进程打开大数目的socket描述符

select 最不能忍受的是一个进程所打开的FD是有一定限制的,由FD_SETSIZE设置,默认值是1024。

不过 epoll则没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右,具体数目可以cat

 /proc/sys/fs/file-max查看,一般来说这个数目和系统内存关系很大。


2. IO效率不随FD数目增加而线性下降

传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合,不过由于网络延时,任一时间只有部分的socket是“活跃”的,但是select/poll每次调用都会线性扫描全部

的集合,导致效率呈现线性下降。

但是epoll不存在这个问题,它只会对“活跃”的socket进行操作---这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么,只有“活跃”的socket才会主动的去

调用 callback函数,其他idle状态socket则不会,在这点上,epoll实现了一个“伪”AIO,因为这时候推动力在os内核。在一些 benchmark中,如果所有的socket基本上都是活跃

的---比如一个高速LAN环境,epoll并不比select/poll有什么效率,相反,如果过多使用epoll_ctl,效率相比还有稍微的下降。但是一旦使用idle connections模拟WAN环境,epoll

的效率就远在select/poll之上了。


3. 使用mmap加速内核与用户空间的消息传递

无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就很重要,在这点上,epoll是通过内核与用户空间mmap同一块内存实现的。

























  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值