I/O多路复用之select

I/O 多路复用之select()、poll()、epoll()详解 I/O 多路复用select==>时间复杂度O(n)poll==>时间复杂度O(n)epoll==>时间复杂度O(1)epoll的接口介绍epoll的工作模式epoll和select/poll的对比epoll的优点 select,poll,epoll都是IO多路复用的机制。I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件 阅读详情

I/O复用使得程序能同时监听多个文件描述符,这对提高程序的性能至关重要。通常,网络程序在下列的情况下需要使用I/O复用技术:

  • 客户端程序要同时处理多个socket。
  • 客户端程序要同时处理用户输入和网络连接。
  • TCP服务器要同时处理监听socket和连接socket。这是I/O多路复用使用最多的场合。
  • 服务器要同时处理TCP请求和UDP请求。
  • 服务器要同时监听多个端口,或者处理多种服务。

需要指出的是,I/O复用虽然能同时监听多个文件描述符,但它本身是阻塞的。


select系统调用的用途是:在一段指定时间内,监听用户感兴趣的文件描述符上的可读,可写和异常等事件。


select()系统调用原型:

#include<sys/select.h>
int select(int nfds,fd_set* readfds,fd_set* writefds,fd_set* exceptfds,struct timeval* timeout);

系统调用select()会一直阻塞,直到一个或多个文件描述符集合成为就绪态。
文件描述符何时满足就绪态,后面我们会讲到。


nfds参数:

nfds参数指定被监听的文件描述符的总数。它通常被设置为select监听的所有文件描述符中的最大值加1,因为文件描述符是从0开始计数的。
该参数让select()变的更有效率,因为此时内核就不用去检查大于这个值的文件描述符号是否属于这些文件描述符集合。

文件描述符集合:

参数readfds,writefds以及exceptfds都是指向文件描述符集合的指针,所指向的数据类型是fd_set结构体。

fd_set结构体定义如下:

 //每个ulong型可以表示多少个bit
#define __NFDBITS (8 * sizeof(unsigned long))   
//socket最大取值为1024
#define __FD_SETSIZE 1024    
//bitmap一共有1024个bit,共需要多少个ulong                                     
#define __FDSET_LONGS (__FD_SETSIZE/__NFDBITS)     

typedef struct {
    unsigned long fds_bits [__FDSET_LONGS];      //用ulong数组来表示bitmap
} __kernel_fd_set;

typedef __kernel_fd_set   fd_set;

由以上定义可见,fd_set结构体仅包含一个整型数组,该数组的每个元素的每一位(bit)标记一个文件描述符。fd_set能容纳的文件描述符数量由FD_SETSIZE指定,在linux上,该常量的值为1024,这就限制了select能同时处理的文件描述符的数量。

数据类型fd_set以位掩码的形式来实现。但是,我们并不需要知道这些细节,因为所有关于文件描述符集合的操作都是通过四个宏来完成的:FD_ZERO(),FD_SET(),FD_CLR(),FD_ISSET()。

#include<sys/select.h>

void FD_ZERO(fd_set *fdset);         //清除fdset的所有位
void FD_SET(int fd,fd_set *fdset);   //设置fdset的位fd
void FD_CLR(int fd,fd_set *fdset);   //清除fdset的位fd

int FD_ISSET(int fd,fd_set *fdset);  //测试fdset的位fd是否被设置

  • readfds是用来检测输入是否就绪的文件描述符集合
  • writefds是用来检测输出是否就绪的文件描述符集合
  • exceptfds是用来检测异常情况是否发生的文件描述符集合

