你的位置:首页 > 信息动态 > 新闻中心
信息动态
联系我们

Linux杂谈(2):Select简介

2021/12/5 17:01:21

文章目录

  • 1 select简介
  • 2 函数详解
    • 2.1 函数原型
    • 2.2 参数
      • 2.2.1 nfds
      • 2.2.2 readfds
      • 2.2.3 writefds
      • 2.2.4 exceptfds
      • 2.2.5 timeout
    • 2.3 文件描述集合操作
      • 2.3.1 FD_ZERO()
      • 2.3.2 FD_SET()
      • 2.3.3 FD_CLR()
      • 2.3.4 FD_ISSET()
    • 2.4 注意
    • 2.5 示例
  • 3 文件描述符集合细探
  • 4 select的缺点

1 select简介

在linux下,我们可以使用select来进行I/O复用,可以监视多个文件描述符,判断是否有符合条件的事件发生。
使用select函数时,我们可以查看是否有可读、可写或者错误的事件发生。

2 函数详解

2.1 函数原型

#include <sys/select.h>

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

2.2 参数

2.2.1 nfds

整形变量,它的值是所有文件描述符中最大的值加1。它的最大值为FD_SETSIZE,FD_SETSIZE一般定义为1024,不同的系统可能不一样。

2.2.2 readfds

这个文件描述集合用于监视此文件集中的文件是否有数据可读,比如我们监视串口是否有数据可读,可以将串口的文件描述符放到此集合中去。当监视到有可读的事件后,readfds文件描述集合将清除不可读的文件描述符,只保留可读的文件描述符。不需要时,将其设置为NULL。

2.2.3 writefds

这个文件描述集合用于监视是否有可写的事件发生,当监视到后,会清除不可写的文件描述符,只保留下可写的文件描述符。不需要时,将其设置为NULL。

2.2.4 exceptfds

这个文件描述集合用于监视是否发送错误。不需要时,将其设置为NULL。

2.2.5 timeout

用于所监视的文件集合中的事件没有发生时候的等待时间。为NULL表示阻塞等待一直到事件发生,为0表示立马返回,不为0表示等待的时间。

2.3 文件描述集合操作

所谓的文件描述集合fd_set,文件描述符的集合,在linux下,万物都是文件,因此设备、管道、socket等等都是文件,都有自己的文件描述符。

在上面提到的文件描述集合fd_set,主要有以下4个宏可以进行操作。同时文件描述集合中的文件数量最大为FD_SETSIZE,超过此值,会有一些未知情况发生。

2.3.1 FD_ZERO()

清空文件描述集合,将文件描述集合全部置0。

2.3.2 FD_SET()

将某个文件描述符加入文件描述集合。

2.3.3 FD_CLR()

将某个文件描述符从文件描述集合中删除。

2.3.4 FD_ISSET()

用于确定某个文件描述符是否是文件描述集合的成员。

2.4 注意

当函数正常返回,即有事件发生时候,会清除掉没有发生的事件,所以在处理完事件之后,如果需要继续监听全部事件,就需要重新将全部文件描述符重新加入文件描述集合中(即先情况描述集合,然后将全部文件描述符重新加入)。

因为select函数会更新超时参数,比如设置超时时间为5s,然后超时退出后,select会将超时时间更新为0,所以如果需要再次监听,那么就需要重新设置超时时间。

2.5 示例

如下,一个小demo,循环监听两个文件描述符,超时时间为5s。

int test()
{
	int s32FD1 = 17;  //文件描述符1 此处demo写为固定值
	int s32FD2 = 18;  //文件描述符2 此处demo写为固定值
	fd_set fds;		   //文件描述集合
	int s32MaxFd = s32FD2 + 1; //所有文件描述符中的最大值加1
	int s32Ret = 0;
	    
	struct timeval tv;

	while(1)
	{	
		/* 重新加入文件描述集合 */
		FD_ZERO(&fds);	   		//清空文件描述集合
		FD_SET(s32FD1, &fds);  	//加入描述集合
		FD_SET(s32FD2, &fds);	    //加入描述集合
		
		/* 重新设置超时时间 */
		tv.tv_sec = 5;
		tv.tv_usec = 0;

		s32Ret = select(s32MaxFd, &fds, NULL, NULL, &tv);
		if (s32Ret > 0)
		{
			if (FD_ISSET(s32FD1, &fds) //FD1 文件有可读事件
			{
				//do something...
			}			
			else if (FD_ISSET(s32FD2, &fds) //FD2 文件有可读事件
			{
				//do something...
			}
		}
	}
	
	return 0;
}

3 文件描述符集合细探

在上面介绍了文件描述集合,到底文件描述集合是个什么样的东东,在下面来追踪一下。
查看fd_set结构体原型,如下:

/* The fd_set member is required to be an array of longs.  */
typedef long int __fd_mask;

/* fd_set for select and pselect.  */
typedef struct
  {
    /* 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_set结构体里面的成员是long int数组,在我的linux系统中 ,fd_set的长度为128位,FD_SETSIZE的值为1024。128*8=1024,因此每一位对应8个文件描述符。而该数组第一位元素对应于描述符0~31, 第二位元素对应于描述符32-63,一次类推。

在上面的示例中,我们将文件描述符17 18加入集合中,17 18处于第一位元素,因此我们可以打印第一位元素的值来进一步确定。

	printf("fdset:%ld, sizeof:%d, FD_SETSIZE:%d\n", (long int)fds.fds_bits[0], sizeof(fd_set), FD_SETSIZE);

#fdset:393216, sizeof:128, FD_SETSIZE:1024

fdset的值为393216,转换为2进制为110 0000 0000 0000 0000,可以看到从0开始计数,第17 18位被置为1.

当select监听17 18时候,17有可读事件,打印fd_set的值:

printf("fdset:%ld\n", (long int)fds.__fds_bits[0]);
#fdset:131072

fdset的值为131072,转换为2进制为10 0000 0000 0000 0000,可以看到从0开始计数,第17 位被置为1, 而第18位被置为0,因此需要重新select时候,需要重新将全部的文件描述符加入fd_set。

4 select的缺点

select的文件描述集包含了所有需要被监听的文件描述符,如果数量很大,那么每一次调用,都需要从头开始遍历,增加了CPU的消耗。
当select返回时,我们需要遍历知道是那个文件描述符被触发。
当需要重新监听时,我们需要重新将所有的文件描述符加入文件描述符集中。