高级IO(一)

目录

一、非阻塞 Fcntl (修改文件描述符参数)

二、多路转接select

2.1 select函数功能

2.2 函数参数接口

2.3 利用多路转接结合网络IO场景代码

 2.3.1 利用select非阻塞处理链接请求Accept

 2.3.2 利用select非阻塞receive客户端的“网络请求”

 2.3.3 利用数组记录已链接的文件描述符(告诉内核遍历的文字描述符表范围)

  2.3.4 实现代码

 三、多路转接之poll

3.1 poll函数功能

3.2 poll的优缺点

3.3 在select模式基础上修改成poll模式


一、非阻塞 Fcntl (修改文件描述符参数)

文件描述符默认文件读取为阻塞式读取,用标准输入这个文件描述符为例子,利用read函数读取文件描述符,当我们不输入任何字符,它会一直在终端等待我们输入,一直等待。

我们可以将该文件描述符设置为非阻塞,这样我们不必一直等待,如果没有数据就进行其他活动。


#pragma once
#include<iostream>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
#include<errno.h>
#include<stdio.h>
void setNonBlock(int fd)
{
    //1.获得文件描述符flag
    int f1 =fcntl(fd,F_GETFL);
    if(f1<0)
    {
        std::cerr<<"fcntl"<<strerror(errno)<<std::endl;
        return;
    }
    //2.添加非阻塞flag
    fcntl(fd,F_SETFL,f1 | O_NONBLOCK);
}

void Printlog()
{
    std::cout<<"this is a log task"<<std::endl;
}

void download()
{
    std::cout<<"this is a download task"<<std::endl;

}


#include<vector>
#include<functional>

using fun_t = std::function<void()>;

//C式宏函数操作
#define INIT(v) do{\
            v.push_back(Printlog);\
        }while(0)

#define EXEC_OTHER(cbs) do{\
        for(auto const & cb : cbs) cb();\
        }while(0)

int main()
{
    std::vector<fun_t> callbacks;
    INIT(callbacks);
    char buffer[1024];
    setNonBlock(0);
    while(true)
    {
        // std::cout<<">>>";
        // fflush(stdout);
        ssize_t n = read(0,buffer,sizeof(buffer)-1);//IO读取
        
        if(n > 0)
        {
            buffer[n-1] = 0;
            std::cout<<"echo# "<<buffer<<std::endl;
        }
        else if(n == 0)
        {
            std::cout<<"read end"<<std::endl;
            break;
        }
        else
        {
            //数据没准备好
            if(errno == EAGAIN)
            {
                std::cout<<"我没错,只是没有数据"<<std::endl;
                EXEC_OTHER(callbacks);
            }
            //信号阻止
            else if(errno == EINTR)
            {
                continue;
            }
            //真的出问题了!
            else{
                std::cout<<"result n ="<< n <<" errno:"<<strerror(errno)<<std::endl;
            }
        }
        sleep(5);
    }
    return 0;
}


二、多路转接select

2.1 select函数功能

通过让程序监视多个文件描述符的状态变化来实现的。当至少有一个被监视的文件描述符的读写时间就绪时,select会通知上层进行读取或者写入(本质是进行拷贝)。

2.2 函数参数接口

  1. nfds这是一个整数,表示需要监视的文件描述符集合中的最大文件描述符值加一。例如,如果需要监视3个文件描述符,分别为3、4和7,那么传给nfds的值应该是8。
  2. readfdswritefdsexceptfds:这些参数都是指向fd_set类型数据的指针,分别用于指定需要监视的可读文件描述符集合可写文件描述符集合异常文件描述符集合fd_set是一个位图,每一位代表一个文件描述符,位的状态(0或1)表示该文件描述符是否被select监听。设置这个函数和指针,可以控制select关注的是哪种事件还是多种事件!

        对fd_set进行操作通过函数调用完成,图片中下面的四个函数:

