select /epoll 阻塞/非阻塞 IO

五种IO模型

1 :阻塞IO
内核将数据准备好之前,系统调用会一直阻塞等待
2 :非阻塞IO
:内核数据没准备好,系统调用会直接返回,并且返回错误码-----EWOULDBLOCK

3 : 信号驱动IO
内核将数据准备好的时候,使用SIGIO信号通知应用程序进行IO操作

4 :IO/多路转接
核心在于:能够同时等待多个文件描述符的就绪状态。

5异步IO
内核将数据拷贝完成之后再通知应用程序(可以拷贝了)

总结: iO过程分两步:
一: 等待
二: 拷贝

高级IO的重要概念

同步通信和异步通信

所谓同步和异步,是指在调用时没有得到结果之前,该调用在返回时有没有得到结果,得到----同步 ,未得到 ----- 异步

注意这里同步异步通信和线程/进程同步异步不一样

线程/进程同步与互斥机制是为了保证线程/进程执行的顺序,尤其是在访问临界资源

阻塞与非阻塞

阻塞与非阻塞关注的是程序等待返回结果时的状态,是阻塞等待,还是轮询;

IO多路转接

非阻塞IO

fcntl
文件描述符,默认都是阻塞IO

函数原型:

#include <unistd.h>
#include <fcntl.h>

int ftcnl (int fd , int cmd , cmd , /* arg*/);

传入cmd 的参数不同,后边追加的参数也就不同

fcntl5种功能:

1 复制现有fd
cmd = F_DUPFD
2 获得/设置文件描述符的标记
cmd =F_GETFD或F_SETFD

3 获得/设置文件状态标记
cmd= F_GETFL 或F_SETFL

4 获得设置异步IO所有权
cmd = F_GETOWM 或 F_SETOWN

5 获得/设置记录锁
cmd = F_GETLOKF ,F_SETLK或F_SETLKW

我们要实现非阻塞,就将文件状态描述符设置为非阻塞

步骤:
1 取出
2 修改,设置回去

void SetNoBlock(int fd)
{
int f1 = fcntl ( fd, F_GETFL);//取出放入f1
if (f1 < 0)
{
perror(" fcntl ");
return ;
}

fcntl = (fd ,F_SETFL,f1 | O_NONBLOCK);
}

I/O 多路转接select

系统提供 select函数实现多路复用输入/输出模型

  1. select 函数是用来监视 程序中多个文件描述符的状态变化的。

  2. select函数会让程序发生阻塞等待,直到有文件描述符状态发生变化

原型:
#include <sys/select.h>

int slecet (int nfds , fd_set * readfds, fd_set* writefds , fd_set *exceptfds , struct timeval *timeout );

参数 nfd 是需要监视的最大文件描述符值 + 1
rdset, wtset 以及 exset 分别对应需要检测的可读、可写、异常文件描述符。

参数 timeout为 结构 timeval时间 ,用来设置 select 的等待时间。

timeout取值:
NULL: 表示select函数 没有等待时间,一直发生阻塞,直到有文件描述符状态变化

0  :仅检测文件描述符集的状态,  然后立即返回。

待定时间:     若没时间发生 ,select超时返回。

slecet 函数的返回值:

执行成功: 返回文件描述符改变的个数

返回0表示 timeout超时

发生错误 返回 -1,错误原因存于error

select 的 特点

1 select 可监视的文件描述符的个数取决于 fd_set的值,每个服务器的 fd_set 值 可能不同

fd_set 是一个整数数组,更确切的说应该是位图,每一位代表一个文件描述符。

2 将fd 加入select 监控集的同时 ,需要准备一个array/ vector 来保存 加入的 fd

原因有二 :
一 : 后期对照查看 哪些fd的状态变化
二:select返回后 会把以前加入的fd中未变化 统统清理掉,每次select 前都要重新从 array 获取fd逐一加入 FD_ZERO优先,,在扫描array的同时 获取array的大小 ,作为 select的第一个参数。

