Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【Linux高级IO】掌握Linux高效编程:深入探索多路转接select机制

【Linux高级IO】掌握Linux高效编程:深入探索多路转接select机制

作者头像
Eternity._
发布于 2025-02-27 00:37:37
发布于 2025-02-27 00:37:37
14000
代码可运行
举报
文章被收录于专栏:登神长阶登神长阶
运行总次数:0
代码可运行

前言: Linux作为一个功能强大、灵活多变的操作系统,提供了丰富多样的I/O处理方式。从传统的阻塞I/O到非阻塞I/O,再到更加高效的异步I/O和内存映射I/O,每一种方式都有其独特的适用场景和性能特点。掌握这些高级I/O机制,不仅能够帮助我们更好地理解和优化系统性能,还能在开发高并发、高性能的应用程序时游刃有余。

select机制,则是Linux中处理多路复用I/O的一种经典方法。它允许一个进程同时监视多个文件描述符,以等待其中的任何一个变为可读、可写或有错误条件发生。这种机制极大地提高了I/O处理的灵活性和效率,特别是在处理大量并发连接时,select机制的优势更加明显。

让我们携手踏上这段探索之旅,一同揭开Linux高级I/O与select机制的神秘面纱。

其他高级IO

非阻塞IO,纪录锁,系统V流机制,I/O多路转接(也叫I/O多路复用),readv和writev函数以及存储映射 IO(mmap),这些统称为高级IO,本篇我们则是重点讨论I/O多路转接

非阻塞IO:fcntl

fcntl 是 Linux 系统编程中一个非常重要的函数,全称为 File Control,即文件控制。它提供了对文件描述符的广泛控制,包括复制文件描述符、获取/设置文件描述符标志、获取/设置文件锁以及获取/设置文件描述符的所有者等。fcntl 函数的灵活性使其成为处理文件 I/O 操作时不可或缺的工具

一个文件描述符, 默认都是阻塞IO,函数原型如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <unistd.h>
#include <fcntl.h>

int fcntl(int fd, int cmd, ... /* arg */ );

后面追加的参数根据cmd的值的不同而产生不同

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)

我们现在只需要使用第三个功能,就能满足当前需要,将一个文件描述符设置为非阻塞

实现函数SetNoBlock


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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void SetNoBlock(int fd) 
{
	int fl = fcntl(fd, F_GETFL);
	if (fl < 0) 
	{
		perror("fcntl");
		return;
	}
	fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}
  • 使用F_GETFL将当前的文件描述符的属性取出来
  • 然后再使用F_SETFL将文件描述符设置回去. 设置回去的同时, 加上一个O_NONBLOCK参数,O_NONBLOCK就是设置非阻塞

轮询方式读取标准输入


代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <iostream>
#include <stdio.h>
#include <unistd.h>
#include <cstdlib>
#include <fcntl.h>

void SetNoBlock(int fd)
{
    int fl = fcntl(fd, F_GETFL);
    if (fl < 0)
    {
        std::cerr << "fcntl error" << std::endl;
        exit(0);
    }
    fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}

int main()
{
    SetNoBlock(0);
    while (true)
    {
        char buffer[1024];
        ssize_t s = read(0, buffer, sizeof(buffer) - 1);
        if (s > 0)
        {
            buffer[s] = 0;
            std::cout << "echo# " << buffer << std::endl;
        }
        else if (s == 0)
        {
            std::cout << "end stdin" << std::endl;
            break;
        }
        else
        {
            // 非阻塞等待,如果数据没有准备好就会按照错误返回,s == -1
            // 那我们怎么知道出错的原因是数据没有准备好,还是真的出错了呢?s是怎么区分的?
            // read, recv会以出错的形式告知上层,数据还没有准备好
            if(errno == EWOULDBLOCK)
            {
                std::cout << "OS的底层数据还没有准备好, error: " << errno << std::endl;
                // other
            }
            else if(errno == EINTR)
            {
                std::cout << "IO interrupted by signal, try again" << std::endl;
            }
            else
            {
                std::cout << "read error!" << std::endl;
                break;
            }
        }
        sleep(1);
    }
    return 0;
}

我们不断的去查看数据是否准备好,只要准备好我们就拿走,没有准备好,我们就去做其他事情

I/O多路转接之select