1.不关心某个文件描述符

2.是否该文件描述符设为了关心状态

3.设置某个文件描述符为关心状态

4.清空该集合

        3. timeout:这是一个指向struct timeval类型的指针,用于设置select的等待时间。struct timeval包含两个成员,分别是秒(tv_sec)和微秒(tv_usec)。如果timeout参数为nullptr,select会阻塞等待,直到有一个或多个文件描述符就绪;如果timeout参数为{0, 0},select会使用非阻塞等待模式,需要程序员编写轮询检测代码;如果timeout参数设置了具体值,如{5, 0},select会在5秒内阻塞等待,如果在5秒内有文件描述符就绪,则通知上层进程;如果没有则超时返回。

select函数返回值有三种情况:

  • 返回值大于0表示有相应个文件描述符就绪。
  • 返回值等于0表示没有文件描述符就绪,超时返回。
  • 返回值小于0表示select调用失败。

2.3 利用多路转接结合网络IO场景代码

 2.3.1 利用select非阻塞处理链接请求Accept

创建完套接字以后服务端通过监听链接请求,调用Accept函数获取链接(本质是约定两端IO的文件描述符),listen套接字本身也是文件描述符,通过accept函数获取链接请求,如果没有链接请求,就会采取阻塞式等待。现在我们利用select,让该文件描述符的等待让select完成,一旦有链接请求,则再accept获取链接的文件描述符。

 2.3.2 利用select非阻塞receive客户端的“网络请求”

同样,当链接建立成功,就可以进行网络通信,客户端发送请求,服务端提供网络服务。服务端通过receive函数调用获取请求,同样是阻塞式的获取请求,我们可以让select非阻塞等待网络请求。

 2.3.3 利用数组记录已链接的文件描述符(告诉内核遍历的文字描述符表范围)

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

结合select第一个参数让其关心maxfd+1,每次获取新的链接我们都要更新这个maxfd。

我们设置一个整形数组,数组存放文件描述符。数组大小是位图所能管理的最大文件描述符个数。设置默认下标值为-1,不为默认值的下标值代表某个链接,我们遍历整个数组,与用当前数组的下标值比较,用更大的文件描述符就更新,这样我们就可以让select帮我们等待更多的请求。

断开链接,对应的文件描述符就close,数组下标恢复默认值。

  2.3.4 实现代码

#pragma once

#include <iostream>
#include<functional>
#include<cstdio>
#include "sock.hpp"
static const int defaultport = 8080;
static const int fdnum = sizeof(fd_set) * 8;    //最大位图表示的文字描述符
static const int defaultfd = -1;


namespace select_ns
{
    using func_t = function<string&(string&)>;
    class SelectServer
    {
    public:
        SelectServer(func_t cb,int port = defaultport):_f(cb), _port(port), _listensock(-1),fd_array(nullptr)
        {}
        void Print()
        {
            std::cout<<"fd list: ";
            for(int i = 0;i<fdnum;i++)
            {
                if(fd_array[i] != defaultfd)
                    std::cout<<fd_array[i]<<" ";
            }
            std::cout<<std::endl;
        }
        void initServer()
        {
            _listensock = sock::GetSocket(_port);       //获取监听套接字
            sock::Bind(_listensock, _port);             //绑定套接字
            sock::Listen(_listensock);                  //监听
            fd_array = new int[fdnum];                  //第三方数组辅助
            for(int i = 0;i<fdnum;i++) fd_array[i] = -1;
            fd_array[0] = _listensock;                  //0号下标设置为监听文字描述符
        }
        //建立链接服务
        void Accepter()
        {
            std::string clientip;
            uint16_t clientport;
            int sock = sock::Accept(_listensock, &clientip, &clientport);   //建立链接!!!
            if (sock < 0)
                return;
            logMessage(NORMAL, "accept success [%s:%d]", clientip.c_str(), clientport);
            int i = 0;
            for (; i < fdnum; i++)
            {
                if (fd_array[i] != defaultfd)
                    continue;
                else
                    break;
            }
            if (i == fdnum)
            {
                logMessage(WARNING, "server is full ,please wait");
                close(sock);
            }
            else
            {
                fd_array[i] = sock;         //数组添加新的链接文件描述符
            }
            Print();
        }

