Operations on file descriptors in Linux (FD_ZERO, FD_SET, FD_CLR, FD_ISSET)

Reprinted from: https://blog.csdn.net/cstarbl/article/details/7645298

In Linux, the kernel uses File Descriptor (File Descriptor) to access files. The file descriptor is a non-negative integer. When opening an existing file or creating a new file, the kernel returns a file descriptor. Reading and writing files also need to use file descriptors to specify the files to be read and written. The "FD" in the macros FD_ZERO, FD_SET, FD_CLR, and FD_ISSET is the abbreviation of file descriptor, which will be introduced one by one below.

First introduce an important structure:, fd_setit will be used many times as a parameter of some of the following functions, fd_set can be understood as a collection, this collection is stored in the file descriptor (file descriptor), that is, the file handle, it Use one bit to represent a fd (will be introduced in detail below). The fd_set collection can be manipulated by the following macros.

1》FD_ZERO

usage:FD_ZERO(fd_set*);

Used to clear the fd_set collection, that is, make the fd_set collection no longer contain any file handles. Before setting the file descriptor set, it must be initialized. If it is not cleared, the system usually does not clear the memory space after the system allocates it, so the result is unknowable.

2》FD_SET

usage:FD_SET(int ,fd_set *);

Used to add a given file descriptor to the collection.

3》FD_CLR

usage:FD_CLR(int ,fd_set*);

Used to delete a given file descriptor from the collection.

4》FD_ISSET

usage:FD_ISSET(int ,fd_set*);

Detect whether the status of fd in the fdset collection has changed, and return true when it detects that the status of fd has changed, otherwise, return false (it can also be considered whether the file descriptor specified in the collection can be read and written).

5》Function select

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

Function: Used to monitor the status changes of the file descriptors (file descriptors in the file set that are read or written) that we need to monitor. And can tell us through the returned value.

Parameter explanation:

int maxfdp: The range of all file descriptors in the set, plus one for the maximum value of all file descriptors.

fd_set *readfds: The set of read files to be monitored.

fd_set *writefds : The set of write files to be monitored.

fd_set *errorfds: Used to monitor abnormal data.

struct timeval* timeout: The timeout period of select, which can make select in three states:

  • First, if NULL is passed in as a formal parameter, that is, no time structure is passed in, that is, select is placed in a blocking state, and it must wait until a file descriptor in the monitoring file descriptor set changes;
  • Second, if the time value is set to 0 seconds and 0 milliseconds, it becomes a pure non-blocking function, no matter whether the file descriptor has changed, it will immediately return to continue execution, the file will return 0 if there is no change, and a positive value will be returned if there is a change ;
  • Third, the value of timeout is greater than 0, which is the waiting timeout time, that is, select blocks within the timeout time, and returns when an event arrives within the timeout time, otherwise it must return anyway after the timeout.
struct timeval timeout; timeout.tv_sec = 0; //秒 timeout.tv_usec = dwTimeout * 1000; //微秒 1毫秒 = 1000微秒

Return value introduction:

>0:被监视的文件描述符有变化,返回对应位仍然为1的fd的总数。
-1:出错
0 :超时

For example:

For example recv(), when there is no data coming to call it, your thread will be blocked. If the data never comes, your thread will be blocked for a long time. This is obviously not good, so use select to check whether the socket is readable (that is, whether there is data to read).

Proceed as follows- -

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   
}

The key to understanding the select model is to understand fd_set. For the convenience of explanation, the length of fd_set is 1 byte, and each bit in fd_set can correspond to a file descriptor fd. Then a 1-byte long fd_set can correspond to 8 fd at most.

  • (1) Execute fd_set set; FD_ZERO(&set); then set is 0000,0000 in bits.
  • (2) If fd=5, execute FD_SET(fd,&set); and set becomes 0001,0000 (the 5th position is 1)
  • (3) If fd=2 and fd=1 are added again, then set becomes 0001,0011
  • (4) Execute select(6,&set,0,0,0) to block waiting
  • (5) If a readable event occurs on both fd=1 and fd=2, select returns, and set becomes 0000,0011 at this time. Note: fd=5 where no event occurred is cleared.

Based on the above discussion, the characteristics of the select model can be easily derived:

(1) The number of file descriptors that can be monitored depends on the value of sizeof(fd_set). On my server, sizeof(fd_set)=512, and each bit represents a file descriptor, so the largest file descriptor supported on my server is 512*8=4096. It is said that it is adjustable, and it is said that although it is adjustable, the upper limit of adjustment is subject to the variable value when compiling the kernel. I am not interested in adjusting the size of fd_set, please refer to Model 2 (1) in http://www.cppblog.com/CppExplore/archive/2008/03/21/45061.html , which can effectively break through the selection of monitorable files Descriptor upper limit.

(2) When adding fd to the select monitoring set, a data structure array is also used to save the fd placed in the select monitoring set. One is to use the array as the source data and fd_set for FD_ISSET judgment after the select returns. The second is that after select returns, it will clear the previously added fd but no event occurred. Each time before starting the select, the fd must be obtained from the array and added one by one (FD_ZERO is the first), and the maximum fd maxfd is obtained while scanning the array. The first parameter used for select.

(3) It can be seen that the select model must loop array before select (add fd, take maxfd), and loop array after select returns (FD_ISSET judges whether there is time to happen).

The following gives a pseudo code to illustrate the server model of the basic select model:

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。

The following is an example of testing the readability of a single file description:

     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;
     }

There is a more complicated application below:

//This code will specify the readability of the description word of the test Socket, because Socket also uses 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;
         };
}

Guess you like

Origin blog.csdn.net/houxiaoni01/article/details/103316774