参数readfds,writefds和exceptfds所指向的结构体都是保存结果值的地方。在调用select()之前,这些参数指向的结构体必须初始化(通过FD_ZERO()和FD_SET()),以包含我们感兴趣的文件描述符集合。之后select()调用会修改这些结构体,当select()返回时,它们包含的就是已处于就绪态的文件描述符集合了(就是将三个文件描述符集合中不满足就绪态的文件描述符去掉,留下的都是已经处于就绪态的文件描述符了)。
由于这些结构体会在调用中被修改,如果要在循环中重复调用select(),我们必须保证每次都要重新初始化它们。
之后通过调用FD_ISSET()遍历每个类型的文件描述符集合,找出处于就绪态的文件描述符。
如果我们对某一类型的事件不感兴趣,那么相应的类型文件描述符集合参数可以指定为NULL。


timeout参数:

参数timeout控制着select()的阻塞行为。该参数可指定为NULL,此时select()会一直阻塞。又或者指向一个timeval结构体。
timeval结构体定义如下:

struct timeval{
    time_t tv_sec;              //seconds
    suseconds_t tv_usec;        //microseconds(long int) 微秒

如果结构体timeval的两个域都为0的话,此时select()不会阻塞,它只是简单地轮询指定的文件描述符集合,看看其中是否有就绪的文件描述符并立刻返回。否则,timeout将为select()指定一个等待时间的上限值。
当timeout设为NULL,或其指向的结构体字段非零时,select()将阻塞直到有下列事件发生:

  • readfds,writefds或exceptfds中指定的文件描述符中至少有一个成为就绪态;
  • 该系统调用被信号处理例程中断;
  • timeout中指定的时间上限已超时。


select()的返回值:

  • 返回-1表示有错误发生
  • 返回0表示在任何文件描述符成为就绪态之前select()调用已经超时。此时,每个返回的文件描述符集合将被清空
  • 返回一个正整数表示有1个或多个文件描述符已经到达就绪态。如果同一个文件描述符在readfds,writefds和exceptfds中同时被指定,且它对于多个I/O时间都处于就绪态的话,那么就会被统计多次


文件描述符就绪条件:

  1. 在网络编程中,下列情况下socket可读:

    • socket内核接收缓冲区中的字节数大于或等于其低水位标记SO_RCVLOWAT。此时我们可以无阻塞地读该socket,并且读操作返回的字节数大于0。
    • socket通信的对方关闭连接。此时对该socket的读操作将返回0。
    • 监听socket上有新的连接请求。
    • socket上有未处理的错误。
  2. 下列情况下socket可写:

    • socket内核发送缓冲区中的可用字节数大于或等于其低水位标记SO_SNDLOWAT。此时我们可以无阻塞地写该socket,并且写操作返回的字节数大于0。
    • socket的写操作被关闭。对写操作被关闭的socket执行写操作将触发一个SIGPIPE信号。
    • socket使用非阻塞connect连接成功或者失败(超时)之后。
    • socket上有未处理的错误。
  3. 异常情况就绪:
    术语“异常情况”常常被误解为在文件描述符上出现了一些错误,这并不正确。在Linux上,一个异常情况只会在下面两种情况发生:

