linux select()函数分析

34 篇文章 2 订阅

参考:

http://blog.csdn.net/zi_jin/article/details/4214359

http://www.cnitblog.com/doublezxh/archive/2010/09/26/69218.html


阻塞方式block,就是进程或是线程执行到这些函数时必须等待某个事件的发生,如果事件没有发生,进程或线程就被阻塞,函数不能立即返回。

非阻塞方式non-block,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高。



二.I/O多路转接

       如果我们想从多个文件描述符读或写数据,如果我们用以前学过的函数(read,write等)去处理可能会阻塞在一个文件描述符上,不能处理其他的文件描述符。那是因为我们以前学的I/O处理函数,都是阻塞的I/O处理函数,它们的特点是,如果缓冲区里有数据它们就会把数据写到文件中,如果缓存区没有数据他们就会等待(阻塞)直到有数据可读。这就造成了他们无法对多个文件描述符进行操作。而对多个文件描述符进行操作在网络通信方面却是执关重要的。

       一种比较好的解决方案就是I/O多路转接技术。它现构造一张有关文件描述符的列表,然后调用一个函数,直到这些描述符中的一个已经准备好进行I/O时,该函数才返回。在返回时,它告诉进程那些描述符已经准备好可以进行I/O。poll,selsct,pselect这三个函数使我们能够执行I/O多路转接,下面就分别介绍它们。

 

2.

名称::

select

功能:

指行I/O多路转接

头文件:

#include <sys/select.h>

函数原形:

int select(int maxfdpl,fd_set *restrict readfds,fd_set *restrict writefds,fd_set *testrict exceptfds,struct timeval *testrict tvptr);

参数:

maxfdpl   最大描述符加1

readfds     读描述符集   

writefds    写描述符集

excepfds    异常描述符集

tvptr      愿意等待的时间

返回值:

准备就绪的文件描述符数,若超时则返回0,若出错则返回-1

      

 

 

 

 

 

 

 

 

 

 

 

 

 

 select函数使我们可以执行I/O多路转接。传向select的参数告诉内核:我们所关心的描述符。对于每个描述符我们所关心的状态。以及我们愿意等待的时间。从select返回时,内核告诉我们:以准备好的描述符的数量。对于读、写或异常这三个状态中的每一个,哪些描述符已经准备好。

       这个函数比较复杂,我们一个一个参数的看。

       第一个参数maxfdp1的意思是“最大描述符加1”。也可将第一个参数设置为FD_SETSIZE,这是<sys/select.h>中的一个常数,它说明了最大的描述符数(经常是1024)。如果将这个参数设置为我们所关注的最大描述符编号值加一,内核就只需在此范围内寻找打开的位,而不必在三个描述符集中的数百位内搜索。

       中间的三个参数readfds、writefds和exceptfds是指向描述符集的指针。这三个描述符集说明了我们关心的可读(readfds)、可写(writefd)或处于异常条件(wxcepfds)的各个描述符。每个描述符集存放在一个fd_set数据类型中。这种结构相当于一个描述符的数组,它为每个可能的描述符设置1位。

fd0     fd1     fd2     fd3       fdn

0

0

0

0

readfds

 

fd0     fd1     fd2     fd3       fdn

0

0

0

0

writefds

 

fd0     fd1     fd2     fd3       fdn

0

0

0

0

excepfds

 

       可用下面4个函数对描述符集进行操作。

 

       select的中间三个参数中的任意一个或全部都可以是空指针,这表示对相应状态不关心。如果所有三个指针都是空指针,则select提供了较sleep更精确的计时器。其等待时间可以小于1秒。

      

       tvptr指定最后等待的时间,它的结构是:

struct timeval{

       long tv_sec; 秒

       long tv_usec; 微秒

};

有三种情况:

(1) tvptr==NULL:永远等待。如果捕捉到一个信号则中断此无限等待。当所指定的描述符中的一个已经准备好或捕捉到一个信号则返回。如果捕捉到一个信号,则select返回-1,errno设置为EINTR.

(2) tvptr->tv_sec==0&&tvptr_usec==0 完全不等待。测试所有的描述符并立即返回。这是得到多个描述符的状态而不阻塞select函数的轮询方法。

(3)tvptr->tv_sec!=0||tvptr_usec!=0 等待指定的秒数或微秒数。当指定的描述符之一已准备好,或当指定的时间值已超过时立即返回。如果在超时还没有一个描述符准备好,则返回值是0。

 

