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

好久没用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 数据可写
POLLRDHUB TCP连接被对方关闭,或对方关闭了写操作
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的效率要远高于他俩

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-04 23:17:00

linux下select,poll,epoll的使用与重点分析的相关文章

Linux下select&amp;poll&amp;epoll的实现原理(一)

最近简单看了一把Linux linux-3.10.25 kernel中select/poll/epoll这个几个IO事件检测API的实现.此处做一些记录.其基本的原理是相同的,流程如下 先依次调用fd对应的struct file.f_op->poll()方法(如果有提供实现的话),尝试检查每个提供待检测IO的fd是否已经有IO事件就绪 如果已经有IO事件就绪,则直接所收集到的IO事件返回,本次调用结束 如果暂时没有IO事件就绪,则根据所给定的超时参数,选择性地进入等待 如果超时参数指示不等待,则

linux下select/poll/epoll机制的比较

select.poll.epoll简介 epoll跟select都能提供多路I/O复用的解决方案.在现在的Linux内核里有都能够支持,其中epoll是Linux所特有,而select则应该是POSIX所规定,一般操作系统均有实现 select: select本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理.这样所带来的缺点是: 1. 单个进程可监视的fd数量被限制,即能监听端口的大小有限. 一般来说这个数目和系统内存关系很大,具体数目可以cat /proc/sys/fs/fil

Linux下select, poll和epoll IO模型的详解(转)

http://blog.csdn.net/tianmohust/article/details/6677985 一).Epoll 介绍 Epoll 可是当前在 Linux 下开发大规模并发网络程序的热门人选, Epoll 在 Linux2.6 内核中正式引入,和 select 相似,其实都 I/O 多路复用技术而已 ,并没有什么神秘的.其实在 Linux 下设计并发网络程序,向来不缺少方法,比如典型的 Apache 模型( Process Per Connection ,简称 PPC ), TP

Linux内核中网络数据包的接收-第二部分 select/poll/epoll

和前面文章的第一部分一样,这些文字是为了帮别人或者自己理清思路的,而不是所谓的源码分析,想分析源码的,还是直接debug源码最好,看任何文档以及书都是下策.因此这类帮人理清思路的文章尽可能的记成流水的方式,尽可能的简单明了. Linux 2.6+内核的wakeup callback机制 Linux 内核通过睡眠队列来组织所有等待某个事件的task,而wakeup机制则可以异步唤醒整个睡眠队列上的task,每一个睡眠队列上的节点都拥有一个 callback,wakeup逻辑在唤醒睡眠队列时,会遍历

Linux统系统开发12 Socket API编程3 TCP状态转换 多路IO高并发select poll epoll udp组播 线程池

[本文谢绝转载原文来自http://990487026.blog.51cto.com] Linux统系统开发12 Socket API编程3 TCP状态转换 多路IO高并发select  poll  epoll udp组播 线程池 TCP 11种状态理解: 1,客户端正常发起关闭请求 2,客户端与服务端同时发起关闭请求 3,FIN_WAIT1直接转变TIME_WAIT 4,客户端接收来自服务器的关闭连接请求 多路IO转接服务器: select模型 poll模型 epoll模型 udp组播模型 线

Linux I/O复用中select poll epoll模型的介绍及其优缺点的比较

关于I/O多路复用: I/O多路复用(又被称为"事件驱动"),首先要理解的是,操作系统为你提供了一个功能,当你的某个socket可读或者可写的时候,它可以给你一个通知.这样当配合非阻塞的socket使用时,只有当系统通知我哪个描述符可读了,我才去执行read操作,可以保证每次read都能读到有效数据而不做纯返回-1和EAGAIN的无用功.写操作类似.操作系统的这个功能通过select/poll/epoll之类的系统调用来实现,这些函数都可以同时监视多个描述符的读写就绪状况,这样,**多

Linux I/O复用中select poll epoll模型的介绍及其优缺点的比較

关于I/O多路复用: I/O多路复用(又被称为"事件驱动"),首先要理解的是.操作系统为你提供了一个功能.当你的某个socket可读或者可写的时候.它能够给你一个通知.这样当配合非堵塞的socket使用时,仅仅有当系统通知我哪个描写叙述符可读了,我才去运行read操作.能够保证每次read都能读到有效数据而不做纯返回-1和EAGAIN的无用功.写操作相似.操作系统的这个功能通过select/poll/epoll之类的系统调用来实现.这些函数都能够同一时候监视多个描写叙述符的读写就绪状况

I/O多路复用之select,poll,epoll简介

一.select 1.起源 select最早于1983年出现在4.2BSD中(BSD是早期的UNIX版本的分支). 它通过一个select()系统调用来监视多个文件描述符的数组,当select()返回后,该数组中就绪的文件描述符便会被内核修改标志位,使得进程可以获得这些文件描述符从而进行后续的读写操作. 2.select的优点 目前几乎在所有的平台上支持,具有良好的跨平台支持. 3.select的缺点 单个进程能够监视的文件描述符的数量存在最大限制.默认情况下,在Linux上单个进程能够打开的最

转一贴,今天实在写累了,也看累了--【Python异步非阻塞IO多路复用Select/Poll/Epoll使用】

下面这篇,原理理解了, 再结合 这一周来的心得体会,整个框架就差不多了... http://www.haiyun.me/archives/1056.html 有许多封装好的异步非阻塞IO多路复用框架,底层在linux基于最新的epoll实现,为了更好的使用,了解其底层原理还是有必要的.下面记录下分别基于Select/Poll/Epoll的echo server实现.Python Select Server,可监控事件数量有限制: 1 2 3 4 5 6 7 8 9 10 11 12 13 14