初识select:

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

  • select系统调用是用来让我们的程序监视多个文件描述符的状态变化的
  • 程序会停在select这里等待,直到被监视的文件描述符有一个或多个发生了状态改变

select函数原型


代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <sys/select.h>

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

参数解释:

  • 参数nfds是需要监视的最大的文件描述符值+1
  • rdset,wrset,exset分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集 合及异常文件描述符的集合
  • 参数timeout为结构timeval,用来设置select()的等待时间

参数timeout取值:

  • NULL:则表示select()没有timeout,select将一直被阻塞,直到某个文件描述符上发生了事件
  • 0:仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生
  • 特定的时间值:如果在指定的时间段里没有事件发生,select将超时返回

关于fd_set结构:

这个结构就是一个整数数组, 更严格的说, 是一个 "位图",使用位图中对应的位来表示要监视的文件描述符,用比特位的内容来告诉内核是否关心这个位置的发生事件,其中给出了一组接口来方便操作位图

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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的全部位

关于timeval结构:

timeval结构用于描述一段时间长度,如果在这个时间内,需要监视的描述符没有事件发生则函数返回,返回值为0

函数返回值:

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

错误值可能为:

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

socket的就绪条件


读就绪:

  • socket内核中, 接收缓冲区中的字节数, 大于等于低水位标记SO_RCVLOWAT. 此时可以无阻塞的读该文件描述符, 并且返回值大于0;
  • socket TCP通信中, 对端关闭连接, 此时对该socket读, 则返回0;
  • 监听的socket上有新的连接请求;
  • socket上有未处理的错误;

写就绪:

  • socket内核中, 发送缓冲区中的可用字节数(发送缓冲区的空闲位置大小), 大于等于低水位标记 SO_SNDLOWAT, 此时可以无阻塞的写, 并且返回值大于0
  • socket的写操作被关闭(close或者shutdown). 对一个写操作被关闭的socket进行写操作, 会触发SIGPIPE信号
  • socket使用非阻塞connect连接成功或失败之后
  • socket上有未读取的错误

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的第一个参数。

select的缺点

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

select使用示例


讲了这么多,就让我们用用select正是操作一把,单进程实现多服务器消息交流,体现多路转接的真正实力

SelectServer:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#pragma once

#include <iostream>
#include <string>
#include <sys/select.h>

#include "Log.hpp"
#include "Socket.hpp"

using namespace Net_Work;

const static int gdefaultport = 8888;
const static int gbacklog = 8;
const static int num = sizeof(fd_set) * 8;

class SelectServer
{
private:
    void HandlerEvent(fd_set &rfds)
    {
        for (int i = 0; i < num; i++)
        {
            if (_rfds_array[i] == nullptr)
                continue;

            // 合法的fd
            // 读事件分两种,一类是新链接的到来,一类是新数据的到来
            int fd = _rfds_array[i]->GetSocket();
            if (FD_ISSET(fd, &rfds))
            {
                // 读事件就绪 -> 新链接的到来
                if (fd == _listensock->GetSocket())
                {
                    lg.LogMessage(Info, "get a new link\n");
                    std::string clientip;
                    uint16_t clientport;
                    // 这里不会阻塞,因为select已经检测到listensock就绪了
                    Socket *sock = _listensock->AcceptConnection(&clientip, &clientport);
                    if (!sock)
                    {
                        lg.LogMessage(Error, "accept error\n");
                        return;
                    }
                    lg.LogMessage(Info, "get a client, client info is# %s:%d, fd:%d\n", clientip.c_str(), clientport, sock->GetSocket());
                    // 获取成功了,但是我们不能直接读写,底层的数据不确定是否就绪
                    // 新链接fd到来时,要把新链接fd交给select托管 --- 只需要添加到数组_rfds_array中即可
                    int pos = 0;
                    for (; pos < num; pos++)
                    {
                        if (_rfds_array[pos] == nullptr)
                        {
                            _rfds_array[pos] = sock;
                            break;
                        }
                    }
                    if (pos == num)
                    {
                        sock->CloseSocket();
                        delete sock;
                        lg.LogMessage(Warning, "server is full ... !\n");
                    }
                }
                // 新数据的到来
                else
                {
                    std::string buffer;
                    bool res = _rfds_array[i]->Recv(&buffer, 1024);
                    if(res)
                    {
                        lg.LogMessage(Info, "client say# %s\n", buffer.c_str());
                        buffer.clear();
                    }
                    else
                    {
                        lg.LogMessage(Warning, "client quit, maybe close or error, close fd: %d\n", _rfds_array[i]->GetSocket());
                        _rfds_array[i]->CloseSocket();
                        delete _rfds_array[i];
                        _rfds_array[i] = nullptr;
                    }
                }
            }
        }
    }

public:
    SelectServer(int port = gdefaultport)
        : _port(port), _listensock(new TcpSocket())
    {
    }