select 缺点:

1 每次调用select ,都要手动设置fd集合 ,不方便

2 每次调用select 都要把fd从用户态 拷贝到内核态,fd越多,开销越大

3 每次调用 select 都要遍历array ,开销大

4 select 支持监听的文件描述符数量太小。

I/O多路转接之epoll

epoll 认识:
man手册中说 epoll 是为了 处理大批量句柄而改进的poll
于内核2.5.44 开始引进
,被公认为Linux 2.6下性能最好的多路I/O就绪通知方法。

epoll 相关的系统调用

1 epoll_create :

创建一个epoll 句柄 从 Linux 2.6.8以后 忽略参数

epoll句柄 用完之后 必须及时close。

int epool_create(int size);

2 epoll_ctl()

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

  • 需要先注册需要监听的事件
  • 第一个参数是epoll_create 返回值(句柄)
  • [ ]第二个参数表示动作,,用三个宏来表示,分别为
  •  		1:EPOLL_CTL_ADD,注册新的fd到epfd中 
    
  •  		2:EPOLL_CTL_MOD,修改已注册fd的监听事件
    
  •  		3: EPOLL_CTL_DEL,从已注册的epfd中删除一个fd
    

-【】 第三个参数是需要监听的fd

  • 【】第四个参数告诉内核需要监听什么事。

  • events 也是几个宏,

  • EPOLLIN :表示对应文件描述符的文件可以读

  • EPOLLOUT:可以写

  • EPOLLPRI:有紧急数据可读

  • EPOLLERR:对因文件描述符文件发生错误

  • EPOLLHUP:对应文件描述符被挂断

  • EPOLLLET : 将EPOLL设为边缘触发模式。

  • EPOLLONESHOT:只监听一次

3 epoll_wait

int epoll_wait( int epfd, struct epoll_event *events , int maxevents , int timeout);

收集在 epoll监控的事件中已经发送的事件

最后一个参数:是超时时间(0 立即返回 ,-1 永久阻塞)

函数调用成功:返回对应I/O上已经准备好的文件描述符的数目,返回0 表示超时了。返回值 小于0 调用失败。

如果缓冲区上数据没有被读完,调用epoll_wait 会立刻返回,直到缓冲区上数据读完了,epoll_wait才会发生阻塞。

epoll 的优点 (和 select的缺点对应)

1 使用方便,不用再每次循环都要设置select中的fd,同时也做到了输入输出的分离。

2 数据拷贝轻量级,在合适的时候调用EPOLL_CTL_ADD,将文件描述符结构拷贝到内核中;而select和poll每次循环都需要拷贝。

3 事件回掉机制,避免使用遍历,而用了回掉函数的方式,将就绪文件描述符结构加入到了就绪队列中。
解释 : epoll_wait 返回之后,直接访问就绪队列就知道哪些文件描述符已经就绪。时间复杂度为O(1)

4没有时间限制,文件描述符数目无限制。而select 不能太多,有限制。

5 select 每次传入传出都需要注册;epoll一次注册永久生效

epoll在默认情况下是水平触发模式,select poll是在LT模式下的

什么是边缘触发和水平触发?

水平触发: LT
只要缓冲区有数据,就一直处理。

例如 打游戏到了激动人心的时刻,妈妈喊饭熟了吃饭,,你没理,你妈妈就一直喊,

边缘触发: ET
只有接收了数据才会处理,并不一定能处理完数据。

你妈妈喊了你一次,你没理,就不管你了自己吃饭去了, 典型后妈(_)

对比 LT 和 ET

边缘出发ET的 性能要比水平触发的性能好:::大大减少了用户态和内核态之间的切换,切换很耗时间。

但是使用ET时,我们必须保证一次响应时将数据都处理完。

实质是: 当一个文件描述符就绪了,LT会反复提醒就绪了,增加了

不会再反复提醒就绪了,而如果LT要是能够让这个就绪状态立即返回处理,而不一直提示的话,其实性能也是一样的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值