xml地图|网站地图|网站标签 [设为首页] [加入收藏]
来自 IT之家 2019-12-30 02:52 的文章
当前位置: 新濠国际登录平台 > IT之家 > 正文

select函数用于在非阻塞中,监听用户感兴趣的文

select系统调用的的用途是:在一段指定的时间内,监听用户感兴趣的文件描述符上可读、可写和异常等事件。

先来谈谈为什么会出现select函数,也就是select是解决什么问题的?

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <string.h>
 4 #include <sys/types.h>
 5 #include <sys/socket.h>
 6 #include <arpa/inet.h>
 7 #include <unistd.h>
 8 
 9 #define PORT 8888
10 #define BACKLOG 20
11 
12 #define MAX1(a,b) (a) > (b) ? (a) : (b)
13 
14 int main (){
15     int ret = 0;
16     int i = 0;
17     int max = 0;
18     int nCurrentSocket = 0;
19     int ss,sc;
20     char buffer[1024];
21     ssize_t size = 0;
22     struct sockaddr_in server_addr;
23     struct sockaddr_in client_addr;
24     int err;
25     pid_t pid;
26     char dst_ip[16]="";
27     
28     ss = socket(AF_INET,SOCK_STREAM,0);
29     if(ss < 0 ){
30         printf("socket error n");
31         return -1;
32     }
33     
34     bzero(&server_addr,sizeof(server_addr));
35     server_addr.sin_family = AF_INET;
36     server_addr.sin_addr.s_addr = inet_addr("192.168.0.219");
37     server_addr.sin_port = htons(PORT);
38     
39     err = bind(ss,(struct sockaddr*)&server_addr,sizeof(server_addr));
40     if(err < 0 ){
41         printf("bind errorn");
42         return -1;
43     }
44     
45     err = listen (ss , BACKLOG);
46     if(err < 0 ){
47         printf("listen errorn");
48         return -1;
49     }
50     
51     fd_set fdReadFds;
52 
53 
54 
55     FD_ZERO(&fdReadFds);
56     FD_SET(ss,&fdReadFds);
57     max = ss;
58 
59     while(1){
60     
61         ret = select(max + 1, &fdReadFds, NULL, NULL, NULL);
62         if (ret == 0)
63         {
64             continue;
65         }
66         if (ret < 0)
67         {
68             continue;
69         }
70         
71 
72         for (i = 3; i <= max; i++)
73         {
74             nCurrentSocket = i;
75             
76             if (FD_ISSET(nCurrentSocket, &fdReadFds))
77             {
78                 if (nCurrentSocket == ss)
79                 {
80                     socklen_t addrlen= sizeof(struct sockaddr);
81                        sc = accept(ss,(struct sockaddr*)&client_addr,&addrlen);
82                     if(sc < 0)
83                     {
84                         continue;
85                     }
86                     FD_SET(sc,&fdReadFds);
87                     max = MAX1(max,sc);
88                     continue;
89                 }
90                 else
91                 {
92                     memset(buffer,0,1024);
93                                         size = read(sc,buffer,1024);
94                                         printf("==%d==:%d    %sn",sc,size,buffer);
95                 }    
96             }
97         }
98     }
99 }

select 机制的优势

为什么会出现select模型?

先看一下下面的这句代码:

int iResult = recv(s, buffer,1024);

这是用来接收数据的,在默认的阻塞模式下的套接字里,recv会阻塞在那里,直到套接字连接上有数据可读,把数据读到buffer里后recv函数才会返回,不然就会一直阻塞在那里。在单线程的程序里出现这种情况会导致主线程(单线程程序里只有一个默认的主线程)被阻塞,这样整个程序被锁死在这里,如果永 远没数据发送过来,那么程序就会被永远锁死。这个问题可以用多线程解决,但是在有多个套接字连接的情况下,这不是一个好的选择,扩展性很差。

再看代码:

int iResult = ioctlsocket(s, FIOBIO, (unsigned long *)&ul);
iResult = recv(s, buffer,1024);