    void InitServer()
    {
        _listensock->BuildListenSocketMethod(_port, gbacklog);
        for (int i = 0; i < num; i++)
        {
            _rfds_array[i] = nullptr;
        }
        _rfds_array[0] = _listensock.get();
    }

    void Loop()
    {
        _isrunning = true;

        while (_isrunning)
        {
            // 不能直接accept新连接,而是要将selete交给selete, 只有selete有资格知道IO事件有没有就绪
            fd_set rfds;
            FD_ZERO(&rfds);
            int max_fd = _listensock->GetSocket();
            for (int i = 0; i < num; i++)
            {
                if (_rfds_array[i] == nullptr)
                {
                    continue;
                }
                else
                {
                    int fd = _listensock->GetSocket();
                    FD_SET(fd, &rfds); // 添加所有合法的fd到rfds集合中
                    if (max_fd < fd)   // 更新最大fd
                    {
                        max_fd = fd;
                    }
                }
            }

            // 遍历数组,1.找最大值 2.合法的fd添加到rfds集合中

            // 定义时间
            struct timeval timeout = {0, 0};
            // rfds本质是一个输入输出型参数,rfds是在select调用返回的时候,不断被修改,所以每次都要重置
            PrintDebug();
            int n = select(max_fd + 1, &rfds, nullptr, nullptr, /*&timeout*/ nullptr);
            switch (n)
            {
            case 0:
                lg.LogMessage(Info, "select timeout ... last time: %u.%u\n", timeout.tv_sec, timeout.tv_usec);
                break;
            case -1:
                lg.LogMessage(Error, "select error !!! \n");
            default:
                lg.LogMessage(Info, "select success, begin event handler, last time: %u.%u\n", timeout.tv_sec, timeout.tv_usec);
                HandlerEvent(rfds);
                break;
            }
        }

        _isrunning = false;
    }

    void stop()
    {
        _isrunning = false;
    }

    void PrintDebug()
    {
        std::cout << "current select rfds list is: ";
        for (int i = 0; i < num; i++)
        {
            if (_rfds_array[i] == nullptr)
                continue;
            else
                std::cout << _rfds_array[i]->GetSocket() << " ";
        }
        std::cout << std::endl;
    }

    ~SelectServer()
    {
    }

private:
    std::unique_ptr<Socket> _listensock;
    int _port;
    bool _isrunning;
    Socket *_rfds_array[num];
};

git完整代码链接

总结

虽然说select实现了我们之前从未做到过的功能,select 只负责等待,可以等待多个fd,IO的时候,效率比较高一些,但是对于它的缺点来说,它还是不适合我们使用的

缺点:

  • 1.我们每次都要对select的参数进行重置
  • 2.编写代码的时候,select因为要使用第三方数组,所以充满了遍历。可能会影响select 的效率
  • 3.用户到内核,内核到用户,每次select调用和返回,都要对位图进行重新设置。用户和内核之间,要一直进行效据拷贝
  • 4.select 让OS在底层遍历要关心的所有的fd,这个会造成效率低下
  • 5.fd set:是一个系统提供的类型,fd set大小是固定的,也就是位图个数是固定的,也就是 select最多能够检测的付d总数是有上限的
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int n = select(max_fd + 1, &rfds, nullptr, nullptr, /*&timeout*/ nullptr);

// max_fd + 1 表示的是

正因为这些缺点,select被我们放弃,但我们也不会损失什么,因为后面还有更厉害的工具等待着我们

随着我们一同走过这段关于Linux高级I/O与select机制的学习之旅,我们不难发现,这些技术不仅是系统编程中的关键要素,更是提升应用程序性能和稳定性的有力武器。从非阻塞I/O到异步I/O,从内存映射到文件锁定,再到select机制的多路复用处理,每一项技术都为我们打开了新的视角,让我们能够更加深入地理解和优化系统行为。

