• Linux高级I/O:非阻塞IO fcntl | 多路转接之select | poll



    全文约 8212 字,预计阅读时长: 24分钟


    fcntl

    • 一个文件描述符, 默认都是阻塞IO。
    #include <unistd.h>
    #include <fcntl.h>
    int fcntl(int fd, int cmd, ... /* arg */ );
    
    //传入的cmd的值不同, 后面追加的参数也不相同.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • fcntl函数有5种功能:
      • 复制一个现有的描述符(cmd=F_DUPFD).
      • 获得/设置文件描述符标记(cmd=F_GETFD 或 F_SETFD).
      • 获得/设置文件状态标记(cmd=F_GETFL 或 F_SETFL).
        • 可以将一个文件描述符设置为非阻塞。
      • 获得/设置异步I/O所有权(cmd=F_GETOWN 或 F_SETOWN).
      • 获得/设置记录锁( cmd=F_GETLK, F_SETLK 或 F_SETLKW).

    基于fcntl, 我们实现一个SetNoBlock函数, 将文件描述符设置为非阻塞。

    void SetNoBlock(int fd) 
    { 
    	 int fl = fcntl(fd, F_GETFL); 
    	 if (fl < 0) 
     	{ 
    	 perror("fcntl");
    	 return; 
    	 }
    	 //像open那样。。。
    	 fcntl(fd, F_SETFL, fl | O_NONBLOCK); 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    轮询方式读取标准输入

    #include
    #include
    #include
    int main()
    {
        set_block(0);
        while (1)
        {
            char buffer[1024];
            ssize_t s= read(0,buffer,sizeof(buffer)-1);
            if(s>0)
            {
                buffer[s]=0;
                cout<<buffer<<endl;
            }
            else//轮询检测直至读事件就绪
            {
                cout<<"i am gauging...."<<endl;
                sleep(1);
                continue;
            }
        }
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    I/O多路转接之select

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

    • select系统调用是用来让我们的程序监视多个文件描述符的状态变化的;
    • 程序会停在select这里等待,直到被监视的文件描述符有一个或多个发生了状态改变
    • select的函数原型如下:
    #include 
    int select(int nfds, fd_set *readfds, fd_set *writefds,
     fd_set *exceptfds, struct timeval *timeout)
    
    struct timeval
    {
    	__time_t tv_sec;        /* Seconds. */
    	__suseconds_t tv_usec;  /* Microseconds. */
    };
    
    //OS提供了一组操作  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的全部位
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 参数
      • nfds是需要监视的最大的文件描述符值+1;
      • rdset,wrset,exset分别对应于需要检测的 可读文件描述符的集合,可写文件描述符的集 合及 异常文件描述符的集合。
        • 系统提供的结构类型,位图结构。
        • 比特位的位置表示是哪一个文件描述符;比特位的内容表示该文件描述符的 读写或异常事件 是否就绪。
      • timeout为结构timeval,用来设置select()的等待时间。
        • NULL:则表示select()没有timeout,select将一直被阻塞,直到某个文件描述符上发生了事件;
        • 0:仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生。轮询检测。
        • 特定的时间值:如果在指定的时间段里没有事件发生,select将超时返回。
    • 函数返回值
      • 执行成功则返回文件描述词状态已改变的个数。
      • 如果返回0代表在描述词状态改变前已超过timeout时间,没有事件就绪。
      • 当有错误发生时则返回-1,错误原因存于errno,此时参数readfds,writefds, exceptfds和timeout的值变成不可预测。
        • EBADF 文件描述词为无效的或该文件已关闭
        • EINTR 此调用被信号所中断
        • EINVAL 参数n 为负值。
        • ENOMEM 核心内存不足

    常用套路格式

    在这里插入图片描述


    简易封装:TCP select服务器

    sock。hpp: 封装创建、绑定,监听。

    #include 
    #include 
    #include 
    #include 
    #include
    #include
    #include
    using std::cerr;
    using std::cin;
    using std::cout;
    using std::endl;
    
    namespace ns_sock
    {
        enum
        {
            SOCK_ERR = 2,
            BIND_ERR,
            LISTEN_ERR
        };
    
        const int g_backlog = 5;
    
        struct sock_package
        {
            static int SockCreate()
            {
                int sock = socket(AF_INET, SOCK_STREAM, 0);
                if (sock < 0)
                {
                    cerr << "socket  failed" << endl;
                    exit(SOCK_ERR);
                }
                //设置可以立即重启绑定。
                int opt = 1;
                setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
                return sock;
            }
            static void Bind(const int &sock_fd, uint16_t &port)
            {
                struct sockaddr_in local;
                bzero(&local,sizeof(local));
                local.sin_addr.s_addr=INADDR_ANY;
                local.sin_port=htons(port);
                local.sin_family=AF_INET;
                if(bind(sock_fd,(sockaddr*)&local,sizeof(local))<0)
                {
                    cerr<<"Bind Error"<<endl;
                    exit(BIND_ERR);
                }
            }
            static void Listen(const int& sock_fd)
            {
                if(listen(sock_fd,g_backlog)<0)
                {
                    cerr<<"Listen Error"<<endl;
                    exit(LISTEN_ERR);
                }
            }
        };
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    slt(select)。hpp:循环获取连接通信,对文件描述符集的处理、select()参数的处理。

    • 需要有一个数组保存已经设置过的文件描述符集。
    • 每次要获取从上述的数组(文件描述符集)中获取一个最大值,作为select()的第一个参数;同时要将上述数组重新设置进文件描述符集当中。
    • 当某一个读事件就绪时:
      • 新连接到来?是,则需要将其插入到上述的数组中。
      • 接收缓冲区有客户发送数据过来?是则正常读取。
    #include "sock.hpp"
    #include 
    namespace ns_slect
    {
        using namespace ns_sock;
        const int g_port = 8080;
        using std::ostream;
    #define NUM (sizeof(fd_set)*8) //字节*比特位可以设置多少个fd
        class slt_pack
        {
        private:
            /* data */
            int _listen_sock;
            uint16_t _port;
            int fd_array[NUM];
    
        public:
            slt_pack(int port = g_port) : _listen_sock(-1), _port(g_port)
            {
                for (size_t i = 0; i < NUM; i++)
                {
                    fd_array[i] = -1;
                }
            }
            ~slt_pack()
            {
                if (_listen_sock >= 0)
                {
                    close(_listen_sock);
                }
            }
            void Init()
            {
                _listen_sock = sock_package::SockCreate();
                sock_package::Bind(_listen_sock, _port);
                sock_package::Listen(_listen_sock);
                fd_array[0] = _listen_sock;
                //cout << "the first sock" << fd_array[0] << endl;
                cout << "Init Sucess" << endl;
            }
    
            ostream &print_fd_arr()
            {
                for (size_t i = 0; i < NUM; i++)
                {
                    if (fd_array[i] != -1)
                    {
                        cout << fd_array[i] << " ";
                    }
                }
                return cout;
            }
            void Hander(const fd_set &rfds)
            {
                for (size_t i = 0; i < NUM; i++)
                {
                    if (fd_array[i] != -1)
                    {
                        if (FD_ISSET(fd_array[i], &rfds))
                        {
                            if (fd_array[i] == _listen_sock) //表示监听套接字读事件就绪---》新连接到来。
                            {
                                sockaddr_in peer;
                                socklen_t len = sizeof(peer);
                                int sock = accept(_listen_sock, (sockaddr *)&peer, &len);
                                if (sock < 0)
                                {
                                    cout << "accept error" << endl;
                                }
                                else
                                { //添加到 rfds 中
                                    int j = 1;
                                    for (; j < NUM; j++)
                                    {
                                        if (fd_array[j] == -1)
                                        {
                                            fd_array[j] = sock;
                                            break;
                                        }
                                    }
                                    if (j == NUM)
                                    {
                                        cout << " rfds is full" << endl;
                                        close(sock);
                                    }
                                    else
                                    {
                                        cout << "here is a  new sock : " << sock << endl;
                                        print_fd_arr() << "....present array" << endl;
                                    }
                                }
                            }
                            else
                            {
                                //这里一次不一定读取完,有粘包以及每个客户端都要创建一个缓冲区?等等问题后续有待解决。
                                char buffer[1024];
                                ssize_t s = recv(fd_array[i], buffer, sizeof(buffer), 0);
                                if (s > 0)
                                {
                                    buffer[s] = 0;
                                    cout << "client says: " << buffer << " .." << endl;
                                }
                                else if (s == 0)
                                {
                                    cout << "Socket for client exit: " << fd_array[i] << endl;
                                    cout << "client closed.." << endl;
                                    close(fd_array[i]);
                                    //从文件集中去除该套接字
                                    fd_array[i] = -1;
                                    print_fd_arr() << "....present array" << endl;
                                }
                                else
                                {
                                    cerr << "recv error" << endl;
                                }
                            }
                        }
                    }
                }
            }
    
            void Loop()
            {
                fd_set rfds;
    
                while (true)
                {
                    FD_ZERO(&rfds);
                    int max_fd = -1;
                    for (size_t i = 0; i < NUM; i++)
                    {
                        if (fd_array[i] == -1)
                        {
                            continue;
                        }
                        FD_SET(fd_array[i], &rfds);
                        if (max_fd < fd_array[i])
                        {
                            max_fd = fd_array[i];
                        }
                    }
                    // struct timeval timeout ={0,0};
                    int n = select(max_fd + 1, &rfds, nullptr, nullptr, nullptr);
                    switch (n)
                    {
                    case 0:
                        cout << "timeout gg...." << endl;
                        break;
                    case -1:
                        cerr << "select error..." << endl;
                    default:
                        //至少有一个成功
                       // cout<<" At least one ready event is read !..."<
                        Hander(rfds);
                        break;
                    }
                }
            }
        };
    }
    ----//slt,cc
    #include"slt.hpp"
    using namespace ns_slect;
    
    int main()
    {
        slt_pack slp;
        slp.Init();
        slp.Loop();
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171

    select的特点

    • 可监控的文件描述符个数取决与sizeof(fd_set)的值. 我这边服务器上sizeof(fd_set)=512,每bit表示一个文件描述符,则我服务器上支持的最大文件描述符是512*8=4096。
    • fd 加入 select 监控集的同时,还要再使用 一个数据结构 array 保存放到 select 监控集中的 fd。
      • 一是用于再 select 返回后,array作为源数据 和 fd_set进行 FD_ISSET 判断。
      • 二是 select 返回后会把以前加入的但并无事件发生的 fd清空,则每次开始 select前 都要重新从 array取得fd 逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个参数。
    • fd_set的大小可以调整,可能涉及到重新编译内核.

    select缺点

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

    I/O多路转接:poll

    poll函数接口

    #include 
    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 */
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 参数说明
      • fds是一个poll函数监听的结构列表. 每一个元素中, 包含了三部分内容: 文件描述符, 监听的事件集合, 返回的事件集合.
      • nfds表示fds数组的长度.
      • timeout表示poll函数的超时时间, 单位是毫秒(ms).
    • 返回结果
      • 返回值小于0, 表示出错;
      • 返回值等于0, 表示poll函数等待超时;
      • 返回值大于0, 表示poll由于监听的文件描述符就绪而返回.

    eventsrevents的取值:
    在这里插入图片描述


    简易封装:TCP poll服务器

    pl。hpp 和 pl。cc;sock。hpp 同select的一样

    #include "sock.hpp"
    #include 
    namespace ns_poll
    {
        using namespace ns_sock;
        const int g_port = 8080;
    #define NUM 1024
    
        class poll_pack
        {
        private:
            /* data */
            int _listen_sock;
            uint16_t _port;
            pollfd _pfds[NUM]; //设置1024个文件描述符的pollfd结构体。
        public:
            poll_pack(int port = g_port) : _listen_sock(-1), _port(g_port)
            {
                for (size_t i = 0; i < NUM; i++)
                {
                    _pfds[i].fd = -1;
                    _pfds[i].events = 0;
                    _pfds[i].revents = 0;
                }
            }
            ~poll_pack()
            {
                if (_listen_sock >= 0)
                {
                    close(_listen_sock);
                }
            }
            void Init()
            {
                _listen_sock = sock_package::SockCreate();
                sock_package::Bind(_listen_sock, _port);
                sock_package::Listen(_listen_sock);
    
                _pfds[0].fd = _listen_sock;
                _pfds[0].events = POLLIN;
                cout << "Init Sucess...." << endl;
            }
    
            void Hander()
            {
                for (size_t i = 0; i < NUM; i++)
                {
                    if (_pfds[i].fd != -1)
                    {
                        if (_pfds[i].revents & POLLIN) //读事件就绪
                        {
                            if (_pfds[i].fd == _listen_sock) //表示监听套接字读事件就绪---》新连接到来。
                            {
                                cout<<"新连接来了!...."<<endl;
                                sockaddr_in peer;
                                socklen_t len = sizeof(peer);
                                int sock = accept(_listen_sock, (sockaddr *)&peer, &len);
                                if (sock < 0)
                                {
                                    cout << "accept error" << endl;
                                }
                                else
                                { //添加到 pfds 中
                                    int j = 1;
                                    for (; j < NUM; j++)
                                    {
                                        if (_pfds[j].fd == -1)
                                        {
                                            break;               
                                        }
                                    }
                                    if (j == NUM)
                                    {
                                        //扩容
                                        cout << " pfds is full" << endl;
                                        close(sock);
                                    }
                                    else
                                    {
                                            _pfds[j].fd = sock;
                                            _pfds[j].events = POLLIN;
                                    }
                                }
                            }
                            else
                            {
                                cout<<"读事件就绪....."<<endl;
                                char buffer[1024];
                                ssize_t s = recv(_pfds[i].fd, buffer, sizeof(buffer) - 1, 0);
                                if (s > 0)
                                {
                                    buffer[s] = 0;
                                    cout << "client says: " << buffer << " .." << endl;
                                }
                                else if (s == 0)
                                {
                                    cout << "Socket for client exit: " << _pfds[i].fd << endl;
                                    cout << "client closed.." << endl;
                                    _pfds[i].fd=-1;
                                    _pfds[i].events=0;
                                    _pfds[i].revents=0;
                                    close(_pfds[i].fd);
                                }
                                else
                                {
                                    cerr << "recv error" << endl;
                                }
                            }
                        }
                    }
                }
            }
    
            void Loop()
            {
                int timeout = -1; //阻塞式等待
                while (true)
                {
                    int n = poll(_pfds, NUM, timeout);
                    switch (n)
                    {
                    case 0:
                        cout << "timeout gg...." << endl;
                        break;
                    case -1:
                        cerr << "select error..." << endl;
                    default:
                        //至少有一个成功
                        // cout<<" At least one ready event is read !..."<
                        Hander();
                        break;
                    }
                }
            }
        };
    }
    ----//pl.cc
    #include"poll.hpp"
    using namespace ns_poll;
    
    int main()
    {
        poll_pack slp;
        slp.Init();
        slp.Loop();
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147

    poll的优缺点

    poll的优点

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

    poll的缺点

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

    编者展望

    • 呵呵呵呵呵…
  • 相关阅读:
    Rust开发问题汇总
    2022.11.22Longest Ordered Subsequence POJ - 2533
    硫化铅量子点,PbS QDs,近红外PbS量子点的特性(波尔半径大,量子效应显著)
    TFTP协议解析及C/C++代码实现
    跨平台传输结构体的注意事项
    leetcode59. 螺旋矩阵 II
    如何做bug分析 ?bug分析什么 ? 为什么要做bug分析 ?
    Leetcode刷题笔记——二分法
    【31】GPU(下):为什么深度学习需要使用GPU?
    Java设计模式之外观模式
  • 原文地址:https://blog.csdn.net/WTFamer/article/details/126584736