首页 > 其他 > 详细

select 模型

时间:2014-03-30 08:57:17      阅读:612      评论:0      收藏:0      [点我收藏+]

select 模型

具体的模型名称是:多路复用输入/输出模型

直白的解释:比如说你的设备同时接收来自网络的两路数据,这时你可以建两个socket来接收,用select函数就可以同时监控两个socket的情况,完成两个socket的接收,这就是所谓“多路复用接收”。参考:这里

这里边的FD指的是file descriptor(文件描述符)


系统调用:

#include<sys/select.h> // select

#include<sys/time.h> // struct timeval 

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

select监听文件描述符(也就是文件的句柄)的属性事件,属性包括:可读、可写、和异常。它可以是阻塞也可以是非阻塞的,监听的时候,一般使用while(1)循环。对于结构体fd_set 稍后了解。监听的文件描述符,不局限于网络编程里边的套接字文件描述符,Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内,所以毫无疑问一个socket就是一个文件,socket句柄就是一个文件描述符。

参数介绍:

nfds:

是需要监视的最大的文件描述符值+1,在使用select之前,要记得找到那几个监听的句柄的最大值。

可读:

比如A、B两端通信,A向B发送套接字消息,但是B不知道A何时发,那么就用select监听可读事件,因为可读也就意味着有数据来了。

可写:

而对于可写情况,多数网络编程的时候,不需要使用,因为网络缓冲区一般都很大。例子

异常:

指的是异常条件出现的文件描述符,错误并不包括在里边。

阻塞和非阻塞:

用第五个参数来控制:

1.    timeout=NULL                                   (阻塞:直到有一个fd位被置为1函数才返回)
2.    timeout所指向的结构设为非零时间(等待固定时间:有一个fd位被置为1或者时间耗尽,函数均返回)
3.    timeout所指向的结构,时间设为0  (非阻塞:函数检查完每个fd后立即返回)

返回值:

小于0(-1):表示select函数出现错误,或者因为停止信号而停止,想要得到真正原因,可以通过errno得到,具体参考:这里

等于0:超时

大于0:发生状态变化的句柄的数量


比较好的例子:

特殊情况比如对方通过一个socket句柄发来了紧急数据。如果我们程序里只想检测某个socket是否有数据可读,我们可以这样:
fd_set rdfds; /* 先申明一个 fd_set 集合来保存我们要检测的 socket句柄 */
struct timeval tv; /* 申明一个时间变量来保存时间 */
int ret; /* 保存返回值 */
FD_ZERO(&rdfds); /* 用select函数之前先把集合清零 */
FD_SET(socket, &rdfds); /* 把要检测的句柄socket加入到集合里 */
tv.tv_sec = 1;
tv.tv_usec = 500; /* 设置select等待的最大时间为1秒加500毫秒 */
ret = select(socket + 1, &rdfds, NULL, NULL, &tv); /* 检测我们上面设置到集合rdfds里的句柄是否有可读信息 */
if(ret < 0) perror("select");/* 这说明select函数出错 */
else if(ret == 0) printf("超时 "); /* 说明在我们设定的时间值1秒加500毫秒的时间内,socket的状态没有发生变化 */
else { /* 说明等待时间还未到1秒加500毫秒,socket的状态发生了变化 */
    printf("ret=%d ", ret); /* ret这个返回值记录了发生状态变化的句柄的数目,由于我们只监视了socket这一个句柄,所以这里一定ret=1,如果同时有多个句柄发生变化返回的就是句柄的总和了 */
    /* 这里我们就应该从socket这个句柄里读取数据了,因为select函数已经告诉我们这个句柄里有数据可读 */
    if(FD_ISSET(socket, &rdfds)) { /* 先判断一下socket这外被监视的句柄是否真的变成可读的了 */
        /* 读取socket句柄里的数据 */
        recv(...);    //之前可能还会加ioctl的控制
    }
}

这里边有一点需要格外注意,先要循环读取的时候,要每次进入循环都重新使用FD_ZERO清空集合并用FD_SET将句柄重新设置,因为事件发生后,内核会将文件描述符集合修改。

《Linuc高性能服务器编程》的第九章9.1.3节;里边的一个例子:

bubuko.com,布布扣

bubuko.com,布布扣

bubuko.com,布布扣

bubuko.com,布布扣

bubuko.com,布布扣

bubuko.com,布布扣

对于select整体了解了,了解一下参数里边的类型fd_set :

select()机制中提供一fd_set的数据结构,实际上是一long类型的数组,每一个数组元素都能与一打开的文件句柄(不管是socket句柄,还是其他文件或命名管道或设备句柄)建立联系,建立联系的工作由程序员完成,当调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执行了select()的进程哪一socket或文件发生了可读或可写事件。

fd_set定义为:

typedefstruct
{
 /* XPG4.2 requires this member name.    Otherwise avoid the name 
         from the global namespace.    */
#ifdef __USE_XOPEN 
          __fd_mask fds_bits[__FD_SETSIZE / __NFDBITS]; 
# define __FDS_BITS(set) ((set)->fds_bits) 
#else
          __fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS]; 
# define __FDS_BITS(set) ((set)->__fds_bits) 
#endif
} fd_set;

几个操作它的红定义:

FD_ZERO(&set); /*将set清零使集合中不含任何fd*/

FD_SET(fd, &set); /*将fd加入set集合*/

FD_CLR(fd, &set); /*将fd从set集合中清除*/

FD_ISSET(fd, &set); /*在调用select()函数后,用FD_ISSET来检测fd在fdset集合中的状态是否变化返回整型,当检测到fd状态发生变化时返回真,否则,返回假(0)*/

以上式子中的fd为socket句柄。

例子:
#include <stdio.h>
#include <sys/select.h>
#include <unistd.h>
intmain(intargc,char**argv){   
    fd_set fdset;    
    FD_ZERO (&fdset);                         /*清空集合中所有的元素*/   
    FD_SET(STDOUT_FILENO,&fdset);             /*设置stdout,使集合中包含stdout*/ 
    if(FD_ISSET(STDOUT_FILENO,&fdset)!=0)     /*测试stdout是否包含在集合中*/       
        printf("stdout has been set\n");   
    else       
        printf("stdout has not been set\n");  
    FD_CLR(STDOUT_FILENO,&fdset);             /*从位向量中清除stdout*/ 
    if(FD_ISSET(STDOUT_FILENO,&fdset)!=0)     /*再次测试*/      
        printf("stdout has been set\n");   
    else       
        printf("stdout has not been set\n");   
    return0;
}

缺点:
select本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理。这样所带来的缺点是:
1、 单个进程可监视的fd数量被限制,即能监听端口的大小有限。
    一般来说这个数目和系统内存关系很大,具体数目可以cat /proc/sys/fs/file-max察看。32位机默认是1024个。64位机默认是2048.
2、 对socket进行扫描时是线性扫描,即采用轮询的方法,效率较低:
    当套接字比较多的时候,每次select()都要通过遍历FD_SETSIZE个Socket来完成调度,不管哪个Socket是活跃的,都遍历一遍。这会浪费很多CPU时间。如果能给套接字注册某个回调函数,当他们活跃时,自动完成相关操作,那就避免了轮询,这正是epoll与kqueue做的。
3、需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大


参考:

select 模型,布布扣,bubuko.com

select 模型

原文:http://blog.csdn.net/lileiyang12/article/details/22478789

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!