精华内容
下载资源
问答
  • fd_set
    千次阅读
    2019-09-18 09:36:05

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

    fd_set结构体原型:

    typedef struct
    {
    /*XPG4.2requiresthismembername.Otherwiseavoidthename
    fromtheglobalnamespace.*/
    #ifdef__USE_XOPEN
    __fd_maskfds_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;

    系统提供了4个宏对描述符集进行操作:

    #include <sys/select.h>
    #include <sys/time.h>
    void FD_SET(int fd, fd_set *fdset);
    void FD_CLR(int fd, fd_set *fdset);
    void FD_ISSET(int fd, fd_set *fdset);
    void FD_ZERO(fd_set *fdset);

    宏FD_SET设置文件描述符集fdset中对应于文件描述符fd的位(设置为1),宏FD_CLR清除文件描述符集fdset中对应于文件描述符fd的位(设置为0),宏FD_ZERO清除文件描述符集fdset中的所有位(既把所有位都设置为0)。使用这3个宏在调用select前设置描述符屏蔽位,在调用select后使用FD_ISSET来检测文件描述符集fdset中对应于文件描述符fd的位是否被设置。

    常见用法:

    fd_set fdset;
    FD_ZERO(&fdset); /*将set清零使集合中不含任何fd,清空fdset与所有文件句柄的联系*/
    FD_SET(fd, &fdset); /*将fd加入set集合,建立文件句柄fd与fdset的联系*/
    FD_CLR(fd, &fdset); /*将fd从set集合中清除,清除文件句柄fd与fdset的联系*/
    FD_ISSET(fd, &fdset); /*在调用select()函数后,用FD_ISSET来检测fd是否在set集合中,当检测到fd在set中则返回真,否则,返回假(0)*/
    以上式子中的fd为socket句柄。

    示例:

    #include <stdio.h>
    #include <sys/select.h>
    #include <unistd.h>
    int main(int argc, 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");
    
        FD_ZERO(&fdset);
        FD_SET(1, &fdset);
        FD_SET(2, &fdset);
        FD_SET(3, &fdset);
        FD_SET(7, &fdset);
        printf("fdset.__fds_bits[0] = %d\n", fdset.__fds_bits[0]);
    
        return 0;
    }

    运行结果:

    stdout has been set
    stdout has not been set
    fdset.__fds_bits[0] = 142

    即:将fdset所有元素的二进制连接为一个整体,FD_SET(1, &fdset)、FD_SET(2, &fdset)、FD_SET(3, &fdset)、FD_SET(7, &fdset)相当于将二进制中的bit1、bit2、bit3、bit7设置为1,即1000 1110,所以fdset.__fds_bits[0]值为142。

    可以查看fdset的定义,是一个long类型数组,数组大小为__FD_SETSIZE/__NFDBITS,前者为1024,后者与系统有关,这种定义保证了fdset数组最大可以存储__FD_SETSIZE个bit位。

    一句话:fd_set就是一个long类型数组,数组中所有元素按照二进制位排列,每一位都对应一个文件描述符(通过索引关联)。

    通常,操作系统通过宏FD_SETSIZE来声明在一个进程中select所能操作的文件描述符的最大数目。例如:
    在4.4BSD的头文件中我们可以看到:

    #ifndef FD_SETSIZE
    #define FD_SETSIZE 1024
    #endif

    在红帽Linux的头文件<bits/types.h>中我们可以看到:

    #define __FD_SETSIZE 1024

    以及在头文件<sys/select.h>中我们可以看到:

    #include <bits/types.h>
    #define FD_SETSIZE __FD_SETSIZE

    既定义FD_SETSIZE为1024,一个整数占4个字节,既32位,那么就是用包含32个元素的整数数组来表示文件描述符集。我们可以在头文件中修改这个值来改变select使用的文件描述符集的大小,但是必须重新编译内核才能使修改后的值有效。当前版本的unix操作系统没有限制FD_SETSIZE的最大值,通常只受内存以及系统管理上的限制。

    更多相关内容
  • ―――――――――――――――――――――――――――――――――――――――2、select函数的接口比较简单:int select(int nfds, fd_set *readset, fd_set *writeset,fd_set* exceptset, struct tim *timeout...

    ―――――――――――――――――――――――――――――――――――――――

    2、select函数的接口比较简单:

    int select(int nfds, fd_set *readset, fd_set *writeset,fd_set* exceptset, struct tim *timeout);

    功能:

    测试指定的fd可读?可写?有异常条件待处理?

    参数:

    nfds

    需要检查的文件描述字个数(即检查到fd_set的第几位),数值应该比三组fd_set中所含的最大fd值更大,一般设为三组fd_set中所含的最大fd值加1(如在readset,writeset,exceptset中所含最大的fd为5,则nfds=6,因为fd是从0开始的)。设这个值是为提高效率,使函数不必检查fd_set的所有1024位。

    readset

    用来检查可读性的一组文件描述字。

    writeset

    用来检查可写性的一组文件描述字。

    exceptset

    用来检查是否有异常条件出现的文件描述字。(注:错误不包括在异常条件之内)

    timeout

    用于描述一段时间长度,如果在这个时间内,需要监视的描述符没有事件发生则函数返回,返回值为0。

    有三种可能:

    1.timeout=NULL(阻塞:select将一直被阻塞,直到某个文件描述符上发生了事件)

    2.timeout所指向的结构设为非零时间(等待固定时间:如果在指定的时间段里有事件发生或者时间耗尽,函数均返回)

    3.timeout所指向的结构,时间设为0(非阻塞:仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生)

    返回值:

    返回对应位仍然为1的fd的总数。

    Remarks:

    三组fd_set均将某些fd位置0,只有那些可读,可写以及有异常条件待处理的fd位仍然为1。

    举个例子,比如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长度为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。据说可调,另有说虽 然可调,但调整上限受于编译内核时的变量值。本人对调整fd_set的大小不太感兴趣,参考http://www.cppblog.com/CppExplore/archive/2008/03/21/45061.html中的模型2(1)可以有效突破select可监控的文件描述符上限。

    (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前循环array(加fd,取maxfd),select返回后循环array(FD_ISSET判断是否有时间发生)。

    下面给一个伪码说明基本select模型的服务器模型:

    array[slect_len];

    nSock=0;

    array[nSock++]=listen_fd;(之前listen port已绑定并listen)

    maxfd=listen_fd;

    while{

    FD_ZERO(&set);

    foreach (fd in array)

    {

    fd大于maxfd,则maxfd=fd

    FD_SET(fd,&set)

    }

    res=select(maxfd+1,&set,0,0,0);

    if(FD_ISSET(listen_fd,&set))

    {

    newfd=accept(listen_fd);

    array[nsock++]=newfd;

    if(--res=0) continue

    }

    foreach 下标1开始 (fd in array)

    {

    if(FD_ISSET(fd,&set))

    执行读等相关操作

    如果错误或者关闭,则要删除该fd,将array中相应位置和最后一个元素互换就好,nsock减一

    if(--res=0) continue

    }

    }

    使用select函数的过程一般是:

    先调用宏FD_ZERO将指定的fd_set清零,然后调用宏FD_SET将需要测试的fd加入fd_set,接着调用函数select测试fd_set中的所有fd,最后用宏FD_ISSET检查某个fd在函数select调用后,相应位是否仍然为1。

    以下是一个测试单个文件描述字可读性的例子:

    int isready(int fd)

    {

    int rc;

    fd_set fds;

    struct tim tv;

    FD_ZERO(&fds);

    FD_SET(fd,&fds);

    tv.tv_sec = tv.tv_usec = 0;

    rc = select(fd+1, &fds, NULL, NULL, &tv);

    if (rc < 0) //error

    return -1;

    return FD_ISSET(fd,&fds) ? 1 : 0;

    }

    下面还有一个复杂一些的应用:

    //这段代码将指定测试Socket的描述字的可读可写性,因为Socket使用的也是fd

    uint32 SocketWait(TSocket *s,bool rd,bool wr,uint32 timems)

    {

    fd_set rfds,wfds;

    #ifdef _WIN32

    TIM tv;

    #else

    struct tim tv;

    #endif

    FD_ZERO(&rfds);

    FD_ZERO(&wfds);

    if (rd) //TRUE

    FD_SET(*s,&rfds); //添加要测试的描述字

    if (wr) //FALSE

    FD_SET(*s,&wfds);

    tv.tv_sec=timems/1000; //second

    tv.tv_usec=timems%1000; //ms

    for (;;) //如果errno==EINTR,反复测试缓冲区的可读性

    switch(select((*s)+1,&rfds,&wfds,NULL,

    (timems==TIME_INFINITE?NULL:&tv))) //测试在规定的时间内套接口接收缓冲区中是否有数据可读

    { //0--超时,-1--出错

    case 0:

    return 0;

    case (-1):

    if (SocketError()==EINTR)

    break;

    return 0; //有错但不是EINTR

    default:

    if (FD_ISSET(*s,&rfds)) //如果s是fds中的一员返回非0,否则返回0

    return 1;

    if (FD_ISSET(*s,&wfds))

    return 2;

    return 0;

    };

    }

    展开全文
  • 【一文搞懂】FD_SET的使用

    千次阅读 多人点赞 2022-02-14 20:53:46
    FD_SETFD_ZERO、FD_ISSETFD_CLR 以及 select

    阅读大概需要十分钟,绝对干货,看完还没搞懂你找我。


    随便查一下,可以看到对FD_SET的说明如下:

    一个long类型的数组,提供给select()机制使用的一种数据结构。主要功能是建立联系。其中每一个数组元素都能与任意一个打开的句柄(socket句柄、文件、命名管道、设备句柄等)建立联系。但是这种建立联系的工作是必须由程序员自己去完成的。

    小白,比如像我这种就会纳闷,设置这种联系的目的是什么?

    “可以理解为给打开的句柄添加了一种标识。( or or 异常 )的标识。暂且你就只需要知道我们可以通过fd_set(小写)去判断socket的操作即可。

    在这里,我们提出以下几个问题,从简单的到稍微复杂的依次如下:

    1. fd_set是什么?
    2. FD_SET、FD_ZERO、FD_ISSET、FD_CLR的作用都是什么?
    3. 如何通过fd_set(结合select())判断句柄的状态?

    本文就以上三个问题,回答和记录一下。实验环境(win10+vs2017+v141)

    socket相关使用的文件头大致如下:

    #include <iostream>
    #include <WinSock2.h>
    #include <stdio.h>
    #pragma comment(lib, "iphlpapi.lib")
    #pragma comment(lib, "ws2_32.lib")
    

    1. fd_set是什么?

    开篇我们就说了,fd_set是一个long类型的数组。我们可以认为这是一个很大的字节数组。

    先来一小段代码理解一下fd_set这个数组。

    代码1-1

    int main()
    {
    	SOCKET socket = {0};	// 定义一个socket对象
    	fd_set fdset = {0};		// 声明并定义,如果不赋初值,fd_set中存储的则是随机值
    
    	FD_ZERO(&fdset);
    	FD_SET(1, &fdset); // ’联系‘就是在这里产生的,以下4个操作会产生其他4个联系
    	FD_SET(2, &fdset);
    	FD_SET(3, &fdset);
    	FD_SET(7, &fdset);
    	FD_SET(socket, &fdset);
    
    	int isset = FD_ISSET(socket, &fdset); // ’联系‘就是在这里产生的
    	printf("isset = %d\n", isset); // isset = 1
    	FD_CLR(socket, &fdset);
    	isset = FD_ISSET(socket, &fdset); // isset = 0
    	printf("isset = %d\n", isset);
    	return 0;
    }
    

    调试截图如下:

    可以看到,fd_set是一个长度为64的数组,由于代码进行了初始化,所以每一位都是0。在调用FD_SET的过程中,相当于vector.push_back的操作。

    其中,到底有多少个set,则是通过fd_count来决定的。如上截图,虽然看似fd_array有效的值只有1、2、3、7,但实际上fd_count的值为5。这里不是没有绑定到scoket,而是因为socket被初始化为0了,所以实际上fdset变量保存的有效数组为[1,2,3,7,0]

    2. FD_SET、FD_ZERO、FD_ISSET、FD_CLR的作用都是什么?

    首先我们得知道,提供的以上四个宏接口(注意是接口)的作用肯定是用来操作fd_set的。具体作用如下所示:

    代码2-1

    // 这里的fd 实际使用都是以 句柄 传入
    FD_ZERO(fd_set *fdset);              // 将set清零使集合中不含任何fd
    FD_SET(int fd, fd_set *fdset);       // 将fd加入set集合
    FD_CLR(int fd, fd_set *fdset);       // 将fd从set集合中清除
    FD_ISSET(int fd, fd_set *fdset);     // 检测fd是否在set集合中,不在则返回0
    

    正确的使用流程是:

    调用FD_ZERO将一个 fd_set 变量的所有位设置为0。要开启描述符集中的一位,可以调用FD_SET。调用FD_CLR可以清除一位。最后,可以调用FD_ISSET测试描述符集中的一个指定位是否已打开。

    还是结合1-1的代码:

    1. FD_ZERO就是把当前fd_set所有位的数字都置为0

    2. FD_SET实现了句柄和fd_set联系,可以把fd(代码2-1),也就是句柄加入到fd_set中。

    3. FD_CLR清除所绑定的联系注意注意:这里只清除你传进去的fdfd_set之间的联系。需要注意的是,FD_CLR的操作类似于链表节点的删除(后续节点会填补被删除节点)。例如第一个问题中的代码;

      代码2-2

      	int isset = FD_ISSET(socket, &fdset); // 
      	printf("isset = %d\n", isset); // isset = 1
      	FD_CLR(socket, &fdset);
      	isset = FD_ISSET(socket, &fdset); // isset = 0
      	printf("isset = %d\n", isset);
      

      代码2-2第3行,只是清除了前文FD_SET(socket, &fdset);绑定的联系,但是不涉及1、2、3、7fdset之间的联系。怎么判断这种联系?就是通过FD_ISSET

    4. FD_ISSET宏接口。如上代码(代码2-2)所示。如果绑定的联系在则返回1,反之,则返回0。

      • 在调用FD_ISSET之后,isset的值为 1 【LINE 2】
      • 调用FD_CLR之后,isset的值变为 0 【LINE 5】

    3. 如何通过fd_set(结合select())判断句柄的状态?

    要了解如何判断,还是得先回到select()函数
    搬书《UNIX 环境高级编程》一书中 I/O多路转接 章节讲解的很清楚

    select()函数原型:

    代码3-1

    int select(	int maxfdpl, fd_set *restrict readfds,
     		fd_set *restrict writefds,fd_set *restrict exceptfds,
     		struct timeval *restrict typfr);
    // 返回值∶准备就绪的描述符数目;若超时,返回0;若出错,返回-1
    

    在所有POSIX 兼容的平台上,select 函数使我们可以执行I/O多路转接。传给 select 的参数告诉内核∶

    • 我们所关心的描述符;

    • 对于每个描述符我们所关心的条件(是否想从一个给定的描述符读,是否想写一个给定的描述符,是否关心一个给定描述符的异常条件);

    • 愿意等待多长时间(可以永远等待、等待一个固定的时间或者根本不等待)。

    select 返回时,内核告诉我们∶

    • 已准备好的描述符的总数量;

    • 对于读、写或异常这3个条件中的每一个,哪些描述符已准备好。

    使用这种返回信息,就可调用相应的 I/O函数(一般是 read 或 write),并且确知该函数不会阻塞。

    • socket非阻塞
      如果要设置socket为非阻塞的状态,则需要调用ioctlsocket(m_Socket, FIONBIO, &ul);来设置。其中的ul是一个unsigned long类型的变量,在此函数接口中,ul == 1表示设置当前的m_Socket为非阻塞状态。

    本文主要关注的是select()函数中间的三个参数readfds、writefds、exceptfds(第一个参数也很重要)。这三个参数是指向描述符集的指针,描述符集说明了我们关心的 可读、可写、异常 的结合。如下图所示:

    对select指定读、写和异常条件描述符

    1. select()的中间3个参数中的任意一个(或全部)可以是空指针,当你不需要进行操作判断读写异常的时候可以这么做。如果3个指针都是NULL,则select提供了比sleep更精确的定时器。(什么意思?sleep等待整数秒,而 select 的等待时间则可以小于1秒,其实际精度取决于系统时钟。)

    2. select()的第一个参数maxfdp1的意思是“最大文件描述符编号加1”。还是得先明白一个概念,即fd_set的每一位只能使用一次,只能标志一种状态。为避免发生重复应用的情况,如下代码,就需要通过第一个参数去控制。也就是第一个参数maxfdp1。那么第一个参数值如何选取?

      • 设置为FD_SETSIZE。这是<sys/select.h>的一个常量,它指定最大描述符数(通常是1024)。但是一般情况下,该数过于大,一般的程序也就是3~10个描述符。所以一般情况下,选择手动指定。
      • 手动指定,如下代码就属于手动指定。在所有的描述符集中,选择我们关注的最大的描述符数即可。下边代码中,指定的最大描述符数是3,因此select函数的第一个参数为4(= 3+1),即最大描述符编号值加1。

      代码3-2

        fd_set readset, writeset;
        FD_ZERO(&readset);
        FD_ZERO(&writeset);
        FD_SET(0, &readset);
        FD_SET(3, &readset);
        FD_SET(1, &writeset);
        FD_SET(2, &writeset);
        select(4, &readset, &writeset, NULL, NULL); // 该处的select就会返回-1
    

    如代码3-2设置后的readset、writeset如下图所示:

    select的样本描述符集

    select()有3个可能的返回值:

    1. 返回值-1表示出错。这是可能发生的,例如,在所指定的描述符一个都没有准备好时捕捉到一个信号。在此种情况下,一个描述符集都不修改。代码3-2就会返回-1
    2. 返回值0表示没有描述符准备好。若指定的描述符一个都没准备好,指定的时间就过了,那么就会发生这种情况。此时,所有描述符集都不修改。
    3. 一个正返回值说明了已经准备好的描述符数。该值时3个描述符集中已经准备好的描述符之和,所以如果通过描述符已准备好读和写,那么在返回值中会对其计两次数。在这种情况下,3个描述符集中仍旧打开的位对应于已准备好的描述符。

    对于“准备好”的含义要作一些更具体的说明。

    • 若对读集(readfds)中的一个描述符进行的 read操作不会阻塞,则认为此描述符是准备好的。
    • 若对写集(writefds)中的一个描述符进行的write 操作不会阻塞,则认为此描述符是准备好的。
    • 若对异常条件集(exceptfds)中的一个描述符有一个未决异常条件,则认为此描述符是准备好的。现在,异常条件包括∶在网络连接上到达带外的数据,或者在处于数据包模式的伪终端上发生了某些条件。(Stevens【1990】的15.10 节中描述了后一种条件。)
    • 对于读、写和异常条件,普通文件的文件描述符总是返回准备好。

    一个描述符阻塞与否并不影响 select 是否阻塞,理解这一点很重要。也就是说,如果希望读个非阻塞描述符,并且以超时值为5秒调用 select,则 select 最多阻塞5s。相类似,如果指定一个无限的超时值,则在该描述符数据准备好,或捕捉到一个信号之前,select会一直阻塞。

    如果在一个描述符上碰到了文件尾端,则select 会认为该描述符是可读的。然后调用 read,它返回0,这是 UNIX系统指示到达文件尾端的方法。(很多人错误地认为,当到达文件尾端时,select会指示一个异常条件。)

    针对上述第3中情况,完整代码如下:

    这里需要远端开一个服务,可以使用华为的IPOP工具

    代码3-3

    #include <iostream>
    #include <WinSock2.h>
    #include <stdio.h>
    #pragma comment(lib, "iphlpapi.lib")
    #pragma comment(lib, "ws2_32.lib")
    using namespace std;
    int main()
    {
        WSADATA wsa;
    	WSAStartup(MAKEWORD(2, 2), &wsa);
    
    	SOCKADDR_IN addrServer;
    	SOCKET Socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    	fd_set readset, writeset;
    
    	addrServer.sin_addr.S_un.S_addr = inet_addr("192.168.3.16");
    	addrServer.sin_family = AF_INET;
    	addrServer.sin_port = htons(6000);
        
    	DWORD dwResult = connect(Socket, (SOCKADDR*)&addrServer, sizeof(SOCKADDR));
    	FD_ZERO(&readset);
    	FD_ZERO(&writeset);
    
    	FD_SET(Socket, &readset);
    	FD_SET(Socket, &writeset);
    
    	/*
    	int isset = FD_ISSET(Socket, &readset); // isset = 0
    	printf("isset = %d\n", isset);
    	isset = FD_ISSET(Socket, &writeset); // isset = 0
    	printf("isset = %d\n", isset);
    	*/
    
    	int nRet = select(0, &readset, &writeset, NULL, NULL);
    	cout << "The Ret of Select is " << nRet << endl;
    
    	return 0;
    }
    

    输出:

    The Ret of Select is 1

    那么全篇都在说的 读、写、异常 是怎么判断的呢?

    答案是FD_ISSET

    1. 在使用前我们通过FD_SET去建立这种读写异常的联系

    2. select()的时候会修改fd_set的值,而这个修改完之后的值就是我们可以拿去判断的东西。如代码代码3-3,我们可以在select之后增加判断条件

      if (!FD_ISSET(m_Socket, &readset))
      {
          cout << "sock not in readset!" << endl;
      }
      if (FD_ISSET(m_Socket, &writeset))
      {
          cout << "sock not in writeset!" << endl;
      }
      if (FD_ISSET(m_Socket, &exceptset))
      {
          cout << "getsockopt fail!" << endl;
      }
      

      这个时候就可以判断句柄的操作了。

    废话不多说,直接上现场:

    因为没有发生读操作,所以只标志了写的操作。运行截图

    补充:

    参考资料:

    1. 《unix高级环境编程》
    2. win32官方文档

    以上就是关于fd_set的详细说明。

    为避免误人子弟,如有误,还望评论或私信指正。✌✌✌🤝

    展开全文
  • 在Linux中,内核利用文件描述符(File Descriptor)即文件句柄,来访问文件。文件描述符是非负整数。打开现存文件或新建文件时,内核会返回一个文件描述符。...宏FD_ZERO、FD_SETFD_CLR、FD_ISSET中“FD...

    转载自:https://blog.csdn.net/cstarbl/article/details/7645298

    在Linux中,内核利用文件描述符(File Descriptor)即文件句柄,来访问文件。文件描述符是非负整数。打开现存文件或新建文件时,内核会返回一个文件描述符。读写文件也需要使用文件描述符来指定待读写的文件。宏FD_ZERO、FD_SET、FD_CLR、FD_ISSET中“FD”即为file descriptor的缩写,下面来一一进行介绍。

    首先介绍一个重要的结构体:fd_set,它会作为下面某些函数的参数而多次用到,fd_set可以理解为一个集合,这个集合中存放的是文件描述符(file descriptor),即文件句柄,它用一位来表示一个fd(下面会仔细介绍)。fd_set集合可以通过下面的宏来进行人为来操作。

    1》FD_ZERO

    用法:FD_ZERO(fd_set*);

    用来清空fd_set集合,即让fd_set集合不再包含任何文件句柄。在对文件描述符集合进行设置前,必须对其进行初始化,如果不清空,由于在系统分配内存空间后,通常并不作清空处理,所以结果是不可知的。

    2》FD_SET

    用法:FD_SET(int ,fd_set *);

    用来将一个给定的文件描述符加入集合之中。

    3》FD_CLR

    用法:FD_CLR(int ,fd_set*);

    用来将一个给定的文件描述符从集合中删除。

    4》FD_ISSET

    用法:FD_ISSET(int ,fd_set*);

    检测fd在fdset集合中的状态是否变化,当检测到fd状态发生变化时返回真,否则,返回假(也可以认为集合中指定的文件描述符是否可以读写)。

    5》函数select

    用法:int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);

    作用:用来够监视我们需要监视的文件描述符(读或写的文件集中的文件描述符)的状态变化情况。并能通过返回的值告知我们。

    参数解释:

    int maxfdp:集合中所有文件描述符的范围,为所有文件描述符的最大值加1。

    fd_set *readfds:要进行监视的读文件集。

    fd_set *writefds :要进行监视的写文件集。

    fd_set *errorfds:用于监视异常数据。

    struct timeval* timeout:select的超时时间,它可以使select处于三种状态:

    • 第一,若将NULL以形参传入,即不传入时间结构,就是 将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;
    • 第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数, 不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;
    • 第三,timeout的值大于0,这就是等待的超时时间,即 select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回。
    struct timeval timeout; timeout.tv_sec = 0; //秒 timeout.tv_usec = dwTimeout * 1000; //微秒 1毫秒 = 1000微秒
    

    返回值介绍:

    >0:被监视的文件描述符有变化,返回对应位仍然为1的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长度为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。据说可调,另有说虽然可调,但调整上限受于编译内核时的变量值。本人对调整fd_set的大小不太感兴趣,参考http://www.cppblog.com /CppExplore/archive/2008/03/21/45061.html中的模型2(1)可以有效突破select可监控的文件描述符上限。

    (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前循环array(加fd,取maxfd),select返回后循环array(FD_ISSET判断是否有时间发生)。

    下面给一个伪码说明基本select模型的服务器模型:

    array[slect_len];
    nSock=0;
    array[nSock++]=listen_fd;(之前listen port已绑定并listen)
    maxfd=listen_fd;
    while{
       FD_ZERO(&set);
       foreach (fd in array) 
       {
           fd大于maxfd,则maxfd=fd
           FD_SET(fd,&set)
       }
       res=select(maxfd+1,&set,0,0,0)if(FD_ISSET(listen_fd,&set))
       {
           newfd=accept(listen_fd);
           array[nsock++]=newfd;
                if(--res=0) continue
       }
       foreach 下标1开始 (fd in array) 
       {
           if(FD_ISSET(fd,&set))
              执行读等相关操作
              如果错误或者关闭,则要删除该fd,将array中相应位置和最后一个元素互换就好,nsock减一
                 if(--res=0) continue
       }
    }
    

    使用select函数的过程一般是:

    先调用宏FD_ZERO将指定的fd_set清零,然后调用宏FD_SET将需要测试的fd加入fd_set,接着调用函数select测试fd_set中的所有fd,最后用宏FD_ISSET检查某个fd在函数select调用后,相应位是否仍然为1。

    以下是一个测试单个文件描述字可读性的例子:

         int isready(int fd)
         {
             int rc;
             fd_set fds;
             struct tim tv;    
             FD_ZERO(&fds);
             FD_SET(fd,&fds);
             tv.tv_sec = tv.tv_usec = 0;    
             rc = select(fd+1, &fds, NULL, NULL, &tv);
             if (rc < 0)   //error
             return -1;    
             return FD_ISSET(fd,&fds) ? 1 : 0;
         }
    

    下面还有一个复杂一些的应用:

    //这段代码将指定测试Socket的描述字的可读可写性,因为Socket使用的也是fd

    uint32 SocketWait(TSocket *s,bool rd,bool wr,uint32 timems)    
    {
         fd_set rfds,wfds;
    #ifdef _WIN32
         TIM tv;
    #else
         struct tim tv;
    #endif    
         FD_ZERO(&rfds);
         FD_ZERO(&wfds); 
         if (rd)     //TRUE
         FD_SET(*s,&rfds);   //添加要测试的描述字 
         if (wr)     //FALSE
           FD_SET(*s,&wfds); 
         tv.tv_sec=timems/1000;     //second
         tv.tv_usec=timems%1000;     //ms 
         for (;;) //如果errno==EINTR,反复测试缓冲区的可读性
              switch(select((*s)+1,&rfds,&wfds,NULL,
                  (timems==TIME_INFINITE?NULL:&tv))) //测试在规定的时间内套接口接收缓冲区中是否有数据可读
             {                                              //0--超时,-1--出错
             case 0:    
                  return 0; 
             case (-1):   
                  if (SocketError()==EINTR)
                       break;              
                  return 0; //有错但不是EINTR 
              default:
                  if (FD_ISSET(*s,&rfds)) //如果s是fds中的一员返回非0,否则返回0
                       return 1;
                  if (FD_ISSET(*s,&wfds))
                       return 2;
                  return 0;
             };
    }
    
    展开全文
  • fd_set 用法

    2021-05-10 19:52:15
    ―――――――――――――――――――――――――――――――――――――――select函数的接口比较简单:int select(int nfds, fd_set *readset, fd_set *writeset,fd_set* exceptset, struct timeval *time...
  • FD_SETFD_ISSETFD_ZERO

    千次阅读 2018-08-11 15:51:01
    select使用涉及四个宏,通过glibc把他们实现找出来说一说: typedef long int __fd_mask;  #define __NFDBITS (8 * (int) sizeof (__fd_mask))//32位,...#define __FD_ELT(d) ((d) / __NFDBITS)//对应的fd除以3...
  • 详解fd_set结构体

    千次阅读 2020-07-24 17:16:02
    在使用select函数时,就免不了要遇到fd_set结构体。那我们就来深入研究下fd_set的结构体! ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////...
  • 本文带领读者从源码作者的角度去实现一个fd_set, 并将fd_set在windows和linux下的实现进行了简单的对比和分析。
  • Linux:fd_set 结构体定义及使用

    千次阅读 2019-07-09 19:22:36
    在使用select函数时,fd_set结构体是很重要的。 想正确使用select函数,理解fd_set是必不可少的。 <sys/select.h> 下面给出<sys/select.h>头文件的全部内容: /* `fd_set' type and related macros, and...
  • 前言 这个也我在公司的使用自己写库clib中在win上连接服务器时中只能连接63个的客户端,这个我一开始还以为是自己的配置文件... 在我的知识体系中select最大连接数是1024, 我找很长时间 最后在发现在win 上 FD_SETS...
  • 先调用宏FD_ZERO将指定的fd_set清零,然后调用宏FD_SET将需要测试的fd加入fd_set,接着调用函数select测试fd_set中的所有fd,最后用宏FD_ISSET检查某个fd在函数select调用后,是否还在这个集合fd_set中。   ...
  • 曾经在windows上用遍历fd _ set的方法完成向所有已连接的套接字发送消息的功能,现在想移植到Linux上,发现以前用的 fd _ count 都不能使用了,fd _ array也是。想要完成如下代码一样的功能,在Linux上应该怎么写呢...
  • fd_set 详解

    万次阅读 2017-11-18 16:42:18
    一、winsock中 #include ...fd_set* readfds , fd_set* writefds , fd_set* exceptfds , const struct timeval* timeout  ); nfds:本参数忽略,仅起到兼容作用。  readfd
  • 在很多比较各种网络模型的文章中,但凡提到select模型时,都会说select受限于轮询的套接字数量,这个数量也就是系统头文件中定义的FD_SETSIZE值(例如64)。但事实上这个算不上真的限制。 C语言的偏方: 在C语言的...
  • 本文大致记录一下我对socket编程中的select()方法,类型fd_set以及FD_SETFD_ZERO、FD_CLR、FD_ISSET这些宏的用法的了解, 有不当的地方,请指出。 1 select 在socket编程中,select的字面意思就是选择,也就是...
  • Linux: fd_set用法

    万次阅读 2017-06-02 10:23:10
    select()机制中提供一fd_set的数据结构,实际上是一long类型的数组,每一个数组元素都能与一打开的文件句柄(不管是socket句柄,还是其他文件或命名管道或设备句柄)建立联系,建立联系的工作由程序员完成,当调用...
  • fd_set 用法 socket

    2016-12-02 10:28:05
    fd_set(它比较重要所以先介绍一下)是一组文件描述字(fd)的集合,它用一位来表示一个fd(下面会仔细介绍),对于fd_set类型通过下面四个宏来操作:   fd_set set;  FD_ZERO(&set); /* 将set清零使集合中...
  • 在Linux中,内核利用文件描述符(File Descriptor)即文件句柄,来访问文件。...宏FD_ZERO、FD_SETFD_CLR、FD_ISSET中“FD”即为file descriptor的缩写,下面来一一进行介绍。 首先介绍一个重要的结构体:fd_s...
  • 每个集 合类型都是 fd_set。下面有一些宏来对这个类型进行操作: FD_ZERO(fd_set *set) - 清除一个文件描述符集合  FD_SET(int fd, fd_set *set) - 添加fd到集合  FD_CLR(int fd, fd_set *set) - 从集合中移去fd ...
  • 自己编译正常,调用编译也正常,但是在同事那边(xcode11.3)调用静态库编译demo时却出现了错误:Undefined symbols for architecture arm64:"___darwin_check_fd_set_overflow"。随后,让同事升级Xcode版本到最新...
  • Linux下 fd_set 结构小结

    千次阅读 2019-03-30 22:30:03
    fd_set是一种数据类型,在select函数中包含了3个参数,就是这个fd_set类型,fd_set也是理解select模型的关键,关于select的具体说明,可以参考之前的文章嵌入式Linux编程之select使用总结。 select可以同时对多个...
  • Linux下I/O多路转接之select --fd_set

    千次阅读 2016-08-09 23:02:38
    Linux下I/O多路转接之select --fd_set
  • fd_set 的用法

    千次阅读 2016-09-07 14:39:39
    fd_set(它比较重要所以先介绍一下)是一组文件描述字(fd)的集合,它用一位来表示一个fd(下面会仔细介绍),对于fd_set类型通过下面四个宏来操作:       fd_set set;      FD_ZERO(&set)...
  • Android 5.0及以上,select调用会检查fd大小,...FORTIFY_SOURCE: FD_SET: file descriptor >= FD_SETSIZE. Calling abort(). 然后崩溃,检查的代码在: /bionic/libc/bionic/__FD_chk.cpp extern "C" int __FD_IS
  • 一、在网络编程中,经常用到selec系统调用... int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); 其中,fd_set是一个socket集合,常用如下宏来对fd_set进行...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 239,016
精华内容 95,606
关键字:

fd_set

友情链接: tetar_lmac.rar