高级IO——select模型

1、select介绍

select 模型是一种用于处理 I/O 多路复用的编程模型,广泛应用于网络编程和系统编程中。它允许一个进程监视多个文件描述符,以便在其中一个或多个文件描述符变为可读、可写或发生异常时进行响应。

2、select接口

#include <sys/select.h>

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
  • nfds: 监视的文件描述符的最大值加一。
  • readfds: 监视可读事件的文件描述符集合。
  • writefds: 监视可写事件的文件描述符集合。
  • exceptfds: 监视异常事件的文件描述符集合。
  • timeout: 指定等待的时间,如果为 NULL,则无限期等待。

相关函数接口:

FD_ZERO(fd_set* set);             //清空文件描述符集合
FD_SET(int fd, fd_set *set)       //将文件描述符 fd 添加到集合 set 中
FD_CLR(int fd, fd_set *set)       //从集合 set 中移除文件描述符 fd
FD_ISSET(int fd, fd_set *set)     //检查文件描述符 fd 是否在集合 set 中

3、selectServer

简单的select服务器搭建:

select.hpp

#pragma once

#include <iostream>
#include <cstring>
#include <cerrno>
#include <sys/select.h>
#include <string>
#include "sock.hpp"
#include "err.hpp"
#include "log.hpp"

#define READ_EVENT   (0x1)
#define WRITE_EVENT  (0x1<<1)
#define EXCEPT_EVENT (0x1<<2)

const static uint16_t gport = 8888;
typedef struct Fd
{
    int fd;
    uint8_t event;
    std::string clientIp;
    uint16_t clientPort;

} type_t;
//static const defaultfd = -1;

class selectServer
{
    static const int N = (sizeof(fd_set)*8);
    static const uint8_t defaultevent = 0;

public:
    selectServer(uint16_t port = gport) : _port(port)
    {
    }

    void Init()
    {
        _listensock.Socket();
        _listensock.Bind(_port);
        _listensock.Listen();

        for(int i = 0; i < N; i++)
        {
             fdarry_[i].fd = defaultfd;
             fdarry_[i].event = defaultevent;
             fdarry_[i].clientPort = 0;
        }
    }

    void Recver(int index)
    {
        // ServiceIO
        // BUG
        char buffer[1024];
        ssize_t s = recv(fdarry_[index].fd, buffer, sizeof(buffer) - 1, 0);
        if (s > 0)
        {
            buffer[s - 1] = 0;
            std::cout << "client# " << buffer << std::endl;

            // 发送消息也要被select管理的,TODO
            std::string echo = buffer;
            echo += "[select server echo]";

            send(fdarry_[index].fd, echo.c_str(), echo.size(), 0);
        }
        else
        {
            if (s == 0)
            {
                logMessage(Info, "client quit ... ,fdarry_[i] -> defaultfd: %d->%d", fdarry_[index].fd, defaultfd);
            }
            else
            {
                logMessage(Warning, "recv error ... , fdarry_[i] -> defaultfd: %d->%d", fdarry_[index].fd, defaultfd);
            }
            close(fdarry_[index].fd);
            fdarry_[index].fd = defaultfd;
            fdarry_[index].event = 0;
            fdarry_[index].clientPort = 0;
            fdarry_[index].clientIp.resize(0);
        }
    }

    void accepter()
    {
        std::cout << "有一个新连接来了!\n";
        // Accept,这里进行Accept会不会阻塞呢?不会!
        std::string clientIp;
        uint16_t clientPort;
        int sock = _listensock.Accept(&clientIp, &clientPort);
        if (sock < 0)
            return;
        // 得到对应的sock我们能不能直接进行recv/send? 不能
        // 你怎么直到sock上有数据就绪了呢?不知道,我们需要把sock交给select,让select进行管理
        
        logMessage(Debug, "[%s : %d], sock : %d", clientIp.c_str(), clientPort, sock);
        // 要让select帮我们管理,只要把sock添加到fdarry_[]里面即可
        int pos = 1;
        for (; pos < N; pos++)
        {
            if (fdarry_[pos].fd == defaultfd)
                break;
        }
        if (pos >= N)
        {
            // 说明数组满了
            close(sock);
            logMessage(Warning, "fdarry_ is full!");
        }
        else
        {
            fdarry_[pos].fd = sock;
            //fdarry_[pos].event = READ_EVENT | WRITE_EVENT;
            fdarry_[pos].event = READ_EVENT;
            fdarry_[pos].clientPort = clientPort;
            fdarry_[pos].clientIp = clientIp;
        }
    }

    void HandlerEvent(fd_set& rfds, fd_set& wfds)
    {
        for (int i = 0; i < N; i++)
        {

            if(fdarry_[i].fd == defaultfd) continue;

            if ((fdarry_[i].event & READ_EVENT) && FD_ISSET(_listensock.Fd(), &rfds))
            {
                if (fdarry_[i].fd == _listensock.Fd())
                {
                    accepter();
                }
                else if (fdarry_[i].fd != _listensock.Fd())
                {
                    Recver(i);
                }
            }
            else if((fdarry_[i].event & WRITE_EVENT) && FD_ISSET(_listensock.Fd(), &wfds))
            {
                //TODO
            }
            else 
            {

            }
        }
    }

