【Linux】从零开始使用多路转接IO --- select

发布于:2025-05-14 ⋅ 阅读:(13) ⋅ 点赞:(0)

在这里插入图片描述

碌碌无为,则余生太长;
欲有所为,则人生苦短。
--- 中岛敦 《山月记》---

1 前言

上一篇文章我们讲解了五种IO模型的基本概念,并通过系统调用使用了非阻塞IO。
一般的服务器不会使用非阻塞IO,因为非阻塞IO非常耗费CPU资源,导致CPU发热效率下降!非阻塞IO只有在特定情况下才比较好用!

今天我们来学习多路转接select

我们知道IO = 等 + 拷贝。拷贝的前提是底层有数据,没有数据的时候就需要进行等待。为了提高效率可以等待多个文件描述符。多路转接就是等待文件描述符上的新事件,等到就可以通知程序员事件已经就绪,可以进行拷贝!

这个事件可以是:

  • 读事件就绪:OS底层有数据了
  • 写事件就绪:OS底层有空间了

今天我们要学习的就是多路转接select

2 认识多路转接select

我们先来看其作用与定位:

  • select的定位是:只在IO中只负责等待,不进行拷贝! 并且select可以等待多个文件描述符,有新事件就进行通知。

来看select系统调用:

SELECT(2)                                                                 Linux Programmer's Manual                                                                 SELECT(2)

NAME
       select, pselect, FD_CLR, FD_ISSET, FD_SET, FD_ZERO - synchronous I/O multiplexing

SYNOPSIS
       #include <sys/select.h>

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

       void FD_CLR(int fd, fd_set *set);
       int  FD_ISSET(int fd, fd_set *set);
       void FD_SET(int fd, fd_set *set);
       void FD_ZERO(fd_set *set);

       int pselect(int nfds, fd_set *readfds, fd_set *writefds,
                   fd_set *exceptfds, const struct timespec *timeout,
                   const sigset_t *sigmask);

   Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

       pselect(): _POSIX_C_SOURCE >= 200112L

select函数中有5个参数,都是用来干什么的呢?

  1. int nfds:输入性参数 ,表示等待的多个文件描述符最大值 加 1。比如等待1 2 5 6 99 这几个文件描述符,那么就要传入100。注意不是文件描述符的个数!
  2. struct timeval *timeout:输入输出性参数 ,这是一个结构体表示微秒级别的时间戳,其中有两个参数分别表示秒和微秒。这个参数告诉select在这个时间戳内进行阻塞式select,超出时间就进行一次返回。如果时间以内等到了新事件,就返回,并把剩余时间返回。传入{0,0}就是非阻塞轮询了。传入nullptr表示一直阻塞等待事件
    在这里插入图片描述

那么现在我们知道了两个参数,我们探索一下返回值:

  • 大于0:有几个就绪了
  • 等于0:超时返回了
  • 小于0:select出错了

那么其他三个参数呢?首先fd_set代表文件描述符集,是用位图进行维护的!位图下标表示文件描述符,该比特位的内容表示对应信息!一共1024比特位,可以表示1024个文件描述符,下面我们就来了解一下这三个参数:
这三个参数都是fd_set,是输入输出参数,分别对应读事件,写事件,异常事件。通过这三个位图的设置,我们就可以对一个文件描述符的操作指明清楚。今天我们以读事件为例进行讲解:

  • 输入时:传入一个读事件文件描述符,就是告诉OS要帮我们关心fd_set集合中的所有fd的读事件。这里比特位的位置表示文件描述符的编号,比特位的内容表示是否关心fd的读事件!
  • 输出时:OS会返回一个读事件文件描述符,表示你让我关心的文件描述符集中哪些已经就绪了!这里比特位的位置表示文件描述符的编号,比特位的内容表示事件是否发生!

OK,现在我们了解了select的基本参数,下面我们就开始使用select进行编程

3 多路转接select等待连接

我们首先把之前的套接字基础的类拷贝过来:

  1. class Socket:实现套接字的创建工作,并进入监听模式。
  2. class InetAddr:网络套接字基本信息类,用于进行网络套接字传参工作。
  3. class Log:进行日志信息的打印,便于调试

然后我们就来设计Selectsever类:

  • 成员变量需要端口号,TcpSocket套接字类
  • 构造函数中进行端口号的初始化,并创建套接字,设置为监听模式
  • 循环函数中不能直接进行accept获取连接,因为底层不一定有数据,直接进行会阻塞式等待。所以我们可以把accept看做IO函数,将等的任务交给select函数。
  • select函数需要对监听套接字进行等待
#pragma once

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

using namespace socket_ns;
using namespace log_ns;

class SelectServer
{
   
public:
    SelectServer(uint16_t port) 

网站公告

今日签到

点亮在社区的每一天
去签到