Linux的I/O多路复用机制之--select&poll

1. Linux下的五种I/O模型

1)阻塞I/O(blocking I/O)
2)非阻塞I/O (nonblocking I/O)
3) I/O复用(select 和poll) (I/O multiplexing)
4)信号驱动I/O (signal driven I/O (SIGIO))
5)异步I/O (asynchronous I/O (the POSIX aio_functions))

前四种都是同步,只有最后一种才是异步IO。

五种I/O模型的比较:

2.多路复用--select

系统提供select函数来实现多路复用输入/输出模型。select系统调用是用来让我们的程序监视多个文件句柄的状态变化的。程序会停在select这里等待,直到被监视的文件句柄有一个或多个发生了状态改变。关于文件句柄,其实就是一个整数,我们最熟悉的句柄是0、1、2三个,0是标准输入,1是标准输出,2是标准错误输出。0、1、2是整数表示的,对应的FILE *结构的表示就是stdin、stdout、stderr。

select函数

int select(int maxfd,fd_set *rdset,fd_set *wrset, \  
           fd_set *exset,struct timeval *timeout);

参数说明:

参数maxfd是需要监视的最大的文件描述符值+1;rdset,wrset,exset分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集合及异常文件描述符的集合。struct timeval结构用于描述一段时间长度,如果在这个时间内,需要监视的描述符没有事件发生则函数返回,返回值为0。

下面的宏提供了处理这三种描述词组的方式:
FD_CLR(inr fd,fd_set* set);用来清除描述词组set中相关fd 的位
FD_ISSET(int fd,fd_set *set);用来测试描述词组set中相关fd 的位是否为真
FD_SET(int fd,fd_set*set);用来设置描述词组set中相关fd的位
FD_ZERO(fd_set *set);用来清除描述词组set的全部位

参数timeout为结构timeval,用来设置select()的等待时间,其结构定义如下:

struct timeval  
{  
    time_t tv_sec;//second  
    time_t tv_usec;//minisecond  
};

如果参数timeout设为:

NULL,则表示select()没有timeout,select将一直被阻塞,直到某个文件描述符上发生了事件。

0:仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生。

特定的时间值:如果在指定的时间段里没有事件发生,select将超时返回。

函数返回值:

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

理解select模型:

理解select模型的关键在于理解fd_set,为说明方便,取fd_set长度为1字节,fd_set中的每一bit可以对应一个文件描述符fd。则1字节长的fd_set最大可以对应8个fd。

(1)执行fd_set set; FD_ZERO(&set);则set用位表示是0000,0000。

(2)若fd=5,执行FD_SET(fd,&set);后set变为0001,0000(第5位置为1)

(3)若再加入fd=2,fd=1,则set变为0001,0011

(4)执行select(6,&set,0,0,0)阻塞等待

(5)若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000,0011。注意:没有事件发生的fd=5被清空。

基于上面的讨论,可以轻松得出select模型的特点:

  • 可监控的文件描述符个数取决与sizeof(fd_set)的值。
  • 将fd加入select监控集的同时,还要再使用一个数据结构array保存放到select监控集中的fd,一是用于再select 返回后,array作为源数据和fd_set进行FD_ISSET判断。二是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始 select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个 参数。
  • 可见select模型必须在select前循环array(加fd,取maxfd),select返回后循环array(FD_ISSET判断是否有时间发生)。

select缺点:

