1.select函数
函数定义
int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout);
优点:
用户可以在一个线程内同时处理多个 socket 的 IO 请求。用户可以注册多个 socket,然后调用 select 函数读取被激活的 socket,从而实现在同一个线程内同时处理多个 IO 请求,在这点上select 函数与同步阻塞模型不同,因为在同步阻塞模型中需要通过多线程才能达到这个目的。
缺点:
-
每次调用 select 都需要将进程加入到所有监视 fd 的等待队列,每次唤醒都需要从每个队列中移除。 这里涉及了两次遍历,而且每次都要将整个 fd_set 列表传递给内核,有一定的开销。
-
当函数返回时,系统会将就绪描述符写入 fd_set 中,并将其拷贝到用户空间。进程被唤醒后,用户线程并不知道哪些 fd 收到数据,还需要遍历一次。
-
受 fd_set 的大小限制,32 位系统最多能监听 1024 个 fd,64 位最多监听 2048 个。
2.poll函数
函数定义
int poll(struct pollfd *fds, nfds_t nfds, int timeout); /* struct pollfd{int fd; // 感兴趣fdshort events; // 监听事件short revents; // 就绪事件 }; */ // return:表示此时有多少个监控的描述符就绪,若超时则为0,出错为-1。
poll 函数与 select 原理相似,都需要来回拷贝全部监听的文件描述符,不同的是:
-
1)poll 函数采用链表的方式替代原来 select 中 fd_set 结构,因此可监听文件描述符数量不受限。
-
2)poll 函数返回后,可以通过 pollfd 结构中的内容进行处理就绪文件描述符,相比 select 效率要高。
-
3)新增水平触发:也就是通知程序 fd 就绪后,这次没有被处理,那么下次 poll 的时候会再次通知同个 fd 已经就绪。
优点:
-
采用链表的方式替代原来 select 中 fd_set 结构,因此可监听文件描述符数量不受限。
-
poll 函数返回后,可以通过 pollfd 结构中的内容进行处理就绪文件描述符,相比 select 效率要高
-
实现在同一个线程内同时处理多个 IO 请求
缺点:
和 select 函数一样,poll 返回后,需要轮询 pollfd 来获取就绪的描述符。事实上,同时连接的大量客户端在一时刻可能只有很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降。
3.epoll函数-用的比较多
函数定义
int epoll_create(int size);//创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大 //事件注册函数,将需要监听的事件和需要监听的 fd 交给 epoll 对象 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); //等待 epfd 上的 io 事件,最多返回 maxevents 个事件。 int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
epoll 是基于事件驱动的 IO 方式,与 select 相比,epoll 并没有描述符个数限制。
epoll 使用一个文件描述符管理多个描述符,它将文件描述符的事件放入内核的一个事件表中,从而在用户空间和内核空间的复制操作只用实行一次即可。
优点:
1)没有最大并发连接的限制,能打开的 FD 的上限远大于 1024。
2)效率提升,不是轮询的方式,不会随着 FD 数目的增加效率下降。
3)内存拷贝,利用 mmap() 文件映射内存加速与内核空间的消息传递,即 epoll 使用 mmap 减少复制开销。
4)新增 ET 模式。