高级IO——非阻塞IO和select

二、非阻塞IO

#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
//最后一个参数可以设置为MSG_DONTWAIT,可以实现非阻塞的方式进行IO,但是并不方便

​ 可以使用open来设置打开方式为O_NONBLOCK;

#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
//可以直接修改文件对象的flag标志位,告诉Linux内核这个文件描述符以现非阻塞方式进行IO;
//第一个参数是文件描述符,第二个参数有多种选项如下;
//复制一个现有的描述符(cmd=F_DUPFD);
//获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD);
//获得/设置文件状态标记(cmd=F_GETFL或F_SETFL);O_NONBLOCK;
//获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN);
//获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW);

​ Linux平台下,ctrl+d快捷键表示标准输入结束;

设置非阻塞如下:

void setnoblock(int fd)
{
    int fl = fcntl(fd, F_GETFL);
    if (fl < 0)
    {
        cerr << "fl error:";
        return;
    }
    fcntl(0, F_SETFL, fl | O_NONBLOCK);
}

​ 当非阻塞设置之后,会设置错误码,表示资源暂时不可获取;

#define	EWOULDBLOCK	EAGAIN //表示操作将会被阻塞,如果是非阻塞方式,则表示条件不就绪,将会再尝试一次;
while (true)
{
    printf("please enter# ");
    fflush(stdout);
    ssize_t n = read(0, buff, sizeof(buff) - 1);
    if (n > 0)
    {
        buff[n - 1] = 0;
        cout << "echo: " << buff << endl;
    }
    else if (n == 0)
    {
        cout << "read done" << endl;
        break;
    }
    else // 资源暂时不可以获取
    {
        // 1.当设置成非阻塞的方式时,数据没有就绪,read/write/recv/send等接口就会以出错的方式返回;
        // 2.出错并不是意味着真的出错了,而是表示一种资源未就绪的状态;
        // 3.通过errno区分是真的出错还是资源未就绪,当错误码为11时就以为着资源未就绪;
        if (errno == EWOULDBLOCK/*EAGAIN*/)
        {
            cerr << "read err, n: " << n << ", errno: " << errno << ", " << 							strerror(errno) << endl;
            sleep(1);
        }
        else
        {
            break;
        }
    }
}

三、多路转接

3.1select

IO = 等待 + 拷贝;

select只负责等待,一次可以等待多个fd;

#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
           fd_set *exceptfds, struct timeval *timeout);
//第一个参数:表示最大文件描述符下标+1;
//返回值:>0,表示有n个fd就绪,==0,表示超时,没有错误也没有fd就绪,<0,表示等待出错;
//第五个参数:给select设置等待时间,1.当等待时间内没有文件描述符就绪就直接返回,值为0,如果有fd就绪就立即返回;2.当时间设置为{0,0}时就意味着非阻塞立即返回;3.当设置为NULL的时候就会阻塞式地等待;4.此参数是输入输出型参数,当提前返回时,此参数显示的就是剩余的快超时时间;
//第二-四个参数:1.fd_set是一个内核提供的数据类型,其实是一个位图类似信号集;2.fd共有三种事件,分别是读时间、写时间、异常事件等;3.关心哪一个事件,就将文件描述符添加到对应的参数位图结构里;4.也是输入输出型参数,输入时,将fd添加到对应参数fd集合里就会让操作系统关心对应的事件,返回时,会告诉用户那些文件描述符就绪;5.比特位的位置表示的是文件描述符的编号,比特位从低到高对应文件描述符从小到大;输入时1/0表示是否对应文件描述符是否关心参数对应事件,输出时1/0表示参数对应事件是否就绪;
struct timeval {
    time_t      tv_sec;     /* seconds */
    suseconds_t tv_usec;    /* microseconds */
};
//如下函数就是对fd_set结构进行修改的接口;
void FD_CLR(int fd, fd_set *set);//将位图中fd对应位置变为0;
int  FD_ISSET(int fd, fd_set *set);//判断fd在位图中是否已经被设置;
void FD_SET(int fd, fd_set *set);//将fd添加到位图结构里;
void FD_ZERO(fd_set *set);//将位图清空;

总结:select只负责等待过程中通知事件就绪

3.2代码实现

​ 要注意不能直接accept,因为accept就是阻塞的等待读事件就绪,所以此时应该使用的是select等待多个文件描述符;

​ 注意等待时间是一个输入输出参数,所以要进行周期地重复设置;

​ 如果事件就绪了,但是没有处理事件,那么select就会一直发送消息,并且当读取fd时是不会阻塞的;

​ select与多线程的优势就是在于select单执行流就可以使得大量的fd的等待事件重叠,减少IO过程中等待时间的占比,而多线程也可以使得等待时间重叠,但是要创建多个线程;

#include <iostream>
#include <string>
#include <sys/select.h>
#include "Socket.hpp"
class selectserver
{

public:
    selectserver(const uint16_t port = defaultport) : port_(port) {}
    ~selectserver()
    {
        listensock_.Close();
    }

public:
    bool init()
    {
        listensock_.Socket();
        listensock_.Bind(port_);
        listensock_.Listen();
        return true;
    }
    void run()
    {
        int listensockfd = listensock_.Fd();
        for (;;)
        {
            fd_set rfds;
            FD_ZERO(&rfds);
            FD_SET(listensockfd, &rfds);
            timeval time = {1, 0};
            int n = select(listensockfd + 1, &rfds, nullptr, nullptr, /*nullptr*/ &time);
            switch (n)
            {
            case 0:
                std::cout << "time out, time: " << time.tv_sec << "." << time.tv_usec << std::endl;
                break;
            case -1:
                std::cerr << "select err" << std::endl;
                break;
            default:
                // 表示有事件就绪
                std::cout << "get a new link..." << std::endl;
                handlerevent();
                break;
            }
        }
    }
    void handlerevent()
    {
    }

private:
    Sock listensock_;
    uint16_t port_;
    static const uint16_t defaultport = 8080;
};

相关推荐

  1. 高级IO——阻塞IOselect

    2024-04-13 08:58:02       18 阅读
  2. 阻塞IO

    2024-04-13 08:58:02       8 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-04-13 08:58:02       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-04-13 08:58:02       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-04-13 08:58:02       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-04-13 08:58:02       20 阅读

热门阅读

  1. 设计模式|建造者模式(Builder Pattern)

    2024-04-13 08:58:02       15 阅读
  2. 【DM8】同义词

    2024-04-13 08:58:02       47 阅读
  3. Mysql事务测试

    2024-04-13 08:58:02       20 阅读
  4. LANG 和 LC_ALL两者的区别

    2024-04-13 08:58:02       19 阅读
  5. 10组Python面试高频问题与详尽解答指南

    2024-04-13 08:58:02       22 阅读
  6. 先验概率和后验概率

    2024-04-13 08:58:02       16 阅读
  7. Postgresql获取指定时间前的时间

    2024-04-13 08:58:02       17 阅读
  8. 分享一个Flask+Vue+Leaflet+Pyinstaller+SpatiaLite的应用

    2024-04-13 08:58:02       18 阅读