IO的多路转接

什么是IO??

IO分两步:等+数据拷贝
举例:
读IO就是:等待读事件就绪+将内核数据拷贝至用户空间
写IO就是:等待写事件就绪+将用户空间数据拷贝至内核空间

五种IO模型的基本概念

  1. 阻塞IO:在内核将数据准备好之前,系统调用会一直等待。所有的套接字,默认都是阻塞方式。
  2. 非阻塞IO:如果内核还未将数据准备好,系统调用直接返回,并且返回EWOULDBLOCK错误码。
    非阻塞IO往往需要程序员轮询检测读写的文件描述符。这对CPU来说是较大的浪费,一般只有特定场景才使用。
  3. 信号驱动IO:内核将数据准备好的时候,使用SIGIO信号通知应用程序进行IO操作。
  4. IO多路转接:IO多路转接可以等待多个文件描述符的就绪状态。
  5. 异步IO:不需要发起IO的进程亲自参与数据拷贝,而是由内核在数据拷贝完成时,通知应用程序。

非阻塞IO

fcntl

一个文件描述符,默认时阻塞IO。
函数原型:

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

int fcntl(int fd,int cmd,.../* arg */);

fcntl 函数的五种功能:

  • 复制一个现有的文件描述符(cmd=F_DUPFD)
  • 获得/设置文件描述符标记(cmd=FD_GETFD或F_SETFD)
  • 获得/设置文件状态标记(cmd=F_SETFL或F_SETFL)
  • 获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN)
  • 获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW)

我们利用fcntl函数的第三个功能 ,将一个文件描述符设置为非阻塞状态。

实现函数SetNoBlock

void SetNoBlock(int fd)
{
	int fl=fcntl(fd,F_GETFL);
	if(fl < 0)
	{
		perror("fcntl");
		return;
	}
	fcntl(fd,F_SETFL,fl | O_NONBLOCK);
}
  • 使用F_GETFL将当前的文件描述符的属性取出来(这是一个位图)
  • 然后再使用F_SETFL将文件描述符设置回去。设置回去的同时加上一个O_NONBLOCK参数

IO多路转接(高级IO/高效IO)

高级IO的本质:减少等的比重

select

  • select 系统调用的作用,可以让我们监视多个文件描述符的状态变化;
  • 系统会在select这里等待,直到被监视的文件描述符有一个或多个发生状态改变。

select函数原型

#include<sys/select.h>

int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
参数解释:
nfds是你要监视的最大的文件描述符+1;
readfds,writefds,exceptfds 分别对应需要检测的可读文件描述符的集合,可写文件描述符的集合和异常文件描述符的集合;
timeout : 结构timeval,用来设置select()的等待时间
timeout取值:
NULL   表示没有timeout,select一直被阻塞,直到某个文件描述符上事件发生
0   :非阻塞,仅检测描述符集合的状态,然后立即返回;
特定时间值:表示多长时间轮询检测一次;

返回值:
> 0 表示有多少个文件描述符就绪
== 0 表示超时返回
-1  表示调用出错(错误码在errno中)

fd_set结构:
在这里插入图片描述在这里插入图片描述
其实这个结构既是一个“位图”
在这里插入图片描述
为了方便操作fd_set,提供了如下接口:

void FD_CLR(int fd, fd_set *set); // 用来清除描述词组set中相关fd 的位
int FD_ISSET(int fd, fd_set *set); // 用来测试描述词组set中相关fd 的位是否为真
void FD_SET(int fd, fd_set *set); // 用来设置描述词组set中相关fd的位
void FD_ZERO(fd_set *set)   //用来清除描述词组set的全部位

select 就绪条件

读就绪

  • socket内核中,接收缓冲区中的字节数, 大于等于低水位标记SO_RCVLOWAT. 此时可以无阻塞的读该文件描述符, 并且返回值大于0;
  • socket TCP通信中, 对端关闭连接, 此时对该socket读, 则返回0;
  • 监听的socket上有新的连接请求;
  • socket上有未处理的错误;

写就绪

  • socket内核中, 发送缓冲区中的可用字节数(发送缓冲区的空闲位置大小), 大于等于低水位标记SO_SNDLOWAT, 此时可以无阻塞的写, 并且返回值大于0;
  • socket的写操作被关闭(close或者shutdown). 对一个写操作被关闭的socket进行写操作, 会触发SIGPIPE信号;
  • socket使用非阻塞connect连接成功或失败之后;
  • socket上有未读取的错误;

