多路复用I/O select()

select(),poll(),epoll()的总结:http://www.cnblogs.com/Anker/p/3265058.html

在socket编程中,仅仅使用connect,accept、这些带有阻塞(block)的程序时,如果没有某个时间来满足条件,就会一直处于阻塞状态。可想而知在一些应用中是不能满足要求的,因此就有了select,poll,epoll这些非阻塞的程序来满足需求。这些程序在进程或者线程中执行时不必非要等待事件的发生,一旦执行肯定会有返回值,不同的值反映不同的函数执行情况。如果事件发生则与阻塞方式相同,若事件没有发生则返回一个代码告知事件未发生。而进程或线程继续执行,从而提高效率,它们能够监视我们需要的文件描述符的变化情况——读、写就绪或是异常。

select();

函数原型:

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

先说明两个结构体:
第一,struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符(filedescriptor),即文件句柄,这可以是我们所说的普通意义的文件,当然Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内,所以毫无疑问一个socket就是一个文件,socket句柄就是一个文件描述符。fd_set集合可以通过一些宏由人为来操作,比如清空集合FD_ZERO(fd_set *),将一个给定的文件描述符加入集合之中FD_SET(int ,fd_set*),将一个给定的文件描述符从集合中删除FD_CLR(int,fd_set*),检查集合中指定的文件描述符是否可以读写FD_ISSET(int ,fd_set* )。

第二,struct timeval是一个大家常用的结构,用来代表时间值,有两个成员,一个是秒数,另一个是毫秒数。

具体解释select的参数:
int maxfdp是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错!在Windows中这个参数的值无所谓,可以设置不正确。

fd_set * readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。

fd_set * writefds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。

fd_set * errorfds同上面两个参数的意图,用来监视文件错误异常。

struct timeval * timeout是select的超时时间,这个参数至关重要,它可以使select处于三种状态:

  第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;

  第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;

  第三,timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。

返回值:返回状态发生变化的描述符总数。
负值:select错误

正值:某些文件可读写或出错

0:等待超时,没有可读写或错误的文件

使用示例:

客户端

#include <stdio.h>
#include "debug.h"
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main(int argc, char **argv)
{
    if(3 != argc)
    {
        printf("Usage: %s <IP> <PORT>\n", argv[0]);
        return -1;
    }

    int sockfd = socket(AF_INET, SOCK_STREAM, 0); //
    if(-1 == sockfd)
        errsys("socket");

    struct sockaddr_in serveraddr = {0};
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argv[2]));
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);//IPv4
    int len = sizeof serveraddr;

    if(-1 == connect(sockfd, (struct sockaddr*)&serveraddr, len))
        errsys("connect");

    char buf[100] = {0};
    while(1)
    {
        printf("mydatabase> ");fflush(stdout);
        gets(buf);
        int ret = write(sockfd, buf, sizeof buf);
        ret = read(sockfd, buf, sizeof buf);
        printf("%s\n", buf);
    }
    close(sockfd);
}

服务器

#include <stdio.h>
#include <stdio.h>
#include "debug.h"
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main()
{
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);     //创建server socket
    if(-1 == listenfd)
        errsys("socket");

    struct sockaddr_in myaddr = {0};
    struct sockaddr_in clientaddr = {0};
    myaddr.sin_family = AF_INET;              //IPV4
    myaddr.sin_port = htons(8888);           //port 8888
    myaddr.sin_addr.s_addr = inet_addr("0.0.0.0"); //INADDR_ANY(监听通过任一一张网卡建立的连接)
    int len = sizeof myaddr;

    if(-1 == bind(listenfd, (struct sockaddr*)&myaddr, len))     //绑定server套接字        errsys("bind");
    if(-1 == listen(listenfd, 10))       //启动监听、监听等待数量为10(并不会阻塞)
        errsys("listen");

    fd_set readfds;
    fd_set writefds;
    FD_ZERO(&readfds);
    FD_ZERO(&writefds);
    FD_SET(listenfd, &readfds);            //将server socket加入到描述符集合中

    fd_set temprfds = readfds;
    fd_set tempwfds = writefds;
    int maxfd = listenfd;                //最大文件描述符为listenfd