最后,让我们携手开启系统编程的新篇章,继续深入探索Linux的奥秘,共同推动技术的进步和发展。在未来的日子里,愿我们都能在技术的海洋中畅游,收获满满的知识与智慧。再见!

希望本文能够为你提供有益的参考和启示,让我们一起在编程的道路上不断前行! 谢谢大家支持本篇到这里就结束了,祝大家天天开心!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-02-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【Linux】从零开始使用多路转接IO --- select
上一篇文章我们讲解了五种IO模型的基本概念,并通过系统调用使用了非阻塞IO。 一般的服务器不会使用非阻塞IO,因为非阻塞IO非常耗费CPU资源,导致CPU发热效率下降!非阻塞IO只有在特定情况下才比较好用!
叫我龙翔
2024/11/04
1190
【Linux】从零开始使用多路转接IO --- select
【Linux高级IO】Linux多路转接:深入探索poll与epoll的奥秘
前言:在现代的Linux网络编程中,高效地管理多个并发连接是服务器性能优化的核心挑战之一。为了应对这一挑战,Linux操作系统提供了多种I/O多路复用技术,其中poll和epoll作为两种重要的机制,在提升系统资源利用率和处理效率方面发挥着关键作用。
Eternity._
2025/03/02
1550
【Linux高级IO】Linux多路转接:深入探索poll与epoll的奥秘
【网络编程】十五、多路转接之 select
​ 系统提供 select 函数来实现多路复用输入/输出模型,这个函数是用来让我们的程序监视多个文件描述符的状态变化的,程序会停在 select 函数中等待,直到被监视的文件描述符有一个或多个发生了状态改变。
利刃大大
2025/05/23
1130
【网络编程】十五、多路转接之 select
IO多路转接之select
多路转接是IO模型的一种,这种IO模型通过select函数进行IO等待,并且select函数能够同时等待多个文件描述符的就绪状态,单个文件描述符的等待与阻塞IO类似。
二肥是只大懒蓝猫
2023/10/13
3300
IO多路转接之select
详解I/O多路转接模型:select & poll & epoll
多路转接是IO模型的一种,这种IO模型通过select、poll或者epoll进行IO等待,可以同时等待多个文件描述符,当某个文件描述符的事件就绪,便会通知上层处理对应的事件。
二肥是只大懒蓝猫
2023/10/13
7110
详解I/O多路转接模型:select & poll & epoll
【Linux网络】多路转接:select、poll、epoll
在Linux中,常见的多路转接/复用有 select、poll 和 epoll 。
_小羊_
2025/03/11
2890
【Linux网络】多路转接:select、poll、epoll
Linux高效IO
IO是什么? 用户在应用层使用read和write的时候,本质就是将数据从用户层写给OS——也就是拷贝函数,所以IO的本质就是进行拷贝+等待(比如没有数据就需要等待,也就是说,要进行拷贝必须先判断条件是否成立,然后在进行读写) 那么什么是高效的IO呢?单位时间内,IO过程中,等的比重越小,IO效率越高。(几乎所有提高IO效率的策略都是让等的比重变小)
有礼貌的灰绅士
2025/04/28
870
Linux高效IO
【计算机网络】select/poll
多路转接属于 IO 复用方式的一种。系统提供 select() 函数来实现多路复用输入/输出模型。select 系统调用是用来让我们的程序监视多个文件描述符的状态变化的。程序会停在 select 这里等待,直到被监视的文件描述符有一个或多个发生了状态改变。
YoungMLet
2024/04/09
1460
【计算机网络】select/poll
【在Linux世界中追寻伟大的One Piece】多路转接select
其实这个结构就是一个整数数组,更严格的说,是一个"位图"。使用位图中对应的位来表示要监视的文件描述符。
枫叶丹
2024/11/07
910
【在Linux世界中追寻伟大的One Piece】多路转接select
【Linux】高级IO --- 多路转接,select,poll,epoll
1. 后端服务器最常用的网络IO设计模式其实就是Reactor,也称为反应堆模式,Reactor是单进程,单线程的,但他能够处理多客户端向服务器发起的网络IO请求,正因为他是单执行流,所以他的成本就不高,CPU和内存这样的资源占用率就会低,降低服务器性能的开销,提高服务器性能。 而多进程多线程方案的服务器,缺点相比于Reactor就很明显了,在高并发的场景下,服务器会面临着大量的连接请求,每个线程都需要自己的内存空间,堆栈,自己的内核数据结构,所以大量的线程所造成的资源消耗会降低服务器的性能,多线程还会进行线程的上下文切换,也就是执行流级别的切换,每一次切换都需要保存和恢复线程的上下文信息,这会消耗CPU的时间,频繁的上下文切换也会降低服务器的性能。前面的这些问题都是针对于服务器来说的,对于程序员来说,多执行流的服务器最恶心的就是调试和找bug了,所以多执行流的服务器生态比较差,排查问题更加的困难,服务器不好维护,同时由于多执行流可能同时访问临界资源,所以服务器的安全性也比较低,可能产生资源竞争,数据损坏等问题。
举杯邀明月
2023/10/17
4870
【Linux】高级IO --- 多路转接,select,poll,epoll
【在Linux世界中追寻伟大的One Piece】poll代码改写
结合select代码,将select server更改成为pollserver,不是一件困难的事情。
枫叶丹
2024/11/11
730
【网络】五种IO模型&&多路转接select/poll/epoll&&Reactor反应堆模式
如果内核还未将数据准备好,系统调用仍然会直接返回,并且返回 EWOULDBLOCK 错误码
用户10925563
2024/12/22
1980
【网络】五种IO模型&&多路转接select/poll/epoll&&Reactor反应堆模式
【网络编程】十六、多路转接之 poll
​ select 和 poll 都是 POSIX 标准规定的多路复用 IO 接口函数,它们都能够让程序同时监视多个文件描述符(如套接字、管道和文件等)的状态,并在有至少一个文件描述符就绪时通知应用程序进行相应的 IO 操作。
利刃大大
2025/05/28
550
【网络编程】十六、多路转接之 poll
【Linux网络#18】:深入理解select多路转接:传统I/O复用的基石
(或称为多任务I/O服务器)是一种高效管理多个I/O操作的技术,允许单线程或单进程同时监控和处理多个I/O事件(如网络套接字、文件描述符等)
IsLand1314
2025/04/03
930
详解I/O多路转接之select
对大量的描述符进行I/O事件监控—可以告诉进程现在有哪些描述符就绪了,然后进行就可以只针对就绪了的描述符进行响应操作,避免对没有就绪的I/O操作所导致的效率降低和流程阻塞。
海盗船长
2020/08/27
9900
【Linux】从零开始使用多路转接IO --- poll
我们对比一下select,select需要传入三个事件集,输入输出性参数,每次都会发生改变!所以才需要每次调用都要进行初始化。而poll使用一个结构体,对于这个文件描述符有两种事件:requested events 与 returned events!输入输出并不互相干扰!那么就解决了select需要不断初始化的问题。
叫我龙翔
2024/11/04
1340
【Linux】从零开始使用多路转接IO --- poll
高级IO(网络)
非阻塞IO往往需要程序员循环的方式反复尝试读写文件描述符, 这个过程称为轮询. 这对CPU来说是较大的浪费, 一 般只有特定场景下才使用.
ljw695
2025/01/03
620
高级IO(网络)
I/O 多路复用, select, poll, epoll
I/O 是应用程序必然逃不掉的一个话题。大家在计算机基础学习中,学过计组,操作系统和计网,而想要把 I/O 研究深入肯定要将对这三个计算机基础方面有所深入。
ge3m0r
2024/05/16
1170
提升性能的必备技术:Linux网络IO与select详解
IO 即“Input”和“Output”的组合,即输入/输出,IO用来处理设备之间的数据传输。socket/fd也是一种IO。
Lion 莱恩呀
2024/08/10
1800
提升性能的必备技术:Linux网络IO与select详解
Linux下Socket编程(三)——非阻塞select的使用简介
阻塞方式block,顾名思义,就是进程或是线程执行到这些函数时必须等待某个事件的发生,如果事件没有发生,进程或线程就被阻塞,函数不能立即返回。 使用Select就可以完成非阻塞(所谓非阻塞方式non- block,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高)方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况读写或是异常。
用户2929716
2018/08/23
4.4K0
Linux下Socket编程(三)——非阻塞select的使用简介
推荐阅读
相关推荐
【Linux】从零开始使用多路转接IO --- select
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验