这一次recv的调用不管套接字连接上有没有数据可以接收都会马上返回。原因就在于我们用ioctlsocket把套接字设置为非阻塞模式了。不过你跟踪一下就会发现,在没有数据的情况下,recv确实是马上返回了,但是也返回了一个错误:WSAEWOULDBLOCK,意思就是请求的操作没有成功完成。

看到这里很多人可能会说,那么就重复调用recv并检查返回值,直到成功为止,但是这样做效率很成问题,开销太大。

select模型的出现就是为了解决上述问题。
select模型的关键是使用一种有序的方式,对多个套接字进行统一管理与调度 。

新濠国际登录平台 1

如上所示,用户首先将需要进行IO操作的socket添加到select中,然后阻塞等待select系统调用返回。当数据到达时,socket被激活,select函数返回。用户线程正式发起read请求,读取数据并继续执行。

从流程上来看,使用select函数进行IO请求和同步阻塞模型没有太大的区别,甚至还多了添加监视socket,以及调用select函数的额外操作,效率更差。但是,使用select以后最大的优势是用户可以在一个线程内同时处理多个socket的IO请求。用户可以注册多个socket,然后不断地调用select读取被激活的socket,即可达到在同一个线程内同时处理多个IO请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。

select流程伪代码如下:

{
    select(socket);
    while(1) 
    {
        sockets = select();
        for(socket in sockets) 
        {
            if(can_read(socket)) 
            {
                read(socket, buffer);
                process(buffer);
            }
        }
    }
}

平常使用的recv函数时阻塞的,也就是如果没有数据可读,recv就会一直阻塞在那里,这是如果有另外一个连接过来,就得一直等待,这样实时性就不是太好。

socket与select()函数使用例程

select相关API介绍与使用

#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int maxfdp, fd_set *readset, fd_set *writeset, fd_set *exceptset,struct timeval *timeout);

参数说明:

maxfdp:被监听的文件描述符的总数,它比所有文件描述符集合中的文件描述符的最大值大1,因为文件描述符是从0开始计数的;

readfds、writefds、exceptset:分别指向可读、可写和异常等事件对应的描述符集合。

timeout:用于设置select函数的超时时间,即告诉内核select等待多长时间之后就放弃等待。timeout == NULL 表示等待无限长的时间

timeval结构体定义如下:

struct timeval
{      
    long tv_sec;   /*秒 */
    long tv_usec;  /*微秒 */   
};

返回值:超时返回0;失败返回-1;成功返回大于0的整数,这个整数表示就绪描述符的数目。

以下介绍与select函数相关的常见的几个宏:

#include <sys/select.h>   
int FD_ZERO(int fd, fd_set *fdset);   //一个 fd_set类型变量的所有位都设为 0
int FD_CLR(int fd, fd_set *fdset);  //清除某个位时可以使用
int FD_SET(int fd, fd_set *fd_set);   //设置变量的某个位置位
int FD_ISSET(int fd, fd_set *fdset); //测试某个位是否被置位

select使用范例:
当声明了一个文件描述符集后,必须用FD_ZERO将所有位置零。之后将我们所感兴趣的描述符所对应的位置位,操作如下:

fd_set rset;   
int fd;   
FD_ZERO(&rset);   
FD_SET(fd, &rset);   
FD_SET(stdin, &rset);

然后调用select函数,拥塞等待文件描述符事件的到来;如果超过设定的时间,则不再等待,继续往下执行。

select(fd+1, &rset, NULL, NULL,NULL);

select返回后,用FD_ISSET测试给定位是否置位:

