【Linux】IO多路复用——select,poll,epoll的概念和使用,三种模型的特点和优缺点,epoll的工作模式

Linux多路复用

  IO多路复用是一种操作系统的技术,用于在单个线程或进程中管理多个输入输出操作。它的主要目的是通过将多个IO操作合并到一个系统调用中来提高系统的性能和资源利用率,避免了传统的多线程或多进程模型中因为阻塞IO而导致的资源浪费和低效率问题。

  在IO多路复用中,通常使用的系统调用有 select()、poll()、epoll() 等,它们允许程序等待多个文件描述符(sockets、文件句柄等)中的任何一个变为可读或可写,然后再进行实际的IO操作。这种模型相比于传统的多线程或多进程模型,具有更高的并发处理能力和更低的系统开销。

在这里插入图片描述
  

1. select

1.1 select的概念

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

  select系统调用是用来让我们的程序监视多个文件描述符的状态变化的;

  程序会停在select这里等待,直到被监视的文件描述符有一个或多个发生了状态改变。

在这里插入图片描述

  

1.2 select的函数使用

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

函数参数:

  nfds:是需要监视的最大的文件描述符值+1。

  readfds:需要检测的可读文件描述符的集合。

  writefds:需要检测的可写文件描述符的集合。

  exceptfds:需要检测的异常文件描述符的集合。

  timeout:为结构体timeval,用来设置select()的等待时间;

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

  当timeout为0:仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生。

  当timeout为特定的时间值:如果在指定的时间段里没有事件发生,select将超时返回。

  其中的可读,可写,异常文件描述符的集合是一个fd_set类型,fd_set是系统提供的位图类型,位图的位置是否是1,表示是否关系该事件。

  例如:

    输入时:假如我们要关心 0 1 2 3 文件描述符

    0000 0000->0000 1111 比特位的位置,表示文件描述符的编号
         比特位的内容 0or1 表示是否需要内核关心

    输出时:

    0000 0100->此时表示文件描述符的编号
         比特位的内容 0or1哪些用户关心的fd 上面的读事件已经就绪了,这里表示2描述符就绪了

  

  系统提供了关于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的值变成不可预测。

  错误值可能为:

  EBADF 文件描述词为无效的或该文件已关闭
  EINTR 此调用被信号所中断
  EINVAL 参数n 为负值。
  ENOMEM 核心内存不足

  

select的执行过程:

  (1)执行fd_set set; FD_ZERO(&set);则set用位表示是0000,0000。

  (2)若fd=5,执行FD_SET(fd,&set);后set变为0001,0000(第5位置为1) 。

  (3)若再加入fd=2,fd=1,则set变为0001,0011 。

  (4)执行select(6,&set,0,0,0)阻塞等待,表示最大文件描述符+1是6,监控可读事件,立即返回。

  (5)若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000,0011。注意:没有事件发生的fd=5被清空。

  

1.3 select的优缺点

select的特点:

  (1)可监控的文件描述符个数取决与sizeof(fd_set)的值。一般大小是1024,但是fd_set的大小可以调整。

  (2)将fd加入select监控集的同时,还要再使用一个数据结构array保存放到select监控集中的fd。

    1. 是用于再select 返回后,array作为源数据和fd_set进行FD_ISSET判断。

    2. 是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个参数。

  

select缺点

  (1)每次调用select, 都需要手动设置fd集合, 从接口使用角度来说也非常不便。

  (2)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大。

  (3)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大。

  (4)select支持的文件描述符数量太小。

  

select使用代码:

#pragma once

#include <iostream>
#include <sys/select.h>
#include <sys/time.h>
#include "Socket.hpp"

using namespace std;

static const uint16_t defaultport = 888;
static const int fd_num_max = (sizeof(fd_set) * 8);
int defaultfd = -1;

class SelectServer
{
   
public:
    SelectServer(uint16_t port = defaultport) : _port(port)
    {
   
        for (int i = 0; i < fd_num_max; i++)
        {
   
            fd_array[i] = defaultfd;
            // std::cout << "fd_array[" << i << "]" << " : " << fd_array[i] << std::endl;
        }
    }
    bool Init()
    {
   
        _listensock.Socket();
        _listensock.Bind(_port);
        _listensock.Listen();

        return true;
    }
    void Accepter()
    {
   
        // 我们的连接事件就绪了
        std::string clientip;
        uint16_t clientport = 0;
        int sock = _listensock.Accept(&clientip, &clientport); // 会不会阻塞在这里?不会
        if (sock < 0) return;
        lg(Info, "accept success, %s: %d, sock fd: %d", clientip.c_str(), clientport, sock);

        // sock -> fd_array[]
        int pos = 1;
        for (; pos < fd_num_max; pos++) // 第二个循环
        {
   
            if (fd_array[pos] != defaultfd)
                continue;
            else
                break;
        }
        if (pos == fd_num_max)
        {
   
            lg(Warning, "server is full, close %d now!", sock);
            close(sock);
        }
        else
        {
   
            fd_array[pos] = sock;
            PrintFd();
            // TODO
        }
    }
    void Recver(int fd, int pos)
    {
   
        // demo
        char buffer[1024];
        ssize_t n = read(fd, buffer, sizeof(buffer) - 1); // bug?
        if (n > 0)
        {
   
            buffer[n] = 0;
            cout << "get a messge: " << buffer << endl;
        }
        else if (n == 0)
        {
   
            lg(Info, "client quit, me too, close fd is : %d", fd);
            close(fd);
            fd_array[pos] = defaultfd; // 这里本质是从select中移除
        }
        else
        {
   
            lg(Warning, "recv error: fd is : %d", fd);
            close(fd);
            fd_array[pos] = defaultfd; // 这里本质是从select中移除
        }
    }
    void Dispatcher(fd_set &rfds)
    {
   
        for (int i = 0; i < fd_num_max; i++) // 这是第三个循环
        {
   
            int fd = fd_array[i
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鳄鱼麻薯球

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值