    • 连接到处于信包模式下的伪终端主设备上的从设备状态发生了改变。
    • 流式套接字上接收到了带外数据。


代码示例:

上面我们提到过,socket上接收到普通数据和带外数据都将使select返回,但处于不同的就绪状态:前者处于可读状态,后者处于异常状态。
下面的代码使用select同时处理两者数据的一个简单示例。

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>

int main( int argc, char* argv[] )
{
    if( argc <= 2 )
    {
        printf( "usage: %s ip_address port_number\n", basename( argv[0] ) );
        return 1;
    }
    const char* ip = argv[1];
    int port = atoi( argv[2] );
    printf( "ip is %s and port is %d\n", ip, port );

    int ret = 0;
    struct sockaddr_in address;
    bzero( &address, sizeof( address ) );
    address.sin_family = AF_INET;
    inet_pton( AF_INET, ip, &address.sin_addr );
    address.sin_port = htons( port );

    int listenfd = socket( PF_INET, SOCK_STREAM, 0 );
    assert( listenfd >= 0 );

    ret = bind( listenfd, ( struct sockaddr* )&address, sizeof( address ) );
    assert( ret != -1 );

    ret = listen( listenfd, 5 );
    assert( ret != -1 );

    struct sockaddr_in client_address;
    socklen_t client_addrlength = sizeof( client_address );
    int connfd = accept( listenfd, ( struct sockaddr* )&client_address, &client_addrlength );
    if ( connfd < 0 )
    {
        printf( "errno is: %d\n", errno );
        close( listenfd );
    }

    char remote_addr[INET_ADDRSTRLEN];
    printf( "connected with ip: %s and port: %d\n", inet_ntop( AF_INET, &client_address.sin_addr, remote_addr, INET_ADDRSTRLEN ), ntohs( client_address.sin_port ) );

    char buf[1024];
    fd_set read_fds;
    fd_set exception_fds;

    FD_ZERO( &read_fds );
    FD_ZERO( &exception_fds );

    int nReuseAddr = 1;
    setsockopt( connfd, SOL_SOCKET, SO_OOBINLINE, &nReuseAddr, sizeof( nReuseAddr ) );
    while( 1 )
    {
        memset( buf, '\0', sizeof( buf ) );
        //每次调用select前都要重新在read_fds和exception_fds中设置文件描述符connfd,因为事件发生之后,文件描述符集合将被内核修改
        FD_SET( connfd, &read_fds );
        FD_SET( connfd, &exception_fds );

        ret = select( connfd + 1, &read_fds, NULL, &exception_fds, NULL );
        printf( "select one\n" );
        if ( ret < 0 )
        {
            printf( "selection failure\n" );
            break;
        }

        //对于可读事件,采用普通的recv函数读取数据
        if ( FD_ISSET( connfd, &read_fds ) )
        {
            ret = recv( connfd, buf, sizeof( buf )-1, 0 );
            if( ret <= 0 )
            {
                break;
            }
            printf( "get %d bytes of normal data: %s\n", ret, buf );
        }
        //对于异常事件,采用带MSG_OOB标志的recv函数读取带外数据
        else if( FD_ISSET( connfd, &exception_fds ) )
        {
            ret = recv( connfd, buf, sizeof( buf )-1, MSG_OOB );
            if( ret <= 0 )
            {
                break;
            }
            printf( "get %d bytes of oob data: %s\n", ret, buf );
        }

    }

    close( connfd );
    close( listenfd );
    return 0;
}


select存在的问题:

I/O多路复用这个概念被提出来以后, select是第一个实现的。
select 被实现以后,很快就暴露出了很多问题。

