高级IO

五种IO模型

  • 阻塞IO: 在内核将数据准备好之前, 系统调用会一直等待. 所有的套接字, 默认都是阻塞方式.
  • 非阻塞IO: 如果内核还未将数据准备好, 系统调用仍然会直接返回, 并且返回EWOULDBLOCK错误码.
  • 信号驱动IO: 内核将数据准备好的时候, 使用SIGIO信号通知应用程序进行IO操作
  • IO多路转接: 虽然从流程图上看起来和阻塞IO类似. 实际上最核心在于IO多路转接能够同时等待多个文件描述符的就绪状态.
  • 异步IO: 由内核在数据拷贝完成时, 通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据).

任何IO过程中, 都包含两个步骤:

第⼀是等待, 第二是拷贝.
而且在实际的应用场景中, 等待消耗的时间往往都远远高于拷贝的时间. 让IO更高效, 最核心的办法就是让等待的时间尽量少.

同步通信&异步通信

  • 同步和异步关注的是消息通信机制.

同步:就是在发出一个调用时,在没有得到结果之前,该调用就不返回. 但是一旦调用返回, 就得到返回值了; 换句话说,就是由调用者主动等待这个调用的结果;

异步:调用在发出之后,这个调用就直接返回了,所以没有返回结果; 换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果; 而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用.

  • 多进程多线程的时候, 也提到同步和互斥

进程/线程同步也是进程/线程之间直接的制约关系
是为完成某种任务而建立的两个或多个线程,这个线程需要在某些位置上协调他们的工作次序而等待、传递信息所产生的制约关系。尤其是在访问临界资源的时候

阻塞&非阻塞

阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.

  • 阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回.
  • 非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程.

非阻塞IO

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

将阻塞IO设置成非阻塞IO的函数时 fcntl

