I/O多路转接之select

前言

        今天我们要介绍的是实现高效IO采用的方案IO多路转接中的select模型,关于select模型是如何实现高效IO,下面我们就一起来具体看看吧。

1.初始select

系统提供select函数来实现多路复用输入/输出模型.
        select系统调用是用来让我们的程序监视多个文件描述符的状态变化的;
        程序会停在select这里等待,直到被监视的文件描述符有一个或多个发生了状态改变;

2.select函数原型

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

参数介绍:

参数nfds是需要监视的最大的文件描述符值+1;
参数timeout为结构timeval,用来设置select()的等待时间

NULL:则表示select ()没有timeout, select将一 直被阻塞, 直到某个文件描述符上发生了事件;

timeout包含字段:

I/O多路转接之select_第1张图片

struct timeval timeout = {0, 0} ->表示非阻塞
struct timeval timeout = {5, 0} >5秒以内阻塞式,超过5秒,非阻塞返回一次 

rdset,wrset,exset分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集合及异常文件描述符的集合;
fd_ set:位图结构,表示文件描述符的集合
rdset,wrset,exset都是输入输出型参数:
输入:表示用户告诉内核,哪些fd上的事件需要内核关心
比特位的位置,表示fd的数值,比特位的内容,表示是否需要关系
输出:内核告诉用户,关心的多个fd中,有哪些E经就绪了
比特位的位置,表示fd的数值,比特位的内容,表示哪些fd上面对应的事件已经就绪了

让用户和内核进行沟通
提供了一组操作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的全部位

函数返回值:
执行成功则返回文件描述词状态已改变的个数
如果返回0代表在描述词状态改变前已超过timeout时间,没有返回
当有错误发生时则返回-1,错误原因存于errno,此时参数readfds,writefds, exceptfds和timeout的
值变成不可预测。

3.select服务器编写

说明:测试使用select,只监听读事件是否就绪,不关心写事件就绪和异常事件就绪,对收到的数据不进行判段是否读取完整,默认一次读取一个完整的报文。

逻辑流程图:

I/O多路转接之select_第2张图片

代码实现:

selectSever.hpp

#pragma once
#include
#include
#include"sock.hpp"
namespace ns
{
    class selectServer
    {
        static const int defaultport = 8080;
        static const int fdnum = sizeof(fd_set)*8;
        static const int defaultfd = -1;
        using func_t = std::function;
    public:
        selectServer(func_t f,int port = defaultport)
        :fun(f),_port(port),_listensock(-1),fdarray(nullptr)
        {}
        void initServer() {
            _listensock = Sock::Socket();
            Sock::Bind(_listensock,_port);
            Sock::Listen(_listensock);
            fdarray = new int[fdnum];
            for(int i = 0; i < fdnum; ++i) fdarray[i] = defaultfd;
            fdarray[0] = _listensock;
        }
        void Accepter(int listensock) {
             //走到这里,说明已经准备就绪,accept不会阻塞
                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);
                // sock我们能直接recv/read 吗?不能,整个代码,只有select有资格检测事件是否就绪
                // 将新的sock 托管给select!
                // 将新的sock托管给select的本质,其实就是将sock,添加到fdarray数组中即可!
                int i = 0;
                for(;i < fdnum; ++i) {
                    if(fdarray[i] != defaultfd) continue;
                    else break;
                }
                if(i == fdnum) {
                    logMessage(WARNING, "server if full, please wait");
                    close(sock);
                }
                else fdarray[i] = sock;
                logMessage(DEBUG, "Accepter out");
        }
        void Recver(int sock,int pos) {
            logMessage(DEBUG, "in Recver");
            //1.读取request
            char buffer[1024];
            ssize_t s = recv(sock,buffer,sizeof(buffer)-1,0);
            if(s > 0) {
                buffer[s] = 0;
                logMessage(NORMAL,"client say# %s",buffer); 
            } else if (s == 0) {
                close(sock);
                fdarray[pos] = defaultfd;
                logMessage(NORMAL,"client quit");
            } else {
                close(sock);
                fdarray[pos] = defaultfd;
                logMessage(ERROR,"client quit: %s",strerror(errno));
            }
            //处理request:
            std::string response = fun(buffer);
            write(sock,response.c_str(),response.size());
        }
        void HandlerEvent(fd_set& rfds) {
            //HandlerEvent 不仅仅是只有一个fd是就绪的,可能有多个
            for(int i = 0; i < fdnum; ++i) {
                if(fdarray[i] == defaultfd) continue;
                //正常的fd,但是不一定就绪了
                if(FD_ISSET(fdarray[i],&rfds) && fdarray[i] == _listensock) Accepter(_listensock);
                else if(FD_ISSET(fdarray[i],&rfds)) Recver(fdarray[i],i);
                else {} //没有就绪
            }
        }
        void start() {
            for(;;) {
                fd_set rfds;
                FD_ZERO(&rfds);
                int maxfd = fdarray[0];
                for(int i = 0; i < fdnum; ++i) {
                    if(fdarray[i] == defaultfd) continue;
                    //合法fd全部添加到读文件描述符集中:
                    FD_SET(fdarray[i],&rfds);
                    if(maxfd < fdarray[i]) maxfd = fdarray[i];
                }
                // struct timeval timeout = {0,0};
                // 一般而言,要是用select,需要程序员自己维护一个保存所有合法fd的数组!
                int n = select(maxfd+1,&rfds,nullptr,nullptr,nullptr);
                switch (n)
                {
                case 0:
                    logMessage(NORMAL, "timeout...");
                    break;
                case -1:
                    logMessage(WARNING, "select error, code: %d, err string: %s", errno, strerror(errno));
                    break;
                default:
                    // 说明有事件就绪了,目前只有一个监听事件就绪了
                    logMessage(NORMAL, "get a new link...");
                    HandlerEvent(rfds);
                    break;
                }
            }
        }
        ~selectServer() {}
    private:
        int _port;
        int _listensock;
        int* fdarray;
        func_t fun;
    };
}

注:

a.不知道一次有多少文件描述符就绪,所以需要借助一个数组管理文件描述符,每次遍历寻找有哪些文件描述符处于就绪状态

b.因为输入输出型参数,第一次添加到读文件描述符集合中的事件没有就绪,内核就会清空对该文件描述符的关心,所以每次都要重新设置文件描述符

c.监控文件描述符大小受限fd_set,该数据类型大小为128byte,即1024个比特位,所以select最多可以监控1024文件描述符

4.select的缺点

1.select能同时等待的文件fd是有上限的
2.必须借助第三方数组维护fd
3.select的大部分参数是输入输出型的,调用select前,要重新设置所有的fd,调用之后,还要检查更新所有的fd,带来了遍历的成本
4.select为什么第一个参数是 最大fd+ 1呢?确定遍历范围
5.select采用位图,用户->内核 内核- >用户 进行数据拷贝,拷贝成本问题

总结

        select可以一次对多个文件描述符设置关心状态,当有事件就绪之后,通知上层处理事件,可以减少等的时间,进而提高IO效率,但是select同时存在许多的缺点,关于等待文件描述符有上限的问题和每次调用都要重新设置关心的fd的问题,会在下一篇poll模型的介绍中进行解决,欢迎大家继续来阅读,今天的内容就介绍这么多了,感谢大家的阅读,我们下次再见!

你可能感兴趣的:(sql,数据库)