#define BUFSIZE 100
#define MAXNFD  1024
    int nready;
    char buf[MAXNFD][BUFSIZE] = {0};

    while(1)
    {
        temprfds = readfds;
        tempwfds = writefds;           

        if(-1 == (nready = select(maxfd+1, &temprfds, &tempwfds, NULL, NULL)))    //阻塞,测试文件描述符集合是否变动//变动文件描述符放在 temprfds和 tempwfds中
            errsys("select");

        if(FD_ISSET(listenfd, &temprfds))           //客户端连接请求
        {
            int sockfd = accept(listenfd, (struct sockaddr*)&clientaddr, &len);      //获得client socket                               //传参回写:len作为参数传入,判断实际长度是否与len相同,还会将实际大小通过len返回            if(-1 == sockfd)
                errsys("accept");
            debug("incoming: %s\n", inet_ntoa( clientaddr.sin_addr) );
            FD_SET(sockfd, &readfds);                        //将client socket加入集合
            maxfd = maxfd>sockfd?maxfd:sockfd;               //更新最大文件描述符
            if(--nready==0)
                continue;
        }

        int fd = 0;
        for(;fd<=maxfd; fd++)
        {
            if(fd == listenfd)                                //客户端连接请求前面已处理
                continue;

            if(FD_ISSET(fd, &temprfds))                        //相关描述符就绪
            {
                int ret = read(fd, buf[fd], sizeof buf[0]);    //从client读取(接收)数据
                if(0 == ret)                                //客户端已关闭或超时
                {
                    close(fd);                                //释放client socket
                    FD_CLR(fd, &readfds);                    //从集合中清除 client socket
                    if(maxfd==fd) --maxfd;                    //更新最大描述符
                    continue;
                }
                FD_SET(fd, &writefds);                        //成功接收数据,将clientfd加入测试集合
            }

            if(FD_ISSET(fd, &tempwfds))                        //向client写入请求就绪
            {
                int ret = write(fd, buf[fd], sizeof buf[0]);
                printf("ret %d: %d\n", fd, ret);
                FD_CLR(fd, &writefds);                        //从集合中清除写请求测试
            }
        }
    }

    close(listenfd);
}
时间: 2024-12-26 02:48:05

多路复用I/O select()的相关文章

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 系统提

39.IO多路复用(用select实现伪并发)

IO多路复用 1.用select实现多端口被多客户端访问的多路复用伪并发 IO多路复用服务端:既读又写 # IO多路复用实现伪并发 用多个IO,可以监听多个文件句柄(socket对象)(一般是可以读了或者可以写了), # 一旦文件句柄出现变化,就可以感应到 # 对于原生的socket 只能处理一个请求,只能监听一个端口 # 1.如何让server端监听两个端口 import socket sk1 = socket.socket() sk1.bind(('127.0.0.1', 8001,)) s

多路复用输入/输出 ---- select

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

五种网络IO模型以及多路复用IO中select/epoll对比

下面都是以网络读数据为例 [2阶段网络IO] 第一阶段:等待数据 wait for data 第二阶段:从内核复制数据到用户 copy data from kernel to user 下面是5种网络IO模型 [阻塞blocking IO] 两阶段全程阻塞 recvfrom -> [syscall -> wait -> copy ->] return OK [非阻塞nonblocking IO] 第一阶段是非阻塞的不断检查是否数据准备好,第二阶段阻塞读取数据 recvfrom -&

多路复用io接口-select()

io多路转接之系统调用 例: select系统调用的多路转接 #include <stdio.h> #include <stdlib.h> #include <sys/ioctl.h> #include <sys/time.h> #include <sys/types.h> #include <unistd.h> int main(int argc, char* argv[]) { fd_set rfds; struct timeva

I/O多路复用模型之select(一)

原理: select函数会等待,直到描述符句柄中有可用资源(可读.可写.异常)时返回,返回值是可用资源(可读/可写/异常等)描述符的个数(>0),0代表超时,-1代表错误.具体到内核大致是:当应用程序调用select() 函数, 内核就会相应调用 poll_wait(), 把当前进程添加到相应设备的等待队列上,然后将该应用程序进程设置为睡眠状态.直到该设备上的数据可以获取,然后调用wake_up()唤醒该应用程序进程.select每次轮训都会遍历所有描述符句柄. 函数接口: int select

linux下多路复用模型之Select模型

Linux关于并发网络分为Apache模型(Process per Connection (进程连接) ) 和TPC , 还有select模型,以及poll模型(一般是Epoll模型) Select模型极其作用:这文章讲述的很好,没必要重述已有的东西,就直接给链接 http://blog.csdn.net/turkeyzhou/article/details/8609360 我的理解: 1 /* According to POSIX.1-2001 */ 2 #include <sys/selec

I/O多路复用模型之select(二) 本文地址:http://www.itpux.com/thre

Select函数实现原理分析(转载) select需要驱动程序的支持,驱动程序实现fops内的poll函数.select通过每个设备文件对应的poll函数提供的信息判断当前是否有资源可用(如可读或写),如果有的话则返回可用资源的文件描述符个数,没有的话则睡眠,等待有资源变为可用时再被唤醒继续执行. 下面我们分两个过程来分析select: 1. select的睡眠过程 支持阻塞操作的设备驱动通常会实现一组自身的等待队列如读/写等待队列用于支持上层(用户层)所需的BLOCK或NONBLOCK操作.当

三种多路复用IO实现方式:select,poll,epoll的区别

select,poll,epoll都是IO多路复用的机制.I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作.但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间. 此时需知道两个概念: 所谓阻塞方式block,顾名思义,就是进程或是线程执行到这些函数时必