3.

名称::

FD_ISSET/FD_CLR/FD_SET/FD_ZERO

功能:

描述符集处理函数

头文件:

#include <sys/select.h>

函数原形:

int FD_ISSET(int fd,fd_set *fdset);

void FD_CLR(int fd,fd_set *fdset);

void FD_SET(int fd,fd_set *fdset);

void FD_ZERO(fd_set *fdset);

参数:

fdset     描述符集

fd        描述符

返回值:

若fd在描述符集中则返回非0值,否则返回0(FD_ISSET)

      

 

 

 

 

 

 

 

 

 

 

 

 

 

 调用FD_ZERO将一个指定的fd_set变量的所有位设置为0。调用FD_SET设置一个fd_set变量的指定位。调用FD_CLR将一指定位清除。最后调用FD_ISSET测试一指定位是否设置。声明了一个描述符集后,必须用FD_ZERO清除其所有位,然后在其中设置我们关心的各个位。

       下面是select函数实现I/O多路转接的一个例子

#include <sys/time.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
	int keyboard;
	int ret=0;
	char c;
	
	fd_set readfd;
	struct timeval timeout;

	if((keyboard=open("/dev/tty",O_RDONLY|O_NONBLOCK))<0) /*打开标准输入(键盘)的文件描述符*/
	{
		perror("open failed");
		exit(1);/*如果失败则退出程序*/
	}

	while(1)
	{
		timeout.tv_sec=3;/*设置等待时间为3秒*/
		timeout.tv_usec=0;
		FD_ZERO(&readfd);/*初始化描述符集*/
		FD_SET(keyboard,&readfd);/*把标准输入(键盘)加入到描述符集中*/

		ret=select(keyboard+1,&readfd,NULL,NULL,&timeout);
		if(ret==0)/*如果超时打印下面的语句*/
		{
			printf("Time out!\n");
			exit(1);
		}

		if(FD_ISSET(keyboard,&readfd))/*如果描述符集readfd的keyboard位被设置*/
		{
			read(keyboard,&c,1);/*从键盘上读如一个字符*/
			if(c=='\n')
				continue;
			printf("You input is %c\n",c);
			if(c=='q')
            break;
		}
	}

	return 0;
}

本程序实现了一个文件描述符的非阻塞I/O。程序执行后等待用户输入,如果用户输入字符就会把它打印到屏幕上。如果用户在3秒钟未输入任何字符,程序就打印“Time out!”.


Select在Socket编程中还是比较重要的,可是对于初学Socket的人来说都不太爱用Select写程序,他们只是习惯写诸如connect、accept、recv或recvfrom这样的阻塞程序。可是使用Select就可以完成非阻塞方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况——读写或是异常。
在有了select后可以写出像样的网络程序来!举个简单的例子,就是从网络上接受数据写入一个文件中。 

int main() 
{ 
	int sock; 
	FILE *fp; 

	struct fd_set fds; 
	struct timeval timeout={3,0}; //select等待3秒,3秒轮询,要非阻塞就置0 
	char buffer[256]={0}; //256字节的接收缓冲区 

	/* 假定已经建立UDP连接,具体过程不写,简单,当然TCP也同理,主机ip和port都已经给定,要写的文件已经打开 
	sock=socket(...); 
	bind(...); 
	fp=fopen(...); */ 

	while(1) 
	{ 
		FD_ZERO(&fds); //每次循环都要清空集合,否则不能检测描述符变化 
		FD_SET(sock,&fds); //添加描述符 
		FD_SET(fp,&fds); //同上 

		maxfdp=sock>fp?sock+1:fp+1; //描述符最大值加1 

		switch(select(maxfdp,&fds,&fds,NULL,&timeout)) //select使用 
		{ 
			case -1: 
				exit(-1);
				break; //select错误,退出程序 
			case 0:
				break; //再次轮询 
			default: 
				if(FD_ISSET(sock,&fds)) //测试sock是否可读,即是否网络上有数据 
				{ 
					recvfrom(sock,buffer,256,.....);//接受网络数据 
					if(FD_ISSET(fp,&fds)) //测试文件是否可写 
					fwrite(fp,buffer...);//写入文件 
					buffer清空; 
				}// end if break; 

		}// end switch 
	}//end while 
}//end main</span>



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值