select特点

  • 可监控的文件描述符个数取决于sizeof(fd_set)的值,我的服务器上sizeof(fd_set)=128,每个比特位表示一个文件描述符,所以我服务器上支持的最大文件描述符是128*8=1024;
  • 将fd加入select监控集的同时,还要使用一个数据结构array保存放到select监控集中的fd,
    一是用于select返回后,array作为原数据和fd_set进行FD_ISSET判断。
    二是select返回后会把以前加入但是并无事件发生的fd清空(输入输出型参数),则每次开始select前都要重新设置fd_set集合,扫描array的同时得到fd最大值,用于设置select的第一个参数。

poll

函数接口

#include<poll.h>
int poll(struct pollfd *fds,nfds_t nfds,int timeout);
// pollfd 结构
struct pollfd{
	int fd;
	short events;
	short revents;
};

参数说明:
fds是一个poll函数监听的结构列表,每一个元素中包含了三个部分:文件描述符,监听的事件集合,返回的事件集合
nfds表示fds数组的长度
timeout:poll函数的超时时间(ms)

返回值:
> 0 表示有多少个文件描述符就绪
== 0 表示超时返回
-1  表示调用出错

events和revents的取值:
在这里插入图片描述

epoll

为处理大量句柄而作了改进的poll
相关的系统调用

int epoll_create(int size);
创建一个epoll的句柄
  -  自从linux 2.6.8之后,size参数是被忽略的
  - y用完之后必须调用close()关闭


int epoll_ctl(int epfd,int op, int fd,struct epoll_event *event);
epoll的事件注册函数:
 - 注册要监听的事件类型
 - epfd:epoll_create()的返回值
 - op:表示动作,用三个宏表示(EPOLL_CTL_ADD:注册新的fd到epfd中; EPOLL_CTL_MOD:修改已注册到fd中的监听事件;EPOLL_CTL_DEL:从epfd中删除一个fd) 
 - fd:需要监听的fd
 - 第四个参数是告诉内核要监听哪个事件

struct epoll_event结构如下:
在这里插入图片描述
events重点关注以下两个:

  • EPOLLIN : 表示对应的文件描述符可以读 (包括对端SOCKET正常关闭);
  • EPOLLOUT : 表示对应的文件描述符可以写;
int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout);

- events:输出型参数,获取并存放已经就绪的事件
- maxevents:告诉内核这个events多大,maxevents不能大于epoll_create()中size的大小
- timeout:(ms,0 立即返回,-1 阻塞)
返回值:
> 0 表示有多少个文件描述符就绪
== 0 表示超时返回
-1  表示调用出错

epoll原理
在这里插入图片描述

总结select,poll,epoll的优缺点

这三者都是高级IO,在IO体系中就负责等,也叫就绪事件通知方案。他们的高效体现在一次可以等待多个文件描述符.但是它们也是各有特点:

select
缺点 :

  1. 等的文件描述符是有上限的;
  2. 每次调用select都需要把fd集合从用户态拷贝至内核态。(设置fd集合),当文件描述符很多时开销会很大;
  3. 同时每次调用select都需要在内核中轮询检测传递进来的文件描述符,这个当fd很多时开销也是很大的;
  4. 还有就是select的输入输出型参数是在一起的,每次调用select都要手动设置fd集合,非常不方便

poll
poll解决了select的两个问题:

  • poll等待的文件描述符无上限
  • 它将输入输出型参数分离开来,每次调用不用从新设置fd集合

但是它也没有解决描述符越多,检测时间就越长的问题和要把大量的fd从用户拷贝到内核的问题。

epoll
它解决了select和poll的问题:

    1. 接口使用方便,不用每次从新设置fd集合,也将输入输出参数分离了
    1. 数据拷贝轻量:只是在合适的时候调用EPOLL_CTL_ADD 将文件描述符结构拷贝到内核中。
  • 事件回调机制:避免了轮询遍历,而是使用回调函数的方式,将就绪的文件描述符结构加入到就绪队列当中,epoll_wait 返回直接访问就绪队列就可以直到哪些文件描述符就绪。时间复杂度O(1);
  • 同时也没有文件描述符上限;

使用场景

适用于长连接的情况:
例如, 典型的一个需要处理上万个客户端的服务器, 例如各种互联网APP的入口服务器, 这样的服务器就很适合epoll.

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值