计算机网络--linux下select函数详解

select()的使用

所需头文件:
#include <sys/select.h>

#include <sys/time.h>

#include <sys/types.h>

#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

功能:

监视并等待多个文件描述符的属性变化(可读、可写或错误异常)。select()函数监视的文件描述符分 3 类,分别是writefds、readfds、和 exceptfds。调用后 select() 函数会阻塞,直到有描述符就绪(有数据可读、可写、或者有错误异常),或者超时( timeout 指定等待时间),函数才返回。当 select()函数返回后,可以通过遍历 fdset,来找到就绪的描述符。

参数:
nfds: 要监视的文件描述符的范围,一般取监视的描述符数的最大值+1,如这里写 10, 这样的话,
描述符 01, 2 …… 9 都会被监视,在 Linux 上最大值一般为1024。



readfd: 监视的可读描述符集合,只要有文件描述符即将进行读操作,这个文件描述符就存储到这。

writefds: 监视的可写描述符集合。

exceptfds: 监视的错误异常描述符集合



中间的三个参数 readfds、writefds 和 exceptfds 指定我们要让内核监测读、写和异常条件的描述字。
如果不需要使用某一个的条件,就可以把它设为空指针( NULL )。集合fd_set 中存放的是文件描述符,
可通过以下四个宏进行设置:

//清空集合

void FD_ZERO(fd_set *fdset); 



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

void FD_SET(int fd, fd_set *fdset);



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

void FD_CLR(int fd, fd_set *fdset);



 // 检查集合中指定的文件描述符是否可以读写 

int FD_ISSET(int fd, fd_set *fdset); 


timeout: 超时时间,它告知内核等待所指定描述字中的任何一个就绪可花多少时间。
其 timeval 结构用于指定这段时间的秒数和微秒数。

struct timeval

{

time_t tv_sec;       /* 秒 */

suseconds_t tv_usec; /* 微秒 */

};

这个参数有三种可能:


1)永远等待下去:仅在有一个描述字准备好 I/O 时才返回。为此,把该参数设置为空指针 NULL2)等待固定时间:在指定的固定时间( timeval 结构中指定的秒数和微秒数)内,在有一个描述字准备好
 I/O 时返回,如果时间到了,就算没有文件描述符发生变化,这个函数会返回 03)根本不等待(不阻塞):检查描述字后立即返回,这称为轮询。为此,struct timeval变量的时间值
指定为 00 微秒,文件描述符属性无变化返回 0,有变化返回准备好的描述符数量。


返回值:
成功:就绪描述符的数目,超时返回 0,

出错:-1

我们写这么一个例子,同时循环读取标准输入的内容,读取有名管道的内容,默认的情况下,标准输入没有内容,read()时会阻塞,同样的,有名管道如果没有内容,read()也会阻塞,我们如何实现循环读取这两者的内容呢?最简单的方法是,开两个线程,一个线程循环读标准输入的内容,一个线程循环读有名管道的内容。而在这里,我们通过 select() 函数实现这个功能:

#include <sys/select.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
 
int main(int argc, char *argv[])
{
	fd_set rfds;
	struct timeval tv;
	int ret;
	int fd;
	
	ret = mkfifo("test_fifo", 0666); // 创建有名管道
	if(ret != 0){
		perror("mkfifo:");
	}
	
	fd = open("test_fifo", O_RDWR); // 读写方式打开管道
	if(fd < 0){
		perror("open fifo");
		return -1;
	}
	
	ret = 0;
	
	while(1){
		// 这部分内容,要放在while(1)里面
		FD_ZERO(&rfds);		// 清空
		FD_SET(0, &rfds);   // 标准输入描述符 0 加入集合
		FD_SET(fd, &rfds);  // 有名管道描述符 fd 加入集合
		
		// 超时设置
		tv.tv_sec = 1;
		tv.tv_usec = 0;
		
		// 监视并等待多个文件(标准输入,有名管道)描述符的属性变化(是否可读)
		// 没有属性变化,这个函数会阻塞,直到有变化才往下执行,这里没有设置超时
		// FD_SETSIZE 为 <sys/select.h> 的宏定义,值为 1024
		ret = select(FD_SETSIZE, &rfds, NULL, NULL, NULL);
		//ret = select(FD_SETSIZE, &rfds, NULL, NULL, &tv);
 
		if(ret == -1){ // 出错
			perror("select()");
		}else if(ret > 0){ // 准备就绪的文件描述符
		
			char buf[100] = {0};
			if( FD_ISSET(0, &rfds) ){ // 标准输入
				read(0, buf, sizeof(buf));
				printf("stdin buf = %s\n", buf);
				
			}else if( FD_ISSET(fd, &rfds) ){ // 有名管道
				read(fd, buf, sizeof(buf));
				printf("fifo buf = %s\n", buf);
			}
			
		}else if(0 == ret){ // 超时
			printf("time out\n");
		}
	
	}
	
	return 0;
}

当前终端运行此程序,另一终端运行一个往有名管道写内容的程序,运行结果如下:

在这里插入图片描述

下面为上面例子的往有名管道写内容的示例代码:

#include <sys/select.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
 
int main(int argc, char *argv[])
{
	//select_demo(8);
	
	fd_set rfds;
	struct timeval tv;
	int ret;
	int fd;
	
	ret = mkfifo("test_fifo", 0666); // 创建有名管道
	if(ret != 0){
		perror("mkfifo:");
	}
	
	fd = open("test_fifo", O_RDWR); // 读写方式打开管道
	if(fd < 0){
		perror("open fifo");
		return -1;
	}
	
	while(1){
		char *str = "this is for test";
		write(fd, str, strlen(str)); // 往管道里写内容
		printf("after write to fifo\n");
		sleep(5);
	}
	
	return 0;
}

运行结果如下:

在这里插入图片描述

select()目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点。

select()的缺点在于:
1)每次调用 select(),都需要把 fd 集合从用户态拷贝到内核态,这个开销在 fd 很多时会很大,同时每次调用 select() 都需要在内核遍历传递进来的所有 fd,这个开销在 fd 很多时也很大。
2)单个进程能够监视的文件描述符的数量存在最大限制,在 Linux 上一般为 1024,可以通过修改宏定义甚至重新编译内核的方式提升这一限制,但是这样也会造成效率的降低。

在这里插入图片描述

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值