        //请求处理服务
        void Recver(int fd,int index)
        {
            char buffer[1024];
            while(true)     //网络TCP/IP协议以字节流传输,所以我们为了读到完整报文,需要循环读取
            {
                int n = recv(fd, buffer, sizeof(buffer) - 1, 0);
                if (n > 0)
                {
                    buffer[n] = 0;
                    cout<<"client echo# "<<buffer<<endl;
                }
                else if (n == 0)
                {
                    close(fd);
                    fd_array[index] = defaultfd;
                    logMessage(NORMAL, "client quit!");
                    return;
                }
                else
                {
                    close(fd);
                    fd_array[index] = defaultfd;
                    logMessage(ERROR, "recv error, code: %d, error string: %s", errno, strerror(errno));
                    return;
                }
                string request = buffer;
                string resp = _f(request);
                resp = "server response# " + resp; 
                write(fd, resp.c_str(), resp.size());
            }
        }
        //处理IO
        void HandlerEvent(fd_set &rfds) 
        {
            for(int i=0;i<fdnum;i++)
            {
                if(fd_array[i] == defaultfd) continue;

                if(fd_array[i] == _listensock && FD_ISSET(_listensock,&rfds))
                    Accepter();                     //建立链接
                else if(FD_ISSET(fd_array[i],&rfds))
                    Recver(fd_array[i],i);          //处理IO请求
            }
        }
        //启动服务端服务器
        void start()
        {
            
            while (true)
            {
                fd_set rfds;
                FD_ZERO(&rfds);
                int maxfd = fd_array[0]; //maxfd ->让select遍历到哪里? 
                for(int i = 0;i<fdnum;i++)
                {
                    if(fd_array[i] == defaultfd)
                        continue;
                    FD_SET(fd_array[i],&rfds);

                    if(maxfd < fd_array[i]) maxfd = fd_array[i];
                }
                struct timeval timeout = {3,0}; //设置等待时间为3s
                // select存在的问题
                //1.位图结构 处理的请求文件描述符有上限
                //2.必须借助第三方数组
                //3.调用的参数都是输入输出类型的参数,所以每次都要更新fd_set
                //4.需要确定遍历范围
                //5.select采用位图,用户->内核 内核->用户 会来回拷贝,拷贝成本大
                int n = select(maxfd + 1, &rfds, nullptr, nullptr, &timeout);
                switch(n)
                {
                case 0:
                    logMessage(NORMAL, "timeout...");
                    break;
                case -1:
                    logMessage(WARNING, "select error, code: %d,error string describes: %s", errno, strerror(errno));
                    break;
                default:
                    logMessage(NORMAL,"get a new link");
                    HandlerEvent(rfds);
                    break;
                }
            }
        }
        ~SelectServer()
        {
            if(_listensock < 0)
                close(_listensock);
        }

    private:
        func_t _f;              //处理的回调函数
        int _port;
        int _listensock;
        int* fd_array;
    };
}

 三、多路转接之poll

3.1 poll函数功能

// 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由于监听的文件描述符就绪而返回
转载图片:关于event 和 revent参数设置的值

3.2 poll的优缺点

不同与select使用三个位图来表示三个fdset的方式,poll使用一个pollfd的指针实现。
1. pollfd结构包含了要监视的event和发生的event,不再使用select“参数-值”传递的方式. 接口使用比 select更方便。
2. poll并没有最大数量限制 (但是数量过大后性能也是会下降)。

3.3 在select模式基础上修改成poll模式

#pragma once