    void start()
    {
        
        
        fdarry_[0].fd = _listensock.Fd();

        while (true)
        {
            //struct timeval timeout = {2, 0};
            //因为rfds是输入输出型参数,注定了每次都要对rfds重置,必定要知道我历史上有哪些fd,fdarry[N]
            //因为select服务器在运行中,sockfd是一直变化的,所以maxfd也一定一直变化,maxfd也要一直更新,fdarry[]
            fd_set rfds;
            fd_set wfds;
            FD_ZERO(&rfds);
            FD_ZERO(&wfds);
            int maxfd = fdarry_[0].fd;
            for(int i = 0; i < N; i++)
            {
                if(fdarry_[i].fd == defaultfd) continue;
                //合法fd
                if(fdarry_[i].event & READ_EVENT)
                    FD_SET(fdarry_[i].fd, &rfds);
                
                if(fdarry_[i].event & WRITE_EVENT)
                    FD_SET(fdarry_[i].fd, &wfds);

                if(maxfd < fdarry_[i].fd) maxfd = fdarry_[i].fd;
            }
            int n = select(maxfd + 1, &rfds, &wfds, nullptr, nullptr);
            switch (n)
            {
            case 0:
                logMessage(Debug, "timeout : %d : %s", errno, strerror(errno));
                break;
            case -1:
                logMessage(Warning, "%d : %s", errno, strerror(errno));
                break;
            default:
                logMessage(Debug, "有一个就绪事件发生了");
                HandlerEvent(rfds, wfds);
                DebugPrint();
                break;
            }
            sleep(1);
        }
    }

    void DebugPrint()
    {
        std::cout << "fdarry_[] : ";
        for(int i = 0; i < N; i++)
        {
            if(fdarry_[i].fd == defaultfd) continue;
            std::cout << fdarry_[i].fd << " ";
        }

        std::cout << std::endl;
    }

    ~selectServer()
    {
        _listensock.Close();
    }

private:
    uint16_t _port;
    sock _listensock;

    type_t fdarry_[N];
};

sock.hpp : 简单的socket封装

#pragma once
#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <cerrno>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>

#include "log.hpp"
#include "err.hpp"

static const int gbacklog = 32;
const int defaultfd = -1;

class sock
{
    public:
        sock():_sock(defaultfd)
        {

        }
        void Socket()
        {
            _sock = socket(AF_INET, SOCK_STREAM, 0);
            if(_sock == 0)
            {
                logMessage(Fatal, "socket error! code : %d errstring : %s", errno, strerror(errno));
                exit(SOCKET_ERR);
            }

            
        }

        void Bind(const uint16_t &port)
        {
            struct sockaddr_in local;
            memset(&local, 0, sizeof(local));

            local.sin_family = AF_INET;
            local.sin_port = htons(port);
            local.sin_addr.s_addr = INADDR_ANY;

            if(bind(_sock, (struct sockaddr*)&local, sizeof(local)) < 0)
            {
                logMessage(Fatal, "bind error! code : %d errstring : %s", errno, strerror(errno));
                exit(BIND_ERR);
            }
        }

        void Listen()
        {
            if(listen(_sock, gbacklog) < 0)
            {
                logMessage(Fatal, "listen error! code : %d errstring : %s", errno, strerror(errno));
                exit(LISTEN_ERR);
            }
        }

        int Accept(std::string* clientip, uint16_t* clientport)
        {
            struct sockaddr_in temp;
            socklen_t len = sizeof(temp);

            int sock = accept(_sock, (struct sockaddr*)&temp, &len);

            if(sock < 0)
            {
                logMessage(Warning, "accept error! code : %d errstring : %s", errno, strerror(errno));
                
            }
            else 
            {
                *clientip = inet_ntoa(temp.sin_addr);
                *clientport = ntohs(temp.sin_port);
            }
            return sock;
        }

        int Connect(const std::string& serverip, const uint16_t &serverport)
        {
            struct sockaddr_in server;
            memset(&server, 0, sizeof(server));
            server.sin_family = AF_INET;
            server.sin_port = htons(serverport);
            server.sin_addr.s_addr = inet_addr(serverip.c_str());

            return connect(_sock, (struct sockaddr*)&server, sizeof(server));
        }

        int Fd()
        {
            return _sock;
        }

        void Close()
        {
            close(_sock);
        }

        ~sock()
        {
            if(_sock != defaultfd) close(_sock);
        }
    private:
        int _sock;

};

4、总结

优点
  • 高效: 能够同时处理多个 I/O 操作,避免了阻塞和频繁的上下文切换。
  • 简单: API 相对简单,易于使用。
缺点
  • 文件描述符限制select 对文件描述符数量有限制(通常为 1024),在高并发场景下可能不够用。
  • 性能下降: 当监视的文件描述符数量很大时,性能可能会下降,因为每次调用 select 都需要遍历整个文件描述符集合。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值