(1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大

(2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大

(3)select支持的文件描述符数量太小,默认是1024

3.多路复用--poll

poll与select非常相似,不同之处在于,select使用三个位图来表示三种不同的事件,而poll使用一个 pollfd的指针实现。

poll函数

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

参数说明:

fds:是一个struct pollfd结构类型的数组,其结构如下:

struct pollfd {    int fd; /* file descriptor */
    short events; /* requested events to watch */
    short revents; /* returned events witnessed */
    };

该结构用于存放需要检测其状态的Socket描述符;每当调用这个函数之后,系统不会清空这个数组,操作起来比较方便;特别是对于 socket连接比较多的情况下,在一定程度上可以提高处理的效率;这一点与select()函数不同,调用select()函数之后,select() 函数会清空它所检测的socket描述符集合,导致每次调用select()之前都必须把socket描述符重新加入到待检测的集合中;因 此,select()函数适合于只检测一个socket描述符的情况,而poll()函数适合于大量socket描述符的情况;

nfds:nfds_t类型的参数,用于标记数组fds中的结构体元素的总数量;

timeout:是poll函数调用阻塞的时间,单位:毫秒;如果timeout>0那么poll()函数会阻塞timeout所指定的毫秒时间长度之后返回。如果timeout==0,那么 poll() 函数立即返回而不阻塞,如果timeout==INFTIM,那么poll() 函数会一直阻塞下去,直到所检测的socket描述符上的感兴趣的事件发 生是才返回,如果感兴趣的事件永远不发生,那么poll()就会永远阻塞下去;

返回值:

>0:数组fds中准备好读、写或出错状态的那些socket描述符的总数量;

==0:数组fds中没有任何socket描述符准备好读、写,或出错;此时poll超时,超时时间是timeout毫秒;换句话说,如果所检测的 socket描述符上没有任何事件发生的话,

-1:  poll函数调用失败,同时会自动设置全局变量errno;

poll() 函数的功能和返回值的含义与 select() 函数的功能和返回值的含义是完全一样的,两者之间的差别就是内部实现方式不一样。

4.select实例之网络服务器(poll实现类似)

服务器端

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

#define _MAX_LISTEN_ 5
#define _MAX_SIZE_ 10
#define _BUF_SIZE_ 1024

int fd_arr[_MAX_SIZE_];
int max_fd = -1;

static void init_fd_arr()
{
    int i = 0;
    for(; i < _MAX_SIZE_; ++i)
    {
        fd_arr[i] = -1;
    }
}

static int add_fd_arr(int fd)
{
    int i = 0;
    for(; i < _MAX_SIZE_; ++i)
    {
        if(fd_arr[i] == -1)
        {
            fd_arr[i] = fd;
            return 0;
        }
    }
    return 1;
}

static void remove_fd_arr(int fd)
{
    int i = 0;
    for(; i < _MAX_SIZE_; ++i)
    {
        if(fd_arr[i] == fd)
        {
            fd_arr[i] = -1;
            break;
        }
    }
}

static void reload_fd_arr(fd_set* pset)
{
    int i = 0;
    max_fd = -1;
    for(; i < _MAX_SIZE_; ++i)
    {
        if(fd_arr[i] != -1)
        {
            FD_SET(fd_arr[i], pset);
            if(fd_arr[i] > max_fd)
                max_fd = fd_arr[i];
        }
    }
}

static printf_msg(int i, const char* msg)
{
    printf("client %d # %s\n", fd_arr[i], msg);
}

void Usage(const char* proc)
{
    printf("%s usage: [ip] [port]\n", proc);
}

int startup(const char* _ip, const char* _port)
{
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if(sock < 0)
    {
        perror("socket");
        exit(1);
    }

    int opt = 1;
    if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) 
    {
        perror("setsockopt");
        exit(2);
    }  

    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_port = htons(atoi(_port));
    local.sin_addr.s_addr = inet_addr(_ip);
    if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0)
    {
        perror("bind");
        exit(3);
    }

    if(listen(sock, _MAX_LISTEN_) < 0)
    {
        perror("listen");
        exit(4);
    }

    return sock;
}

int main(int argc, char* argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        return 1;
    }

    int listen_sock = startup(argv[1], argv[2]);

    init_fd_arr();
    add_fd_arr(listen_sock);

    fd_set rfds;
    FD_ZERO(&rfds);
    struct timeval tv = {5, 0};

    while(1)
    {
        reload_fd_arr(&rfds);
        int fds = select(max_fd+1, &rfds, NULL, NULL, NULL);
        switch(fds)
        {
            case -1:
                perror("select");
                exit(5);
                break;
            case 0:
                printf("time out\n");
                break;
            default:
            {
                int index = 0;
                for(; index < _MAX_SIZE_; ++index)
                {
                    if(fd_arr[index] == listen_sock && FD_ISSET(fd_arr[index], &rfds)) //new accept
                    {
                        struct sockaddr_in peer;
                        socklen_t len = sizeof(peer);
                        int new_fd = accept(listen_sock, (struct sockaddr* )&peer, &len);
                        if(new_fd < 0)
                        {
                            perror("accept");
                            exit(6);
                        }

                        printf("get a new client %d -> ip: %s port: %d\n", new_fd, inet_ntoa(peer.sin_addr), ntohs(peer.sin_port));

                        if(1 == add_fd_arr(new_fd))
                        {
                            perror("fd_arr is full\n");
                            close(new_fd);
                            exit(7);
                        }

                        continue;
                    }
                    if(fd_arr[index] != -1 && FD_ISSET(fd_arr[index], &rfds)) //new read fd
                    {
                        char buf[_BUF_SIZE_];
                        memset(buf, ‘\0‘, sizeof(buf));

                        ssize_t _s = read(fd_arr[index], buf, sizeof(buf)-1);
                        if(_s > 0)
                        {
                            buf[_s] = ‘\0‘;
                            printf_msg(index, buf);
                        }
                        else if(_s == 0) //client closed
                        {
                            printf("client %d is closed...\n", fd_arr[index]);
                            FD_CLR(fd_arr[index], &rfds);
                            close(fd_arr[index]); // must before remove!!!
                            remove_fd_arr(fd_arr[index]);
                        }
                        else
                        {}

                    }
                }
            }
            break;
        }
        
    }

    return 0;
}

