目录

文章目录

一、五种IO模型

1.阻塞IO:

2.非阻塞IO

3.信号驱动IO

4.IO多路转接

5.异步IO

二、高级IO的一些重要概念

1.同步通信和异步通信

2.阻塞和非阻塞

三、其他高级IO

四、非阻塞IO

1.fctl函数

2.实现setNoBlock函数,将文件描述符设置为非阻塞

3.轮询方式读取标准输入

、IO多路转接之select

1.初始select

2.select函数原型

3.理解select执行过程

4.select的特点

5.select的缺点

6.select使用示例:检测标准输入输出

7.select使用实例

、IO多路转接之poll

1.poll函数接口

2.参数说明

3.返回结果

4.poll的优点

5.poll的缺点

6.poll使用实例:使用poll监控标准输入

总结


一、五种IO模型

1.阻塞IO:

内核数据准备好之前,系统调用会一直等待。所有的套接字默认都是阻塞方式阻塞是最常见的IO模型

2.非阻塞IO

如果内核还未将数据准备好,系统调用仍然会直接返回,并返回EWOULDBLOCK错误码。非阻塞IO往往需要程序员循环方式反复尝试读写文件描述符,这个过程称为轮询。这对cpu来说是较大的浪费,一般只有特定的场景下才使用

3.信号驱动IO

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

4.IO多路转接

虽然从图上看和阻塞IO类似,实际上最核心在于IO多路转接能够同时等待多个文件描述符就绪状态

5.异步IO

内核数据拷贝完成时,通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据

在任何IO过程中,都包含两个步骤,第一是等待,第二是拷贝。而且在实际的应用场景中,等待消耗的时间往往都高于拷贝的时间,让IO高效,最核心的办法就是让等待时间尽量减少。

二、高级IO的一些重要概念

1.同步通信异步通信

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

在多进程多线程的时候,也有提到同步互斥这里的是完全不同的概念

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

2.阻塞和非阻塞

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

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

三、其他高级IO

非阻塞io,纪录锁,系统V流机制io多路转接(io多路复用),readvwritev函数以及存储映射IO(MMAP),这些统称为高级IO

本文重点讨论IO多路转接

四、非阻塞IO

1.fctl函数

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

#include<unistd.h>
#include<fcntl.h&gt;

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

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

fcntl函数有5种功能

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

2.实现setNoBlock函数,将文件描述符设置为非阻塞

基于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参数.

3.轮询方式读取标准输入

#include<stdio.h&gt;
#include<unistd.h&gt;
#include<fcntl.h>


void SetNoBlock(int fd)
{
    int fl = fcntl(fd,F_GETFL);
    if(fl <0)
    {
        perror("fcntl");
        return;
    }

    fcntl(fd,F_SETFL,fl|O_NONBLOCK);
}

int main()
{
    SetNoBlock(0);
    while(1)
    {
        char buf[1024] = {0};
        ssize_t read_size = read(0,buf,sizeof(buf)-1);
        if(read_size <0)
       {
         sleep(1);
         continue;
       }

       printf("input:%sn",buf);
       return 0;
}

五、IO多路转接之select

1.初始select

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

2.select函数原型

#include<sys/select.h>

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

//nfd 是需要监视的最大文件描述符值+1
//rdset,wrset,exset分别对应需要检测的可读文件描述符的集合,可写文件描述符的集合,以及异常文件描述符的集合
//timeout结构timeval,用来设置select的等待时间

参数timeout取值

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的全部位

关于timeval结构

timeval结构用于描述一段时间长度,如果在这个时间内,需要监视的描述符没有事件发生则函数返回,返回值为0

strcut timeval
{
    __time_t tv_sec;   //seconds
    __suseconds_t tv_usec;   //microseconds
};

函数返回值

错误值可能为:

常见使用场景

fs_set readset;
FD_SET(fd,&amp;readset);
select(fd+1,&amp;readset,NULL,NULL,NULL);
if(FD_ISSET(fd,readset)){...}

3.理解select执行过程

理解select模型的关键在于理解ds_set,这里取fd_set长度为1字节,fd_set中的每一个bit可以对应一个文件描述符fd,则1字节长的fd_set最大可以对应8个fd

  1. 执行fd_set set; FD_ZERO(&amp;set); 则set用位表示是0000 0000
  2. 若fd = 5 执行FD_SET(fd,&amp;set);后变为0001 0000
  3. 若再加入fd = 2,fd = 1,则set变成 0001 0011
  4. 执行select(6,&amp;set,0,0,0); 阻塞等待
  5. 若fd = 1,fd = 2 上都发生可读事件,则select返回,此时set变为0000 0011
  6. 注意,没有事件发生的fd = 5被清空

4.select的特点

5.select的缺点

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

6.select使用示例:检测标准输入输出

#include<stdio.h>
#include<unistd.h>
#include<sys/select.h>

int main()
{
    fd_set read_fds;
    FD_ZERO(&amp;read_fds);
    FD_SET(0,&amp;read_fds);
    for(;;)
    {
        printf("> ");
        fflush(stdout);
        int ret = select(1,&amp;read_fds,NULL,NULL,NULL);
        if(ret <0)
        {
            perror("select");
            continue;
        }

        if(FD_ISSET(0,&amp;read_fds))
        {
            char buf[1024] = {0};
            read(0,buf,sizeof(buf)-1);
            printf("input:%s",buf);
        }

        else
        {
            printf("invalid fd");
            continue;
         }
        FD_ZERO(&amp;read_fds);
        FD_SET(0,&amp;read_fds);
    }
    return 0;
}

7.select使用实例

参照gitee,实现select字典服务

六、IO多路转接之poll

1.poll函数接口

#include<poll.h>

int poll(struct pollfd * fds,nfds_t nfds, int timeout);

//pollfd结构
struct pollfd{
    int fd;
    short events;  //requested events
    short revents; // returned events
};

2.参数说明

3.返回结果

返回值小于0,表示出错

返回值等于0,表示poll函数等待超时

返回值大于0,表示poll由于监听的fd就绪而返回

4.poll的优点

不同与select使用三个位图来表示fdset的方式,poll使用一个pollfd指针实现

5.poll的缺点

poll中监听的文件fd增多时

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

6.poll使用实例:使用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(&amp;poll_fd, 1, 1000);
 if (ret < 0) {
 perror("poll");
 continue;
 }
 if (ret == 0) {
 printf("poll timeoutn");
 continue;
 }
 if (poll_fd.revents == POLLIN) {
 char buf[1024] = {0};
 read(0, buf, sizeof(buf) - 1);
 printf("stdin:%s", buf);
 }
 }
}

总结

本文主要介绍了select和poll,下一篇文章详解epoll

原文地址:https://blog.csdn.net/jolly0514/article/details/134599812

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_25032.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注