select
1 select函数
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, //关心的文件描述符的最大值+1
fd_set *readfds, //关心的读事件的文件描述符集
fd_set *writefds, //关心的写事件的文件描述符集
fd_set *exceptfds, //关心的错误事件的文件描述符集
struct timeval *timeout); //最长等待的时间 NULL:以阻塞的方式等待(即一直等到事件发生才返回)
// 0: 以非阻塞方式等待(即事件未发生,立即返回)
// 特定时间值:在该时间内事件为发生,select超时返回
//返回值:>0 : 等待的文件描述符中有几个文件描述符就绪
// ==0: 已经超过timeout的时间
// <0: 出错
2 认识fd_set
fd_set是文件描述符集,在系统中用一个位图表示。对于特定的操作系统,系统的文件描述符是有上限的,所以位图的下标表示的是哪个文件描述符,该位的内容表示的是你是否关心该文件描述符上的事件(为1:关心,为0:不关心)
虽然fd_set是位图,但是我们不能随意的进行操作,必须借助系统给定的相关接口:
void FD_CLR(int fd, fd_set *set); //将fd_set上的fd位设置为0
int FD_ISSET(int fd, fd_set *set); //判断fd是否被设置
void FD_SET(int fd, fd_set *set); //将fd_set上的fd位设置为1
void FD_ZERO(fd_set *set); //将整个fd_set内容清除
对于select函数中的readfds/writefds/exceptfds三个参数,既是输入也是输出参数
以readfds为例:
作为输入参数时表示:关心哪个文件描述符上的读事件;
作为输出型参数时表示:你关心的哪个文件描述符上的读事件已经就绪。
同样,timeout也是输入输出型参数,作为输入参数时表示:最长要等待的时间为多长,作为输出参数时表示:剩余的timeout时间
3 select缺点
(1)select所等待的文件描述符有上限(与系统的fd_set有关);
(2)在使用时,每次调用都需要手动设置fd集合,不方便;
(3)在使用时,每次调用都需要遍历fd集合,开销大;(在每次检测关心事件是否发生时就采用遍历的方法)
(4)在使用时,每次调用都需要将fd集合从用户态拷贝至内核态,开销大。(fd_set是我们程序猿自己设置的,而操作系统不相信任何用户,所以fd集合是在我们自己的缓冲区内,操作系统在帮我们完成等待时需要将等待的事件集合fd拷贝到内核)
poll
1 poll函数
#include <poll.h>
int poll(struct pollfd *fds, //poll的结构体数组
nfds_t nfds, //数组的长度
int timeout); //超时时间,-1:表示永久阻塞
//返回值: >0: 等待的文件描述符中有几个已经就绪
// ==0: 超过timeout时间
// <0: 发生错误
2 认识struct pollfd
struct pollfd {
int fd; /* file descriptor 文件描述符*/
short events; /* requested events 关心该文件描述符上的什么事件*/
short revents; /* returned events 该文件描述符的什么事件已经就绪*/
};
常用事件:
POLLIN :读事件
POLLOUT :写事件
POLLERR :错误事件
3 poll优缺点
优点:
(1)对比select,poll将关心的文件描述符上事件与就绪的事件分离,避免了每次调用都要设置集合的问题;
(2)处理的文件描述符的数量没有上限。在poll中文件描述符的数量与你自己设置的数组大小有关,就是说你设置多大的数组,poll就能够给你处理多少文件描述符。(当然对于数组的大小还可以进行动态开辟)
缺点:
(1)在每次调用poll的时候,还是需要遍历整个结构体数组,看那个文件描述符上的事件已经就绪了,开销大;
(2)在每次调用的时候,仍然需要将大量的pollfd结构体从用户拷贝至内核;
(3)随着监视的文件描述符数量的增多,poll的效率会下降。
epoll
1 epoll模型
创建epoll模型分为三步:建立回调机制 ,建立红黑树 ,建立等待队列
(1)底层的回调机制:
(2)红黑树,红黑树中存放的是关心的文件描述符上的事件
(3)等待队列,等待队列中存放已经就绪的事件
epoll的工作原理:创建epoll句柄,建立回调机制,创建红黑树向红黑树中注册关心的文件描述符及其事件,当有事件就绪时,将该文件描述符的该事件添加到就绪队列中,这样每次获取就绪事件就不用和select和poll一样轮询检查,而是直接在就绪队列中取值就可以(时间复杂度为:O(1))
2 epoll相关接口
(1)epoll_create:创建epoll句柄
#include <sys/epoll.h>
int epoll_create(int size);
(2)epoll_ctl :注册关心事件
epoll_ctl的工作相当于创建epoll模型的第二步,建立红黑树并向红黑树中注册关心的文件描述符及其事件。
#include <sys/epoll.h>
int epoll_ctl(int epfd, //epoll句柄,epoll_create的返回值
int op, //选项(添加、修改、删除)
int fd, //文件描述符
struct epoll_event *event); //关心的文件描述符上的事件
epoll_ctl的第二个参数op:
EPOLL_CTL_ADD (注册新的文件描述符到epfd中)
Register the target file descriptor fd on the epoll instance referred to by
the file descriptor epfd and associate the event event with the internal
file linked to fd.
EPOLL_CTL_MOD (修改关心的事件)
Change the event event associated with the target file descriptor fd.
EPOLL_CTL_DEL (删除关心的改事件)
Remove (deregister) the target file descriptor fd from the epoll instance
referred to by epfd. The event is ignored and can be NULL (but see BUGS
below).
epoll_ctl的第四个参数event:
struct epoll_event {
__uint32_t events; /* Epoll events 事件*/
epoll_data_t data; /* User data variable */
};
The events member is a bit set composed using the following available event types:
EPOLLIN (读事件)
The associated file is available for read(2) operations.
EPOLLOUT (写事件)
The associated file is available for write(2) operations.
EPOLLERR (错误事件)
Error condition happened on the associated file descriptor. epoll_wait(2)
will always wait for this event; it is not necessary to set it in events.
(3)epoll_wait :获取已就绪事件
#include <sys/epoll.h>
int epoll_wait(int epfd, //epoll句柄(epoll_create的返回值)
struct epoll_event *events, //输出型参数,已经就绪的事件
int maxevents, //输入型参数,存放就绪事件缓冲区的大小
int timeout); //timeout超时时间,与poll一致
//返回值: >0 :就绪事件的文件描述符
// ==0 :超时,超过timeout时间
// <0 :出错
4 epoll的优点
(1)可以处理的文件描述符没有上限;(epoll中关心的事件是向红黑树中注册的)
(2)基于事件的就绪通知方式;(关心的文件描述符一旦就绪,底层会通过回调机制通知操作系统)
(3)就绪队列。(在查看已就绪事件时不再需要以轮询的方式,而是直接从就绪队列中获取即可,时间复杂度为O(1))
对比select、poll、epoll的优缺点
有一点要说明:
在CPU能处理的过来的情况下,多路转接是用户越多,性能越好,
但是对于select和poll而言,连接用户的数量增多会导致性能的下降;
但是对于epoll而言,并不会,只有当用户多到CPU已经处理不过来的情况下,epoll的就绪队列的长度会变长,当然性能也就会下降。