  • select 会修改传入的参数数组,这个对于一个需要调用很多次的函数,是非常不友好的。
  • select 如果任何一个sock(I/O stream)出现了数据,select 仅仅会返回,但是并不会告诉你是那个sock上有数据,于是你只能自己一个一个的找,10几个sock可能还好,要是几万的sock每次都找一遍,效率可想而知。
  • select 只能监视1024个链接,参见FD_SETSIZE。

因此,select现在很少有人在使用,更多的被poll和epoll取代。

推荐文章:I/O多路复用之poll

I/O多路复用(select)   在之前写过一篇五种I/O模型,感兴趣的可以去看一下,今天主要讲其中的一种,那就是I/O多路复用。因为I/O多路复用可以使一个进程同时处理多个连接。这对提高程序的性能至关重要。对于IO复用的概念与理解在上文说的挺清楚了。本文主要说实现IO复用的系统调用。   在linux下,实现IO复用的系统调用主要有三个:select、poll 和 epoll,下面我们将对其进行逐一讲解。 selec... 阅读详情

相关推荐

《unix网络编程》使用select的客户服务器端例程

《unix网络编程》第六章,使用select的客户服务器程序

IO多路复用之select

本文讲解了IO多路复用,详细的讲解了select函数,并通过select实现了select服务器程序实例让大家深入了解了select多路复用,希望这篇博客可以让大家有所收获。

qq_52836452的博客 2368

DINOv2 基于自定义数据集进行图片分类预测

(前提是安装好pytorch)1. pip安装2. 下载dinov2提供的预训练模型,需要自行前往网站上查找dinov2模型进行下载。模型需要下载三个文件:下载完成后将上述三个文件放置在本地一个名为dinov2_base的文件夹中即可。

sinat_34770838的博客 6665

IO多路转接(复用)之select

rdset用于保存要检测的原始数据,这个变量不能作为参数传递给select函数,因为在函数内部这个变量中的值会被内核修改,函数调用完毕返回之后,里边就不是原始数据了,大部分情况下是值为1的标志位变少了,不可能每一轮检测,所有的文件描述符都是就行的状态。内核在遍历这个读集合的过程中,如果被检测的文件描述符对应的读缓冲区中没有数据,内核将修改这个文件描述符在读集合fd_set中对应的标志位,改为0,如果有数据那么这个标志位的值不变,还是1。这并不是巧合,而是故意为之。

yulindianzi的博客 307

Unity新手必看:5分钟搞定TextMeshPro中文乱码,告别小方块(附7000+常用字库)

本文详细介绍了Unity中TextMeshPro中文乱码问题的解决方案,包括快速生成中文字体的步骤和优化技巧。通过使用支持中文的.ttf字体文件和自定义字符集,开发者可以在5分钟内解决TMP显示小方块的问题,提升游戏或应用的文本显示效果。

weixin_33095355的博客 362

U盘 未知USB设备 设定地址失败 由于该设备有问题Windows 已将其停止(代码 43) 终极解决方案(做过系统装机盘而无法解决的必看)

U盘由于该设备有问题Windows 已将其停止(代码 43) 终极解决方案 我们在使用U盘的时候偶尔会碰到下列情况 一般是因为传输数据的过程中,死机或未响应直接断点或拔掉设备导致的,U盘再次插上之后出现设定地址失败。无法再次读取设备的数据。 解决方案: 首先请确认出现该情况不是因为你摔了U盘或接口处产生断裂这种物理损伤导致的!!! 首先请确认出现该情况不是因为你摔了U盘或接口处产生断裂这种物理损...

qq_39123987的博客 13万+

【网络编程】IO 多路复用 select

文章以【网络编程】TCP socket 单机通信实验 为基础,尝试对 IO 多路复用 select 接口进行使用

_山月 2788

【嵌入式Linux】EC20模块多运营商自适应PPP拨号实战:从IMSI识别到网络配置

本文详细介绍了在嵌入式Linux系统中,如何基于EC20 4G模块实现多运营商自适应PPP拨号。通过解析SIM卡的IMSI识别运营商,并动态配置对应的APN和拨号参数,结合AT指令交互与健壮的pppd脚本,解决了设备跨地域部署时的网络自动连接问题,提升了物联网设备的部署效率和稳定性。

fox11的博客 900

I/O多路复用-select函数

套接口默认情况都是阻塞的,如connect、accept、recv、recvfrom函数等当进程执行到这些函数时,会被阻塞,直到有对应的事件发生,函数才会返回,进程继续执行。WinSock,select函数有5个参数:第一个参数没有使用,只是为了与FreeBSD兼容;中间3个参数用于描述符集合,属于值-结果参数;最后一个参数可以让进程设置等待的超时时间。进程与系统通过描述符集合来交换信息,调用函数时,进程可以在描述符集合中指定一个或多个需要系统监测的描述符;函数返回时,系统把满足条件的描述符放置到集合中,进

wkk的博客 1040

Python气象数据处理实战:用PyCINRAD轻松绘制雷达PPI图像(附避坑指南)

本文详细介绍了如何使用Python库PyCINRAD处理中国CINRAD系列天气雷达的基数据,并绘制专业的PPI图像。内容涵盖从环境配置、数据读取、核心绘图到高级定制与批量处理的完整流程,并针对安装依赖、中文乱码、数据范围等常见问题提供了实用的避坑指南,帮助气象工作者高效完成雷达数据可视化。

dog123的博客 221

使用静态库在嵌入式系统中进行开发

静态库是一种包含预编译对象代码的文件,它可以与应用程序链接在一起,并在编译时将库的代码复制到最终的可执行文件中。静态库是包含预编译对象代码的文件,可以与应用程序链接在一起,并在编译时将库的代码复制到最终的可执行文件中。需要注意的是,由于嵌入式系统的资源限制,开发人员应该仔细选择需要包含在静态库中的功能,并避免引入不必要的依赖关系。需要注意的是,由于嵌入式系统的资源限制,开发人员应该仔细选择需要包含在静态库中的功能,并避免引入不必要的依赖关系。的静态库文件,可以在嵌入式系统的应用程序中使用它了。

ZrElixir的博客 201

Linux——多路复用之select

在前面,我们学习了五种IO模型,对IO有了基本的认识,知道了select效率很高,可以等待多个文件描述符,那他是如何等待的呢?我们又该如何使用呢?

kkbca的博客 2139

PostgreSQL的学习心得和知识总结(八十二)|深入理解PostgreSQL数据库SQL语句的词法解析和语法解析流程

postgresql 词法解析语法解析流程

孤傲小二~阿沐的博客 5446

深入理解网络 I/O 多路复用:SELECT、POLL

该篇博文主要介绍的是 I/O 模型中的多路复用:SELECT、POLL,简要分析了 I/O 多路复用的模型,通过图解分析的方式告知多路复用所带来的好处「介绍了 SELECT、POLL 函数以及它们的区别」,通过实践代码的方式来分析 I/O 多路复用在系统调用中所涉及到的内核流程代码,同时也当代码编写不当时会给 TCP 挥手带来的问题,最后介绍了上篇 NIO 博文与 I/O 多路复用之间的区别,希望能够得到你的支持

vnjohn 博主 > 主线分享后端领域业务设计知识、源码思想、架构设计 5682

老款 NUC 复活计划:装 Ubuntu 部署 OpenClaw 本地 AI

老款 NUC 变身本地 AI 服务器指南 本文详细介绍了如何将老旧华硕 NUC 设备改造为本地 AI 服务器。主要内容包括: 硬件评估:列出老款 NUC 的最低配置要求(i3 6代/8GB内存/256GB SSD),并提供兼容性检查命令和性能预期表 系统准备:指导备份数据、下载 Ubuntu 22.04 LTS 和制作启动盘的方法(Windows/Linux双平台) 安装流程:从 BIOS 设置到 Ubuntu 安装的完整步骤,包括推荐的分区方案 系统优化:禁用不必要的服务、内核参数调优、Swap 配置和

博客 7607

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

I/O 多路复用select,poll

select 是最早出现也是最通用的 I/O 多路复用机制,几乎所有操作系统都支持它。工作原理你提供三组文件描述符集合(一个用于读事件,一个用于写事件,一个用于异常事件)。select 会阻塞等待,直到这些集合中的任何一个文件描述符准备好进行 I/O 操作(比如有数据可读、可写或者发生错误)。当 select 返回时,它会修改这三个集合,只留下那些已经就绪的文件描述符。你需要遍历所有文件描述符,才能知道具体是哪些就绪了。优点可移植性好:几乎所有 Unix-like 系统都支持。

cyz322649的博客 1401

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

I/O模型:用select实现多路复用I/O(linux下C语言版)

本文介绍了UNIX/Linux下的I/O多路复用技术,重点讲解了select函数的实现原理和应用。文章首先分析了阻塞I/O、非阻塞I/O等不同I/O模型的优缺点,指出多路复用I/O能有效解决多输入输出流处理问题。详细说明了select函数的使用方法,包括参数设置和辅助功能函数,并通过键盘/鼠标监测和客户端/服务器通信两个示例演示了具体实现。最后总结了select函数的特点:支持多种事件监控但存在文件描述符数量限制,并建议进一步了解poll和epoll等其他多路复用技术。

2301_82019104的博客 1347

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

I/O 多路复用之 select 详解

简单来说,I/O 多路复用允许我们在一个线程中同时监听多个 I/O 描述符(如 socket),一旦某个描述符就绪(比如有数据可读),我们就可以处理它,而不用阻塞在某一个 I/O 上。selectpollepoll(Linux 独有)其中select是最基础、最广泛支持的一种方式。select的核心思想是:你告诉内核你“关心”哪些文件描述符,内核会帮你监控它们是否就绪(可读、可写、异常)。你只需要等待,当有事件发生时,再去处理对应的 fd。select。

qq_70260827的博客 1208

I/O多路复用 select 、poll

套接字通信并发如果我们服务器端想实现并发,有两种处理方式,第一种是通过多进程的方式来处理并发,第二种是通过多线程的方式来处理服务器端的并发。【问题】如果服务器端的程序只有一个线程,或者说只有一个进程,怎么能够实现并发呢?【回答】需要使用IO多路复用方式了。这种方式的解决方案一共有三种,第一种是select,第二种是poll,第三种是epoll。select可以跨平台,poll和epoll只能在Linux平台使用。从效率上考虑,epoll的底层是一棵红黑树。

呵呵哒!的博客 604

Select实现非阻塞 I/O 多路复用

select()是 Unix 和类 Unix 系统中用于实现非阻塞 I/O 多路复用的一种机制。它允许程序同时监控多个文件描述符的 I/O 活动,如套接字、管道等。当其中一个文件描述符准备好进行读取或写入时,select()函数会返回,从而让程序可以处理这个活动的文件描述符。下面详细介绍select()的概念、用途、API 以及示例代码。

极客代码 1097

《Linux::select——I/O 多路复用的经典实现​》

在网络编程中,处理并发连接是一个绕不开的话题。当需要同时管理多个客户端连接时,传统的 “一连接一线程” 模式会带来巨大的资源消耗。而 I/O 多路复用技术则提供了一种高效的解决方案,其中 select 作为最经典的实现之一,至今仍在许多场景中发挥着作用。本文将带你深入了解 select 的工作原理、使用方法及优缺点。​

fwfxhhx的博客 868

I/O 多路复用机制的演进:select → poll → epoll

NIO模型不用像BIO那样,每次要启用一个线程去处理一个连接了,而是可以对某个IO事件进行监听,并继续执行其他任务,不需要阻塞等待。的逐步演进和优化,它们在功能上相似(都用于同时监控多个文件描述符的 I/O 事件),但在性能、可扩展性和使用方式上有显著差异。在传统的BIO模型中(同步阻塞型I/O),每一个连接都要对要对应一个线程进行处理,到内核里,让内核来检查是否有网络事件产生,检查的方式很粗暴,,一次是在内核态里,一个次是在用户态里 ,而且还会发生。所以,对于 select 这种方式,需要进行。

SoulruiA的博客 2456

I/O多路复用——select

接下来我们讨论3篇关于I/O多路复用的问题,首先我们来看下select,select是多路复用当中最早的一种I/O复用的最主要的功能就是让程序能够同时去监听多个文件描述符,这样程序的性能就能提高。select介绍我们先来看一下select的接口。 int select(int nfds, fd_set *readfds, fd_set *writefds,

宇哲 936

Linux I/O 多路复用实战:Select/Poll

I/O多路复用是一种高效的I/O处理机制,它通过系统调用允许单个进程或线程同时监控多个文件描述符(File Descriptor)的I/O状态。这项技术的核心思想是让内核帮助应用程序监视多个文件描述符,当其中任何一个或多个文件描述符就绪(可读、可写或出现异常)时,立即通知应用程序进行处理。

Made In SQL 866

I/O多路复用技术(select)的学习总结

这是本人个人学习后进行的笔记,希望能帮助到有需要的朋友。

ocjhwz1314的博客 993
上一篇: markdown中需要转义的字符
下一篇: I/O多路复用之poll
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值