if(FD_ISSET(fd, &rset)   
{ 
    ... 
    //do something  
}

下面是一个最简单的select的使用例子:

#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

int main()
{
    fd_set rd;
    struct timeval tv;
    int err;


    FD_ZERO(&rd);
    FD_SET(0,&rd);

    tv.tv_sec = 5;
    tv.tv_usec = 0;
    err = select(1,&rd,NULL,NULL,&tv);

    if(err == 0) //超时
    {
        printf("select time out!n");
    }
    else if(err == -1)  //失败
    {
        printf("fail to select!n");
    }
    else  //成功
    {
        printf("data is available!n");
    }


    return 0;
}

我们运行该程序并且随便输入一些数据,程序就提示收到数据了。
新濠国际登录平台 2

这个问题的几个解决方法:1. 使用ioctlsocket函数,将recv函数设置成非阻塞的,这样不管套接字上有没有数据都会立刻返回,可以重复调用recv函数,这种方式叫做轮询(polling),但是这样效率很是问题,因为,大多数时间实际上是无数据可读的,花费时间不断反复执行read系统调用,这样就比较浪费CPU的时间。并且循环之间的间隔不好确定。2. 使用fork,使用多进程来解决,这里终止会比较复杂(待研究)。 3.使用多线程来解决,这样避免了终止的复杂性,但却要求处理线程之间的同步,在减少复杂性方面这可能会得不偿失。4. 使用异步IO(待研究)。5. 就是本文所使用的I/O多路转接(多路复用)--其实就是在套接字阻塞和非阻塞之间做了一个均衡,我们称之为半阻塞。

select函数用于在非阻塞中,当一个套接字或一组套接字有信号时通知你,系统提供select函数来实现多路复用输入/输出模型,原型:
int select(int maxfd,fd_set *rdset,fd_set *wrset,fd_set *exset,struct timeval *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模型的特点:

(1)可监控的文件描述符个数取决与sizeof(fd_set)的值。我这边服务器上sizeof(fd_set)=512,每bit表示一个文件描述符,则我服务器上支持的最大文件描述符是512*8=4096。据说可调,另有说虽然可调,但调整上限受于编译内核时的变量值。

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

(3)可见select模型必须在select前循环加fd,取maxfd,select返回后利用FD_ISSET判断是否有事件发生。

经过对select的初步了解,在windows和linux下的实现小有区别,所以分开来写。这里先写windows下的select机制。

 

用select处理带外数据

网络程序中,select能处理的异常情况只有一种:socket上接收到带外数据。

什么是带外数据?

带外数据(out—of—band data),有时也称为加速数据(expedited data),
是指连接双方中的一方发生重要事情,想要迅速地通知对方。
这种通知在已经排队等待发送的任何“普通”(有时称为“带内”)数据之前发送。
带外数据设计为比普通数据有更高的优先级。
带外数据是映射到现有的连接中的,而不是在客户机和服务器间再用一个连接。

我们写的select程序经常都是用于接收普通数据的,当我们的服务器需要同时接收普通数据和带外数据,我们如何使用select进行处理二者呢?

下面给出一个小demo:

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


int main(int argc, char* argv[])
{
    if(argc <= 2)
    {
        printf("usage: ip address + port numbersn");
        return -1;
    }

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

        printf("ip: %sn",ip);
        printf("port: %dn",port);

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

    int listenfd = socket(PF_INET,SOCK_STREAM,0);
    if(listenfd < 0)
    {
        printf("Fail to create listen socket!n");
        return -1;
    }

    ret = bind(listenfd,(struct sockaddr*)&address,sizeof(address));
    if(ret == -1)
    {
        printf("Fail to bind socket!n");
        return -1;
    }

    ret = listen(listenfd,5); //监听队列最大排队数设置为5
    if(ret == -1)
    {
        printf("Fail to listen socket!n");
        return -1;
    }

    struct sockaddr_in client_address;  //记录进行连接的客户端的地址
    socklen_t client_addrlength = sizeof(client_address);
    int connfd = accept(listenfd,(struct sockaddr*)&client_address,&client_addrlength);
    if(connfd < 0)
    {
        printf("Fail to accept!n");
        close(listenfd);
    }

    char buff[1024]; //数据接收缓冲区
    fd_set read_fds;  //读文件操作符
    fd_set exception_fds; //异常文件操作符
    FD_ZERO(&read_fds);
    FD_ZERO(&exception_fds);

    while(1)
    {
        memset(buff,0,sizeof(buff));
        /*每次调用select之前都要重新在read_fds和exception_fds中设置文件描述符connfd,因为事件发生以后,文件描述符集合将被内核修改*/
        FD_SET(connfd,&read_fds);
        FD_SET(connfd,&exception_fds);

        ret = select(connfd+1,&read_fds,NULL,&exception_fds,NULL);
        if(ret < 0)
        {
            printf("Fail to select!n");
            return -1;
        }


        if(FD_ISSET(connfd, &read_fds))
        {
            ret = recv(connfd,buff,sizeof(buff)-1,0);
            if(ret <= 0)
            {
                break;
            }

            printf("get %d bytes of normal data: %s n",ret,buff);

        }
        else if(FD_ISSET(connfd,&exception_fds)) //异常事件
        {
            ret = recv(connfd,buff,sizeof(buff)-1,MSG_OOB);
            if(ret <= 0)
            {
                break;
            }

            printf("get %d bytes of exception data: %s n",ret,buff);
        }

    }

    close(connfd);
    close(listenfd);


    return 0;
}

select的大概思想:将多个套接字放在一个集合里,然后统一检查这些套接字的状态(可读、可写、异常等),调用select后,会更新这些套接字的状态,然后做判断,如果套接字可读,就执行read操作。这样就巧妙地避免了阻塞,达到同时处理多个连接的目的。当然如果没有事件发生,select会一直阻塞,如果不想一直让它等待,想去处理其它事情,可以设置一个最大的等待时间。

所在的头文件为:#include <sys/time.h> 和#include <unistd.h>

用select来解决socket中的多客户问题

上面提到过,,使用select以后最大的优势是用户可以在一个线程内同时处理多个socket的IO请求。在网络编程中,当涉及到多客户访问服务器的情况,我们首先想到的办法就是fork出多个进程来处理每个客户连接。现在,我们同样可以使用select来处理多客户问题,而不用fork。

服务器端

#include <sys/types.h> 
#include <sys/socket.h> 
#include <stdio.h> 
#include <netinet/in.h> 
#include <sys/time.h> 
#include <sys/ioctl.h> 
#include <unistd.h> 
#include <stdlib.h>

int main() 
{ 
    int server_sockfd, client_sockfd; 
    int server_len, client_len; 
    struct sockaddr_in server_address; 
    struct sockaddr_in client_address; 
    int result; 
    fd_set readfds, testfds; 
    server_sockfd = socket(AF_INET, SOCK_STREAM, 0);//建立服务器端socket 
    server_address.sin_family = AF_INET; 
    server_address.sin_addr.s_addr = htonl(INADDR_ANY); 
    server_address.sin_port = htons(8888); 
    server_len = sizeof(server_address); 
    bind(server_sockfd, (struct sockaddr *)&server_address, server_len); 
    listen(server_sockfd, 5); //监听队列最多容纳5个 
    FD_ZERO(&readfds); 
    FD_SET(server_sockfd, &readfds);//将服务器端socket加入到集合中
    while(1) 
    {
        char ch; 
        int fd; 
        int nread; 
        testfds = readfds;//将需要监视的描述符集copy到select查询队列中,select会对其修改,所以一定要分开使用变量 
        printf("server waitingn"); 

        /*无限期阻塞,并测试文件描述符变动 */
        result = select(FD_SETSIZE, &testfds, (fd_set *)0,(fd_set *)0, (struct timeval *) 0); //FD_SETSIZE:系统默认的最大文件描述符
        if(result < 1) 
        { 
            perror("server5"); 
            exit(1); 
        } 

        /*扫描所有的文件描述符*/
        for(fd = 0; fd < FD_SETSIZE; fd++) 
        {
            /*找到相关文件描述符*/
            if(FD_ISSET(fd,&testfds)) 
            { 
              /*判断是否为服务器套接字,是则表示为客户请求连接。*/
                if(fd == server_sockfd) 
                { 
                    client_len = sizeof(client_address); 
                    client_sockfd = accept(server_sockfd, 
                    (struct sockaddr *)&client_address, &client_len); 
                    FD_SET(client_sockfd, &readfds);//将客户端socket加入到集合中
                    printf("adding client on fd %dn", client_sockfd); 
                } 
                /*客户端socket中有数据请求时*/
                else 
                { 
                    ioctl(fd, FIONREAD, &nread);//取得数据量交给nread

                    /*客户数据请求完毕,关闭套接字,从集合中清除相应描述符 */
                    if(nread == 0) 
                    { 
                        close(fd); 
                        FD_CLR(fd, &readfds); //去掉关闭的fd
                        printf("removing client on fd %dn", fd); 
                    } 
                    /*处理客户数据请求*/
                    else 
                    { 
                        read(fd, &ch, 1); 
                        sleep(5); 
                        printf("serving client on fd %dn", fd); 
                        ch++; 
                        write(fd, &ch, 1); 
                    } 
                } 
            } 
        } 
    } 

    return 0;
}

客户端

//客户端
#include <sys/types.h> 
#include <sys/socket.h> 
#include <stdio.h> 
#include <netinet/in.h> 
#include <arpa/inet.h> 
#include <unistd.h> 
#include <stdlib.h>
#include <sys/time.h>

int main() 
{ 
    int client_sockfd; 
    int len; 
    struct sockaddr_in address;//服务器端网络地址结构体 
     int result; 
    char ch = 'A'; 
    client_sockfd = socket(AF_INET, SOCK_STREAM, 0);//建立客户端socket 
    address.sin_family = AF_INET; 
    address.sin_addr.s_addr = inet_addr("127.0.0.1");
    address.sin_port = htons(8888); 
    len = sizeof(address); 
    result = connect(client_sockfd, (struct sockaddr *)&address, len); 
    if(result == -1) 
    { 
         perror("oops: client2"); 
         exit(1); 
    } 
    //第一次读写
    write(client_sockfd, &ch, 1); 
    read(client_sockfd, &ch, 1); 
    printf("the first time: char from server = %cn", ch); 
    sleep(5);

    //第二次读写
    write(client_sockfd, &ch, 1); 
    read(client_sockfd, &ch, 1); 
    printf("the second time: char from server = %cn", ch);

    close(client_sockfd); 

    return 0; 
}

运行流程:

客户端:启动->连接服务器->发送A->等待服务器回复->收到B->再发B给服务器->收到C->结束

服务器:启动->select->收到A->发A+1回去->收到B->发B+1过去

测试:我们先运行服务器,再运行客户端
新濠国际登录平台 3

/***********************************************************************************************************/

 

select总结:

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的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大。

下面具体讲讲函数的参数,参见MSDN的解释:

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

 

 

[cpp] view plaincopy

    对于fd_set类型通过下面四个宏来操作:
    FD_ZERO(fd_set *fdset) 将指定的文件描述符集清空,在对文件描述符集合进行设置前,必须对其进行初始化,如果不清空,由于在系统分配内存空间后,通常并不作清空处理,所以结果是不可知的。
    FD_SET(fd_set *fdset) 用于在文件描述符集合中增加一个新的文件描述符。
    FD_CLR(fd_set *fdset) 用于在文件描述符集合中删除一个文件描述符。
    FD_ISSET(int fd,fd_set *fdset) 用于测试指定的文件描述符是否在该集合中。

 

 

  1. int select(  
  2.   _In_     int nfds,  
  3.   _Inout_  fd_set *readfds,  
  4.   _Inout_  fd_set *writefds,  
  5.   _Inout_  fd_set *exceptfds,  
  6.   _In_     const struct timeval *timeout  
  7. );  

UNIX系统通常会在头文件<sys/select.h>中定义常量FD_SETSIZE,它是数据类型fd_set的描述字数量,其值通常是1024,这样就能表示<1024的fd。

函数的返回值,表示准备好的套接字的个数,如果是0,则表示没有一个准备好(超时就是一种情况),如果是-1(SOCKET_ERROR),表示有错误发生,可以使用WSAGetLastError.aspx)()函数来得到错误代码,从而知道是什么错误。

 

 

 

函数的参数,第一个是输入参数nfds,表示满足条件的套接字的个数,windows下可以设置为0,因为fd_set结构体中已经包含了这个参数,这个参数已经是多余的了,之所以还存在,只是是为了与FreeBSD兼容。

好了在研究了一番关于fd_set的信息之后,再回到对select函数的理解上来吧。

第二三四参数都是输入输出参数(值-结果参数,输入和输出会不一样),表示套接字的可读、可写和异常三种状态的集合。调用select之后,如果指定套接字不可读或者不可写,就会从相应队列中清除,这样就可以判断哪些套接字可读或者可写。 

 

说明一下,这里的可读性是指:如果有客户的连接请求到达,套接口就是可读的,调用accept能够立即完成,而不发生阻塞;如果套接口接收队列缓冲区中的字节数大于0,调用recv或者recvfrom就不会阻塞。可写性是指,可以向套接字发送数据(套接字创建成功后,就是可写的)。当然不是套接字可写就会去发送数据,就像不是看到电话就去打电话一样,而是由打电话的需求了,才去看电话是否可打;可读就不一样了,电话响了,自然要去接电话(除非,你有事忙或者不想接,一般都是要接的)。可读已经包含了缓冲区中有数据可以读取,可写只是说明了缓冲区有空间让你写,你需不需要写就要看你有没有数据要写了.关于异常,就是指一些意外情况,自己用的比较少,以后用到了,再过来补上。

    功能:测试指定的fd可读?可写?有异常条件待处理?
    readset  用来检查可读性的一组文件描述字。
    writeset 用来检查可写性的一组文件描述字。
    exceptset用来检查是否有异常条件出现的文件描述字。(注:不包括错误)
    timeout  用于描述一段时间长度,如果在这个时间内,需要监视的描述符没有事件发生则函数返回,返回值为0。

第五个参数是等待的最大时间,是一个结构体:struct timeval,它的定义是:

 

 

    对于select函数的功能简单的说就是对文件fd做一个测试。测试结果有三种可能:
    1.timeout=NULL(阻塞:select将一直被阻塞,直到某个文件描述符上发生了事件)
    2.timeout所指向的结构设为非零时间(等待固定时间:如果在指定的时间段里有事件发生或者时间耗尽,函数均返回)
    3.timeout所指向的结构,时间设为0(非阻塞:仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生)

[cpp] view plaincopy

 

 

 

  1. /* 
  2. * Structure used in select() call, taken from the BSD file sys/time.h. 
  3. */  
  4. struct timeval {  
  5.         long    tv_sec;         /* seconds */  
  6.         long    tv_usec;        /* and microseconds */  
  7. };  

 

具体到秒和微妙,按照等待的时间长短可以分为不等待、等待一定时间、一直等待。对应的设置分别为,(0,0)是不等待,这是select是非阻塞的,(x,y)最大等待时间x秒y微妙(如果有事件就会提前返回,而不继续等待),NULL表示一直等待,直到有事件发生。这里可以将timeout分别设置成0(不阻塞)或者1微妙(阻塞很短的时间),然后观察CPU的使用率,会发现设置成非阻塞后,CPU的使用率已下载就上升到了50%左右,这样可以看出非阻塞占用CPU很多,但利用率不高。

返回值:返回对应位仍然为1的fd的总数。注意啦:只有那些可读,可写以及有异常条件待处理的fd位仍然为1。否则为0哦。

 

举个例子,比如recv(), 在没有数据到来调用它的时候,你的线程将被阻塞,如果数据一直不来,你的线程就要阻塞很久.这样显然不好。所以采用select来查看套节字是否可读(也就是是否有数据读了) 。
步骤如下——
socket s;
.....
fd_set set;
while(1)
{
FD_ZERO(&set);//将你的套节字集合清空
FD_SET(s, &set);//加入你感兴趣的套节字到集合,这里是一个读数据的套节字s
select(0,&set,NULL,NULL,NULL);//检查套节字是否可读,
//很多情况下就是是否有数据(注意,只是说很多情况)
//这里select是否出错没有写
if(FD_ISSET(s, &set) //检查s是否在这个集合里面,
{ //select将更新这个集合,把其中不可读的套节字去掉
//只保留符合条件的套节字在这个集合里面
recv(s,...);
}
//do something here
}

/***********************************************************************************************************新濠国际登录平台,/

 

跟select配合使用的几个宏和fd_set结构体介绍:

套接字描述符为了方便管理是放在一个集合里的,这个集合是fd_set,它的具体定义是:

 

[cpp] view plaincopy

 

本文由新濠国际登录平台发布于IT之家,转载请注明出处:select函数用于在非阻塞中,监听用户感兴趣的文

关键词: