linux下select,poll,epoll的使用与重点分析

开发者福利!热门AI工具限时免费用 购周边即赠Coding Plan Lite,Claude Code、Cursor等20+工具畅享,效率翻倍! 阅读详情

好久没用I/O复用了,感觉差不多都快忘完了,记得当初刚学I/O复用的时候花了好多时间,但是由于那会不太爱写博客,导致花很多时间搞明白的东西,依然很容易忘记。俗话说眼过千遍不如手过一遍,的确,在以后的学习中,无论知识的难易亦或是重要程度如何,我都会尽量义博客的形式记录下来,这样即能用博客来督促自己学习,也能加深对知识的理解俩全其美,好了废话不说了。

I/O复用的基本概述

I/O复用技术主要是用来同时监听多个套接字描述符,使得我们的程序大幅度的提高性能,一般如下情况会用到I/O复用技术
(1)程序需要同时处理多个socket
(2)客户端程序需同时处理用户输入和网络连接
(3)TCP服务器要同时处理监听socket和连接socket
(4)服务器要同时处理TCP和UDP请求
(5)服务器要同时处理多个端口

1.select系统调用

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

.ndf参数指定被监听文件描述符个数,它通常被设为select监听的所有文件描述符加1。
.readfds,writefds,exceptfds参数分别指向可读,可写和异常事件,应用程序通过将自己感兴趣的文件描述符加入到对应的集合中去,select调用返回时,内核将修改他们来通知应用程序哪些文件描述符已经就绪,timeout为超时时间,select调用成功返回就绪的文件描述符个数
我们一般使用如下宏来访问fd_set中的位

#include<sys/select.h>
FD_ZERO(fd_set *fdset);     //清除fdset的所有位
FD_SET(int fd,fd_set *fdset);//设置fdset的fd位
FD_CLR(int fd,fd_set *fdset);//清除fdset的位fd
int FD_ISSET(int fd,fd_set *fdset); //测试fdset的位是否被设置

文件描述符就绪条件
(1)socket内核接收缓冲区大于或等于其低水位标志SO_RCVLOWAT.此时我们可以无阻塞的读该socket
(2)socket通信的对方关闭连接,此时对该socket的读操作将返回0
(3)监听socket上有新的连接请求
(4)socket上有未处理的错误
(5)socket的内核发送缓冲区大于其低水位字节SO_SNDLOWAT
(6)socket使用非阻塞connect连接成功或失败之后
(7)socket上有未处理的错误
具体实例
参考伪代码如下

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

int main(void)
{
    if(argc < 3)
    {
        cout<<"参数有误"<<endl;
    }

    char *ip = argv[1];
    int port = atoi(argv[2]);

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

    int listenfd = socket(AF_INET,SOCK_STREAM,0);
    assert(ret != -1);

    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 len = sizeof(client_address); 

    int connfd = accept(listenfd,(struct sockaddr *)&client_address,&len);

    if(connfd < 0)
    {
        cout<<"error"<<endl;
        close(listenfd);
    }

    char buf[1024];
    fd_set readfds;
    FD_ZERO(&readfds);

    while(1)
    {
        bzero(buf,1024);

        FD_SET(connfd,&readfds);

        ret = select(connfd + 1,&readfds,NULL,NULL,NULL);
        if(ret < 0)
        {
            cout<<"error"<<endl;
        }

        //判读可读事件是否发生
        if(FD_ISSET(connfd,&readfds))
        {
            ret = recv(connfd,buf,sizeof(buf) - 1,0);

            if(ret <= 0)
            {
                break;
            }

            cout<<buf<<endl;
        }


    }

    close(listenfd);
    close(connfd);

    return 0;
}

select的特点
select的内部实现调用了poll(下面会写道,所以读者可以先跳过这里,去读poll,然后在回头一起看这个),所有它和poll的特点相同,只是函数接口有所不同,本质一样
poll的特点如下
(1)将用户传入的pollfd数据(对应select的描述符集合)拷贝到内核空间,这个拷贝过程事件复杂度为O(N)
(2)挨个查询每一个文件描述符的状态,如果无就绪的文件描述符,则进程就会挂起等待,知道发生超时或设备驱动再次唤醒它,然后它再次遍历所有的文件描述符,找出发生事件的文件描述符。由于其共遍历2次文件文件描述符,所以其事件复杂度为O(N)
(3)将获得的数据拷贝至用户空间。时间复杂度又是O(N)

2.poll的使用

poll的原型如下

#include<poll.h>
int poll(struct pollfd *fds,nfds_t nfds,int timeout);

其中fds为pollfd类型的结构体数组其结构体定义如下

struct pollfd
{
    int fd;      //要监听文件描述符
    short events;//注册的事件
    short revents;//实际发生的事件,内核填充
}

poll可监听的事件类型如下(只列出了常用的)

事件描述
POLLIN数据可读
POLLOUT数据可写
POLLRDHUBTCP连接被对方关闭,或对方关闭了写操作
POLLHUB挂起,比如管道的写端被关闭
POLLERR错误

nfds为监听的文件描述符个数
timeout为超时事件
索引Poll返回的文件描述符

int ret = poll(fds,MAX_EVENT_NUMBER,-1);

//必须遍历所有文件描述符找到其中的就绪事件(也可以根据已知的就绪个数进行简单的优化)

for(int i = 0;i<MAX_EVENT_NUMBER,i++)
{
    if(fds[i].revents & POLLIN)
    {
        int sock = fds[i];
    }
}

关于poll的特点读者可以回到select去看,前面有写到,因为实在说其和select的内部实现机制是一样的所以没必要多余写

3.epoll的使用

epoll是linux特有的I/O复用函数,他在实现和使用上与其他I/O复用有所不同。它是使用一组函数来完成任务的,其次epoll把用户关心的文件描述符上的事件放在一个内核事件表中。epoll需要使用一个额外的文件描述符来来唯一的标识内核中的这个事件表,文件描述符使用epoll_create()来创建

#include<sys/epoll.h>
int epoll_create(int size);

size参数告诉内核事件表需要多大,该函数返回的文件描述符,将用于接下来所有函数的第一个参数

下面函数用来操作内核事件表

#include<sys/epoll.h>
int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event);

fd为要操作的文件描述符,op参数则指定操作类型,操作类型有如下几种

操作类型具体描述
EPOLL_CTL_ADD往事件表中注册fd上的事件
EPOLL_CTL_MOD修改fd上的注册事件
EPOLL_CTL_DEL删除fd上的注册事件

epoll_event结构体的定义如下

struct epoll_event
{
    _uint32_t events    //epoll事件
    epoll_data_t        //用户数据
}

其中epoll_data_t是个联合体其定义如下

typedef union epoll_data
{
    void *ptr;
    int fd;
    uint32_t u32;
    uint64_t u64;
}epoll_data_t

epoll_wait()函数
epoll事件调用的主要接口就是epoll_wait函数,它在一段超时事件内等待一组文件描述符上的事件

#include<sys/epoll.h>
int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout);

该函数成功时返回就绪的文件描述符个数
如果epoll_wait检测到就绪事件,就将所有的就绪事件赋值到第二个参数epoll_event数组当中,它只用于输出检测到的就绪事件。不想poll的数组参数既用于传入用户注册的事件,又用于输出内核检测的事件,这会极大的降低应用程序索引文件描述符的效率
epoll的使用

int ret = epoll_wait(epollfd,events,MAX_EVENT_NUMBER,-1);
//只需遍历ret个文件描述符
for(int i = 0;i<ret;i++)
{
int sockfd = events[i].data.fd;
}
特别注意epoll对文件描述符有俩中模式LT和ET,ET是epoll的高效模式

epoll的特点
epoll在内核实现中是根据每个fd上的俄callback函数来实现的,只有活跃的fd才会主动调用callback,其他的fd则不会。如果所监控的所有文件描述符基本上都是活跃的那么epoll和select或poll差距不是太大,但是要是所监控的文件描述符只有少数活跃,epoll的效率要远高于他俩

I/O多路复用的实现机制 - poll 用法总结 一、基本知识 poll的多路复用机制select类似,select在本质上没有多大差别,管理多个描述符也是进行轮询(polling),根据描述符的状态进行处理,但是poll没有最大文件描述符数量上的限制。 二、poll函数 poll函数的原型声明: //使用:man 2 poll,查看poll函数的使用帮助信息(CentOS-7.6) #include <poll.h> int poll(struct pollfd *fds, nfds_t nfds, int timeout); 阅读详情

相关推荐

发那科机器人Modbus通讯配置全攻略:从IP设置到信号调试

本文详细介绍了发那科机器人Modbus通讯的配置方法,从基础认知到IP设置、端口配置、信号调试及故障排查。重点讲解了UOP和DI/DO信号的配置技巧,帮助工程师快速实现机器人PLC的稳定通讯,提升自动化系统效率。

weixin_27872611的博客 599

poll使用方法及代码

一、在谈epollpoll网络编程代码之前,首先简单的介绍一下poll函数和epoll函数组 1、poll函数原型 使用此函数的头文件为 #include<poll.h> poll函数原型如下: #include<poll.h> int poll(struct poll_fd *fds, nfds_t nfds, int timeout) 参数解释 (...

weixin_43825537的博客 8790

深入解析uncompyle6反编译pyc文件及常见错误修复指南

本文深入解析了使用uncompyle6工具反编译Python字节码文件(.pyc)的完整流程。针对实践中最高频的KeyError和Python版本范围限制两大错误,提供了详细的修复方案步骤指南,并分享了处理复杂反编译结果的高级技巧安全实践,是进行代码恢复、调试分析的重要参考。

uber9的博客 517

Linux——多路复用之poll

前面我们学习了多路复用的select,知道多路复用的原理select使用方法,但是select也有许多缺点,导致他的效率不算高。今天我们来学习poll使用,看看poll较于select的优势。

kkbca的博客 2136

poll使用详解

1.poll函数 #include <poll.h> int poll(struct pollfd *fds, nfds_t nfds, int timeout); struct pollfd{ int fd; //file descriptor short events; //requested events short revents; //returned events }; //fd:输入输出参数 //nfds:文件描述符个数 //timeout:超时时间 1.1 poll使

吴刀钓鱼的博客 1379

poll() 的用法

需求在某个xxxservice 里要持续读 /dev/sample_dev 节点。也就是在while循环里面,先poll是否可读,如果可读的话就去调用read,如果不可读的话就继续调用poll,如此循环。但是,如果 /dev/sample_dev 节点相关的设备已不再提供数据了(比如说设备拔掉)。那么每次调用poll会timeout,如此循环,空耗CPU资源。此时底层驱动可以给应用层返回特定的值告

www.wowothink.com的专栏 8059

select/poll/epoll底层执行原理

pollepoll 通过“红黑树管理监控对象”+“就绪链表直接返回结果”无需遍历所有监控的 fd(从 O (n) 到 O (1));减少内存拷贝(仅注册时拷贝,而非每次调用)。这使得 epoll 能轻松支撑十万甚至百万级并发连接,成为 Nginx、Redis 等高性能服务器的核心技术。在 Linux 环境下,高并发场景首选 epoll 是业界共识。

2403_85810926的博客 927

epollpollselect的原理和区别

一、什么是epollepoll是一种I/O事件通知机制,是linux 内核实现IO多路复用的一个实现。IO多路复用是指,在一个操作里同时监听多个输入输出源,在其中一个或多个输入输出源可用的时候返回,然后对其的进行读写操作。 epoll有两种工作方式,ET-水平触发 和 LT-边缘触发(默认工作方式),主要区别是: LT,内核通知你fd是否就绪,如果没有处理,则会持续通知。而ET,内核只通知一次。 二、epoll的三个函数 int epoll_create(int size) size参数告诉内核这

wwwvipp的博客 5万+

C++网络编程(selectpollepoll)

一、select多路IO转接 int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout); void FD_ZERO(fd_set *set); //清空文件描述符集合 void FD_SET(int fd, fd_set *set); //将待监听的文件描述符添加到监听集合中 void FD_CLR(int fd, fd_set *set); //将文

钟离默的个人博客 1610

I/O 多路复用机制的演进:selectpollepoll

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

SoulruiA的博客 2451

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

Linux多路复用、selectselect的概念、select的函数使用select的优缺点、pollpoll的概念、poll的函数使用poll的优缺点、epollepoll的概念、epoll的函数使用epoll的优点、epoll工作模式

Crocodile1006的博客 3978

一文读懂selectpollepoll用法

(1)selectpoll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但是selectpoll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。这就是回调机制带来的性能提升。

嵌入式Linux内核的博客 1259

一篇文章讲清楚四种I/O模型 select/poll/epoll

通过同时监测十条线路,但从始至终只有一个,编号为3。

movefisher的博客 2273

Linux多路转接之pollepoll

本文探讨了Linux系统中三种多路I/O复用技术selectpollepoll。首先分析select的局限性,包括使用固定大小的位图导致FD数量受限(默认1024/4096),以及输入输出参数共用导致的效率问题。接着介绍了poll的改进,它通过pollfd结构体数组分离输入输出参数,突破了FD数量限制,但仍需轮询检测就绪事件。最后详细阐述了epoll的高效实现,采用红黑树管理FD、就绪队列和回调机制,支持边缘触发(ET)和水平触发(LT)两种模式,具有无数量限制、高效事件通知等优势。文章还通过具体代

chenyuhao2024的博客 1236

IO复用的三种方法(select,poll,epoll)深入理解,包括三者区别,内部原理实现?

(一)IO复用是Linux中的IO模型之一,IO复用就是进程告诉内核需要监视的IO条件,使得内核一旦发现进程指定的一个或多个IO条件就绪,就通过进程处理,从而不会在单个IO上阻塞了,Linux中,提供了selectpollepoll三种接口来实现IO复用 (二)select: 缺点: 单个进程能够监视的文件描述符的数量存在最大限制,通常是1024。由于select采用轮询的方式扫描文件描述符...

弯弯传奇的博客 1774

NIO底层:IO多路复用Select/Poll/Epoll

IO多路复用 相信各位小伙伴在学习JavaIO知识时会看到其底层实现是使用了操作系统中的IO多路复用。 那么IO多路复用到底是什么呢? 面试题中出现的select/poll/epollpollepoll的区别又指的是什么呢? 今天我就带大家来看下: I/O多路复用(multiplexing)的本质是通过一种机制(系统内核缓冲I/O数据),让单个进程可以监视多个文件描述符,一旦某个描述符就绪(...

weixin_41021172的博客 738

LINUX网络】PollEpoll

了解Poll,深度从原理和代码层面了解epoll机制——从文件描述符打通到tcp/udp再到epoll

2301_79501467的博客 1114

Linux select/poll机制原理详解

1. 概述 Linux系统在访问设备的时候,存在以下几种IO模型: Blocking IO Model,阻塞IO模型; Nonblocking I/O Model,非阻塞IO模型; I/O Multiplexing Model,IO多路复用模型; Signal Driven I/O Model,信号驱动IO模型; Asynchronous I/O Model,异步IO模型; 今天我们来分析下IO多路复用机制,在Linux中是通过select/poll/ep

lx123010的专栏 674

基于 epoll 的高并发服务器原理实现(对比 selectpoll

特点selectpollepollfd 数量限制1024无固定上限无固定上限用户态/内核态拷贝每次都要每次都要只需一次(注册时)时间复杂度O(n)O(n)O(1)(只返回就绪 fd)并发性能一般一般高效(适合上万连接)

qq_64148519的博客 893
上一篇: Linux下TCP/IP协议栈的简单脉络分析
下一篇: gdb调试多线程的简单命令
Shreck66
博客等级 码龄11年 118粉丝 91原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值