IO复用:同时监听多个文件描述符
1.select
- 面试高频
- select与poll有什么缺点 => 使用epoll
- epoll的ET模式和LT模式
重点关注的是读事件EPOLLIN,因为读事件前提需要缓冲区中有数据才能读,没有数据就会被阻塞,所以需要先判断接受缓冲区中是否有数据,写事件(EPOLLOUT)只需要缓冲区为空就可写
- select:同时监听多个文件描述符,最多1024个,读,写,异常,集合
内核:轮询方式
时间复杂度:O(n)- poll:支持比select更多的文件描述符,更多的事件类型
内核:轮询方式
时间复杂度:O(n)
select与poll的缺点:
- 找到就绪描述符 需要遍历所有描述符O(n)
- 内核中是轮询的方式
- 每次都需要向内核传递描述符和事件- epoll:解决select与poll的缺陷
- select:就绪描述符数目,不知道具体是谁,数目不多的情况下
读、写、异常事件,最多1024个
检测5秒内是否有数据
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include <sys/time.h>
#include<sys/select.h>
int main()
{
int fd = 0;//stdin 0 stdout 1 stderr 2 键盘对应的描述符
fd_set fdset; //集合 收集描述符 被select检测
while(1)
{
struct timeval tv = {5,0}; //超时时间
FD_ZERO(&fdset); //清空集合中的每个位
FD_SET(fd,&fdset); //fd添加到集合中 多个数据需要循环
int n = select(fd+1,&fdset,NULL, NULL,&tv); //得到就绪描述符的数目,无法知道是谁 通过循环找到描述符为1的数据
//fd+1 只检测可能有数据的之前的位
if(n == -1)
{
printf("err \n"); //失败
break;
}
if(n == 0)
{
printf("time out \n");//时间结束
}
else
{ //n>0 有数据
if(FD_ISSET(fd,&fdset)) //成功
{
char buff[128] = {0};
read(fd,buff,127}; //读取数据 输出
printf("read:%s\n",buff);
}
}
}
}
2. poll
- poll:同时监听多个文件描述符
支持比select更多的文件描述符;使用数组来实现
#include<poll.h>
POLLIN:数据可读
POLLOUT:数据可写
POLLRDHUP:TCP连接被对方关闭,或对方关闭了写操作
3. epoll
- epoll:适合于文件描述符特别多的时候
使用一组函数来实现
#include<sys/epoll.h>
- epoll_create():创建内核事件表 在内核空间存储的 不需要每次都要传递描述符和事件 只用向内核中传递一次事件 使用到的数据结构是红黑树
- epoll_ctl():
- epoll_wait():获取就绪描述符
4. ET LT模式
- ET 、LT模式
ET epoll 高效模式 边沿触发
LT select poll epoll 水平触发
4.1 LT模式
- LT模式
像妈妈叫吃饭时,叫一次 不动,就在叫一次 还不动就继续叫 直到你吃饭为止
当客户端输入的数据为hello时,epoll_wait返回了6次,每次只读一个字符,读完h时epoll_wait就会继续发送通知,直至整个数据读完为止
4.2 ET模式
- ET模式
- TCP协议 不存在数据丢失
例如一次只能处理一个字符,客户端发过来的数据是hello,则第一次只处理h,剩下的存储在缓冲区中,等到下一次epoll_wait再有事件通知时,就会输出 c ,在下一次就是 l ;
- 为了在一次事件通知时能够读完所有数据,需要循环读取数据
- 不循环则一次只能读一个,循环读取数据时,如果recv读到最后为空时就会发生阻塞
#include<unistd.h>
#include<fcntl.h>
- int fcntl(int fd,int cmd…) //增加非阻塞的的属性
- 设置非阻塞后 可使用循环 ,能够解决防止因数据读完后为空导致阻塞的情况
- 使用循环就能够使ET模式下,epoll_wait通知一次时就能够读完所有数据,
15 void setnonblock(int fd) //给fd设置非阻塞属性
16 {
17 int oidfl = fcntl(fd,F_GETFL);
18 int newfl = oidfl | O_NONBLOCK;
19 if(fcntl(fd,F_SETFL,newfl) == -1)
20 {
21 printf("fcntl err\n");
22 }
23 }
epoll_create 思路:创建内核事件表
epoll_ctl():向内核事件表中添加文件描述符
5. libevent
libevent:服务器端编程常用,对poll,epoll进行封装
定义实例 Base
创建事件
注册(添加)到libevent
事件循环 开始检测 底层调用的是select poll epoll_wait等函数
将就绪的事件调用回调函数fun()方法
win 句柄:文件描述符