#include <unistd.h> 
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
fcntl函数有5种功能: 
    复制一个现有的描述符(cmd=F_DUPFD). 
    获得/设置文件描述符标记(cmd=FGETFD 或 FSETFD)
    获得/设置文件状态标记(cmd=FGETFL 或 FSETFL)
    获得/设置异步I/O所有权(cmd=FGETOWN 或 FSETOWN)
    获得/设置记录锁(cmd=FGETLK,FSETLK或F_SETLKW

用第三种功能, 获取/设置文件状态标记, 就可以将一个⽂文件描述符设置为非阻塞.

  1 /*
  2  * 通过这个函数把文件描述符设置为非阻塞
  3  */
  4 
  5 #include <stdio.h>
  6 #include <stdlib.h>
  7 #include <unistd.h>
  8 #include <fcntl.h>
  9 
 10 int SetNoBlack(int fd)
 11 {       
 12         int flag=fcntl(fd,F_GETFL);
 13         if(flag<0){
 14                 perror("fcntl error");
 15                 return -1;
 16         }
 17         int ret=fcntl(fd,F_SETFL,flag | O_NONBLOCK);
 18         if(ret<0){
 19                 perror("fcntl error");
 20                 return -1;
 21         }
 22         return 0;
 23 }
 24 
 25 int main( void )
 26 {
 27         //从标准输入尝试读数据
 28 
 29         SetNoBlack(0);
 30         while(1){
 31                 sleep(1);
 32                 printf("> ");
 33                 fflush(stdout);
 34                 char buf[1024]={0};
 35                 ssize_t read_size=read(0,buf,sizeof(buf)-1);
 36                 if(read_size<0){
 37                         perror("read error");
 38                         continue;
 39                 }
 40                 if(read_size==0){
 41                         printf("read done!\n");
 42                         return 0;
 43                 }
 44                 buf[read_size]='\0';
 45                 printf("buf=%s\n",buf);
 46         }
 47 }

这里写图片描述

重定向

dup/dup2系统调用

将第二个数据重定向到第一个数据中

#include <unistd.h>
int dup(int oldfd); 
int dup2(int oldfd, int newfd);

使用dup2将标准输出重定向到文件中

#include <stdio.h> 
#include <unistd.h> 
#include <fcntl.h>
int main() 
{  
    int fd = open("./log", O_CREAT | O_RDWR);  
    if (fd < 0) {    
        perror("open");    
        return 1;  
    }  
    close(1);  
    dup2(fd, 1); 
    for (;;) {    
        char buf[1024] = { 0 };    
        ssize_t read_size = read(0, buf, sizeof(buf) - 1);     if (read_size < 0) {      
            perror("read");      
            continue;    
        }    
        printf("%s", buf);    
        fflush(stdout);  
    }  
    return 0; 
}

select来实现多路复用输入/输出模型.

  • select系统调用是用来让我们的程序监视多个文件描述符的状态变化的;
  • 程序会停在select这里等待,直到被监视的文件描述符有一个或多个发生了状态改变
#include <sys/select.h>

int select(int nfds, fd_set *readfds, fd_set *writefds,              
           fd_set *exceptfds, struct timeval *timeout);
1. 参数nfds是需要监视的最大的文件描述符值+1;
2. rdset,wrset,exset分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集合及异常文件描述符的集合; 
3. 参数timeout为结构timeval,用来设置select()的等待时间


fd_set 结构其实就是一个整数数组,更严格的说,是个“位图”。使用位图中对应的位来表示要监视的文件描述符.
timeval结构用于描述一段时间长度,如果在这个时间内,需要监视的描述符没有事件发生则函数返回,返回值为0。

函数返回值:
    1. 执行成功则返回文件描述词状态已改变的个数 
    2. 如果返回0代表在描述词状态改变前已超过timeout时间,没有返回
    3. 当有错误发生时则返回-1,错误原因存于errno,此时参数readfds,writefds, exceptfds和timeout 的值变成不可预测。

错误值可能为:
             * EBADF 文件描述词为无效的或该文件已关闭                       
             * EINTR 此调用被信号所中断 
             * EINVAL 参数n 为负值。     
             * ENOMEM 核心内存不足
  1  /*
  2        * 实现一个简单的标准输入监控程序,从标准输入拿到数据显示到终端
  3        * 目的:熟悉一下select用法和功能
  4        * 
  5        * select功能是对描述符集合中的描述符进行状态改变监控
  6        * 当集合中有描述符就绪时,将返回
  7        * 或者当select等待超时时,将返回
  8        * 或者当select等待出错时,将返回
  9        * 
 10        * int select(int nfds,fd_set readfds,writefds,exceptfds,tv);
 11        * nfds:监控的最大描述符+1
 12        * readfds:监控的可读描述符集合
 13        * writefds:监控可写的描述符集合
 14        * exceptfds:监控异常的描述符集合
 15        * tv:select是一个阻塞调用,但是可以设置阻塞的时间
 16        *      NULL:一直阻塞
 17        *      0:   非阻塞
 18        *      >0:   在指定在这段时间内如果没有描述符就绪,则返回0,超时
 19      
 20  */
 21 #include <stdio.h>
 22 #include <unistd.h>
 23 #include <stdlib.h>
 24 #include <errno.h>
 25 #include <string.h>
 26 #include <sys/select.h>
 27 #include <fcntl.h>
 28 #include <time.h>
 29 int main( void )
 30         {
 31                 fd_set readfds;
 32                 struct timeval tv;
 33                 int max_fd=0;
 34 
 35                 while(1){
 36                         //设置select的等待超时时间
 37                         tv.tv_sec=3;
 38                         tv.tv_usec=0;
 39                         //清空可读事件集合,将标准输入加入集合中
 40                         FD_ZERO(&readfds);
 41                         //将标准输入的描述符加入到集合中
 42                         FD_SET(0,&readfds);
 43                         int ret=select(max_fd+1,&readfds,NULL,NULL,&tv);
 44                         if(ret<0){
 45                                 perror("select error");
 46                                 continue;
 47                         }
 48                         else if(ret==0){
 49                                 printf("timeout!!\n");
 50                                 continue;
 51                         }
 52 
 53                         char buff[1024]={0};
 54                         ret=read(0,buff,1023);
 55                         buff[ret-1]='\0';
 56                         printf("buff:[%s]\n",buff);
 57                 }
 58 }

如果在3秒内标准输入没有输入,则超时

socket就绪条件

  • 读就绪

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

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

select特点

  • 可监控的文件描述符个数取决与sizeof(fd_set) 的值 .

这边服务器上 sizeof(fdset)=512,每bit表示一个文件描述符,则我服务器上支持的最大文件描述符是512*8=4096.

  • 将fd加入select监控集的同时,还要再使用一个数据结构array保存放到select监控集中的fd,

一是用于再select 返回后,array作为源数据和fdset 进行 FDISSET判断。
二是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个参数

select缺点

  • 每次调用select, 都需要手动设置fd集合, 从接口使用角度来说也非常不便.
  • 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
  • 同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
  • select支持的文件描述符数量太小

poll

  • 函数接口
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
// pollfd结构 
struct pollfd 
{   
    int   fd;         /* file descriptor */   
    short events;     /* requested events */   
    short revents;    /* returned events */ 
};
  • 参数说明

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

  • 返回结果

返回值小于0, 表示出错; 返回值等于0, 表示poll函数等待超时; 返回值大于0, 表示poll由于监听的文件描述符就绪而返回.

  • pol缺点

poll中监听的文件描述符数目增多时
和select函数一样,poll返回后,需要 轮询 pollfd来获取就绪的描述符.
每次调用poll都需要把大量的pollfd结构从用户态拷贝到内核中.
同时连接的大量客户端在一时刻可能只有很少的处于就绪状态, 因此随着监视的描述符数量的增长, 其效率也会线性下降.

//使用poll监控标准输入
#include <poll.h> 
#include <unistd.h> 
#include <stdio.h>
int main() 
{  
    struct pollfd poll_fd;  
    poll_fd.fd = 0;  、
    poll_fd.events = POLLIN;
    for (;;) {    
        int ret = poll(&poll_fd, 1, 1000);    
        if (ret < 0) {      
            perror("poll");      
            continue;    
        }    
        if (ret == 0) {      
            printf("poll timeout\n");      
            continue;    
        }    
        if (poll_fd.revents == POLLIN) {      
            char buf[1024] = {0};      
            read(0, buf, sizeof(buf) - 1);    
            printf("stdin:%s", buf);    
        }  
    } 
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值