五种IO模型与阻塞IO

个人主页:Lei宝啊 

愿所有美好如期而遇


IO本质

我们常说IO就是input,output,也就是输入和输出,但是,他的本质是什么?站在OS角度,站在进程的角度,IO是什么?我们想,输入输出,数据都是保存在内存中,从硬件输入,再输出到硬件上,像scanf,printf,recv,read,write等函数,他们实际上做了什么?其实是等待加拷贝,什么叫等待加拷贝,其实就是说,等待硬件条件满足。我们用recv举例,他就在等待接收缓冲区中有数据,然后将接收缓冲区中的数据拷贝到用户缓冲区中。所以我们说,其实IO的本质就是等待+拷贝

那么什么叫做高效的IO?像我们计算机计算一个简单的加法时,一秒可以计算数亿,但是如果说我们要将每一次计算的结果打印到显示器上,那么可能只有几万,这样的IO显然并不高效,为什么不高效,因为他降低了计算效率,即使结果已经计算出来,但是我们仍然看不到,本质就是因为等待,打印在等待显示器这个硬件资源就绪,而光计算,并不需要等待,所以效率就高,所以高效的IO就是降低等待的时间,等待时间占的比重越低,IO效率越高。

那么什么叫做高效的代码?就是减少IO的比重,因为IO就注定了需要等待硬件资源,注定了会影响效率,第二个就是将等待的时间利用起来,这样也能提高效率。

五种IO模型

  • 1. 阻塞IO,阻塞式等待,资源就绪进行IO。
  • 2. 非阻塞IO, 资源不就绪就直接返回。
  • 3. 信号驱动IO,收到信号进行IO。
  • 4. 多路复用,多路转接。
  • 5. 异步IO。

1和2的区别就在于等待方式不同,什么是多路复用?简单来说,我们传统的IO是等+拷贝,并且一次只能等待一个文件描述符,而多路复用就可以通过系统调用同时等待多个文件描述符,将所有文件描述符等待的时间重叠起来,拷贝工作仍然由我们传统的接口如recv等来完成。异步IO就是说,这些操作全部由内核来完成,用户不用进行IO,我们称用户只要参与了IO,就是同步IO,也就是说,只要用户参与了等或者拷贝,就是同步IO。

这里我们和线程的同步IO做一下区分,线程的同步指的是线程之间的执行具有顺序性,而这里的同步IO指的是只要用户参与了IO就是同步IO。

五种IO方式中最为高效的就是多路复用,当然,因为阻塞IO比较简单,所以使用的还是比较多。

代码实现

接下来我们就简单举个例子来实现一下多路复用。

select系统调用

第一个参数:填写最大fd值+1,中间三个参数,都是fd集合,表示我们想让操作系统监管的fd,最后一个参数是一个结构体指针,这个结构体有两个属性,秒和毫秒,表示select系统调用等待监管的fd集合的时长,当有fd就绪时,这个参数返回,被设置为剩余时长。select返回值为就绪的fd数目。

fd_set类型的变量,实际上是位图,用来标识fd的位置和状态,下面的四个宏,用来对fd_set类型的变量做操作,第一个是移除,第二个是判断存在,第三个是设置,第四个是清空。

#include "Socket.hpp"
#include <vector>

using namespace socket_ns;

const static int N = sizeof(fd_set) * 8;
const static int defaultfd = -1;

class SelectServer
{

public:
    SelectServer(uint16_t port)
        : _port(port), _listensock(make_unique<TcpSocket>()), 
        assist(N, defaultfd)
    {
        Analyze addr("0", _port);
        _listensock->BuildTcpServerSocket(addr);

        assist[0] = _listensock->getfd();
    }

    ~SelectServer() {}

    void Loop()
    {
        while (true)
        {
            fd_set rfds;
            FD_ZERO(&rfds);

            // 将所有合法的fd设置进rfds中
            int maxfd = assist[0];
            for (int i = 0; i < N; i++)
            {
                if (assist[i] == defaultfd)
                    continue;
                FD_SET(assist[i], &rfds);

                if (maxfd < assist[i])
                    maxfd = assist[i];
            }

            timeval timeout = {1, 0};
            int n = select(maxfd + 1, &rfds, nullptr, nullptr, nullptr);
            switch (n)
            {
            case 0:
                Log(Debug, "timeout...");
                break;
            case -1:
                Log(Error, "select error");
                break;
            default:
                Log(Info, "select fd num: %d", n);
                HandlerEvent(rfds);
                break;
            }
        }
    }

    void HandlerEvent(fd_set &rfds)
    {
        for (int i = 0; i < N; i++)
        {
            // 这里判断守监管的fd是否就绪,监管的fd在assist中
            if (assist[i] == defaultfd)
                continue;
            if (FD_ISSET(assist[i], &rfds))
            {
                if (assist[i] == _listensock->getfd())
                {
                    AcceptClientRequest(); 
                    // listen套接字获取普通套接字加入辅助数组
                }
                else
                {
                    HandlerNormalRequest(i); // 处理普通套接字的请求
                }
            }
        }
    }

    // 参数:已就绪的普通fd的位置
    void HandlerNormalRequest(int pos)
    {
        char buffer[N];
        int n = recv(assist[pos], buffer, sizeof(buffer) - 1, 0);

        if (n > 0)
        {
            cout << buffer << endl;

            string rest = "response: ";
            rest += buffer;
            send(assist[pos], rest.c_str(), rest.size(), 0);
        }
        else
        {
            close(assist[pos]);
            assist[pos] = defaultfd;
        }
    }

    void AcceptClientRequest()
    {
        Analyze an("0", _port);
        sock_addr addr = _listensock->Accept(an); 
        // 通过返回值,我们可以拿到普通socketfd

        int socketfd = addr->getfd();
        if (socketfd < 0)
            return;

        int pos = 1;
        for (; pos < N; pos++)
        {
            if (assist[pos] == defaultfd)
                break;
        }

        if (pos == N)
        {
            close(socketfd);
            Log(Warning, "FULL");
            return;
        }
        else if (pos < N)
        {
            assist[pos] = socketfd;
            Log(Info, "AcceptClientRequest Set Success");
        }
    }

private:
    uint16_t _port;
    unique_ptr<Socket> _listensock;

    vector<int> assist;
};

select实现的多路复用,是有缺陷的,因为它能够等待的fd是有上限的,在博主的ubuntu下,他是1024个bit位,也就是说,最多能够同时等待1024个fd。

再一个,每一次,对select的参数来说,都需要重新设置,而poll解决了这两个问题,但是这里我们不再多谈,后面我们会讲解epoll。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lei宝啊

觉得博主写的有用就鼓励一下吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值