#include <iostream>
#include<functional>
#include<cstdio>
#include<poll.h>
#include "sock.hpp"
static const int defaultport = 8080;
static const int defaultfd = -1;
static const int num = 10;              //默认最大关心数 也可以改为动态的

namespace poll_ns
{
    using func_t = function<string&(string&)>;
    class PollServer
    {
    public:
        PollServer(func_t cb,int port = defaultport):_f(cb), _port(port), _listensock(-1),_rfds(nullptr)
        {}
        void Print()
        {
            std::cout<<"fd list: ";
            for(int i = 0;i<num;i++)
            {
                if(_rfds[i].fd != defaultfd)
                    std::cout<<_rfds[i].fd<<" ";
            }
            std::cout<<std::endl;
        }
        void initServer()
        {
            _listensock = sock::GetSocket(_port);
            sock::Bind(_listensock, _port);
            sock::Listen(_listensock);
            _rfds = new struct pollfd[num];
            for(int i =0; i<num;i++)
            {
                _rfds[i].fd = defaultfd;
                _rfds[i].events = 0;
                _rfds[i].revents = 0;
            }
            _rfds[0].fd = _listensock;
            _rfds[0].events = POLLIN;
        }
        void Accepter()
        {
            std::string clientip;
            uint16_t clientport;
            int sock = sock::Accept(_listensock, &clientip, &clientport);
            if (sock < 0)
                return;
            logMessage(NORMAL, "accept success [%s:%d]", clientip.c_str(), clientport);

            int i = 0;
            for (; i < num; i++)
            {
                if (_rfds[i].fd != defaultfd)
                    continue;
                else
                    break;
            }
            if (i == num)
            {
                logMessage(WARNING, "server is full ,please wait");
                close(sock);
            }
            else
            {
                _rfds[i].fd = sock;
                _rfds[i].events = POLLIN;
            }
            Print();
        }
        void Recver(int fd,int index)
        {
            char buffer[1024];
            while(true)
            {
                int n = recv(fd, buffer, sizeof(buffer) - 1, 0);
                if (n > 0)
                {
                    buffer[n] = 0;
                    cout<<"client echo# "<<buffer<<endl;
                }
                else if (n == 0)
                {
                    close(fd);
                    _rfds[index].fd = defaultfd;
                    _rfds[index].events = 0;
                    logMessage(NORMAL, "client quit!");
                    return;
                }
                else
                {
                    close(fd);
                    _rfds[index].fd = defaultfd;
                    _rfds[index].events = 0;
                    logMessage(ERROR, "recv error, code: %d, error string: %s", errno, strerror(errno));
                    return;
                }
                string request = buffer;
                string resp = _f(request);
                resp = "server response# " + resp; 
                write(fd, resp.c_str(), resp.size());
            }
        }
        void HandlerEvent()
        {
            for(int i=0;i<num;i++)
            {
                if(_rfds[i].fd == defaultfd) continue;

                if(!(_rfds[i].events & POLLIN)) continue;
                
                if(_rfds[i].fd == _listensock && (_rfds[i].revents & POLLIN))
                    Accepter();
                else if(_rfds[i].revents == POLLIN)
                    Recver(_rfds[i].fd,i);
            }
        }
        void start()
        {
            
            while (true)
            {
                int timeout = 1000;
                int n = poll(_rfds,num,timeout);
                switch(n)
                {
                case 0:
                    logMessage(NORMAL, "timeout...");
                    break;
                case -1:
                    logMessage(WARNING, "poll error, code: %d,error string describes: %s", errno, strerror(errno));
                    break;
                default:
                    logMessage(NORMAL,"get a new link");
                    HandlerEvent();
                    break;
                }
            }
        }
        ~PollServer()
        {
            if(_listensock < 0)
                close(_listensock);
        }

    private:
        func_t _f;
        int _port;
        int _listensock;
        struct pollfd * _rfds;          //关心的事件
    };
}
  • 10
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值