客户端

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

void Usage(const char* proc)
{
    printf("usage: %s [ip] [port]\n", proc);
}

int main(int argc, char* argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    
    int conn_sock = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in conn;
    conn.sin_family = AF_INET;
    conn.sin_port = htons(atoi(argv[2]));
    conn.sin_addr.s_addr = inet_addr(argv[1]);

    if(connect(conn_sock, (const struct sockaddr*)&conn, sizeof(conn)) < 0)
    {
        perror("connect");
        exit(2);
    }

    char buf[1024];
    memset(buf, ‘\0‘, sizeof(buf));
    while(1)
    {
        printf("please enter # ");
        fflush(stdout);

        ssize_t _s = read(0, buf, sizeof(buf)-1);
        if(_s > 0)
        {
            buf[_s-1] = ‘\0‘;
            write(conn_sock, buf, strlen(buf));
        }
    }

    return 0;
}

程序演示

使用Telnet测试

使用客户端测试

使用浏览器测试


Linux的I/O多路复用机制之--select&poll

时间: 2024-10-10 00:01:32

Linux的I/O多路复用机制之--select&poll的相关文章

Linux的I/O多路复用机制之--epoll

什么是epoll 按照man手册的说法:是为处理大批量句柄而作了改进的poll.它几乎具备了之前所说的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法. epoll的相关系统调用 int epoll_create(int size); 创建一个epoll的句柄.自从linux2.6.8之后,size参数是被忽略的.需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,

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组播模型 线

几种典型的服务器网络编程模型归纳(select poll epoll)

1.同步阻塞迭代模型 同步阻塞迭代模型是最简单的一种IO模型. 其核心代码如下: bind(srvfd); listen(srvfd); for(;;) { clifd = accept(srvfd,...); //开始接受客户端来的连接 read(clifd,buf,...); //从客户端读取数据 dosomthingonbuf(buf); write(clifd,buf)//发送数据到客户端 } 上面的程序存在如下一些弊端: 1)如果没有客户端的连接请求,进程会阻塞在accept系统调用处

Linux select/poll和epoll实现机制对比

关于这个话题,网上已经介绍的比较多,这里只是以流程图形式做一个简单明了的对比,方便区分. 一.select/poll实现机制 特点: 1.select/poll每次都需要重复传递全部的监听fd进来,涉及用户空间和内核直接的数据拷贝. 2.fd事件回调函数是pollwake,只是将本进程唤醒,本进行需要重新遍历全部的fd检查事件,然后保存事件,拷贝到用户空间,函数返回. 3.每次循环都是对全部的监测的fd进行轮询检测,可能发生事件的fd很少,这样效率很低. 4.当有事件发生,需要返回时,也需要将全

Linux下多路复用IO接口epoll/select/poll的区别

select比epoll效率差的原因:select是轮询,epoll是触发式的,所以效率高. Select: 1.Socket数量限制:该模式可操作的Socket数由FD_SETSIZE决定,内核默认32*32=1024. 2.操作限制:通过遍历FD_SETSIZE(1024)个Socket来完成调度,不管哪个Socket是活跃的,都遍历一遍. Poll: 1.Socket数量几乎无限制:该模式下的Socket对应的fd列表由一个数组来保存,大小不限(默认4k). 2.操作限制:同Select.

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

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

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

IO多路复用机制(转)

1.简介 希望通过这篇文章,可以回答以下几个问题? 为什么需要IO多路复用? 什么是IO多路复用机制? IO多路复用的机制该怎么使用? epoll比select/poll相比,优势在哪里? 在了解I/O多路复用之前,先来了解流的概念. 1.1流的概念 一个流可以文件.socket.pipe等可以进行IO操作的内核对象.不管是文件,还是套接字,还是管道,我们都可以把他们看作流. 从流中读取数据或者写入数据到流中,可能存在这样的情况:读取数据时,流中还没有数据:写入数据时,流中数据已经满了,没有空间

I/O多路复用 select poll epoll

I/O多路复用指:通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作. select select最早于1983年出现在4.2BSD中,它通过一个select()系统调用来监视多个文件描述符的数组,当select()返回后,该数组中就绪的文件描述符便会被内核修改标志位,使得进程可以获得这些文件描述符从而进行后续的读写操作. select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点,事实上从现在看来,这也是它所剩不多的优点