Select 在Socket 编程中还是比较重要的,可是对于初学Socket 的人来说都不太爱用Select 写程序,他们只是习惯写诸如 connect 、accept 、recv 或recvfrom 这样的阻塞程序(所谓阻塞方式block ,顾名思义,就是进程或是线程执行到这些函数时必须等待某个事件的发生,如果事件没有发生,进程或线程就被阻塞,函数不能立即返回) 。
可是使用
Select
就可以完成非阻塞(所
谓
非阻塞方式
non- block
,就是
进
程或
线
程
执
行此函数
时
不必非要等待事件的
发
生,一旦
执
行肯定返回,以返回
值
的不同来反映函数的
执
行情况,如果事件
发
生
则
与阻塞方式相同,若事件没有
发
生
则
返回一个代
码
来告知事件未
发
生,而
进
程或
线
程
继续执
行,所以效率
较
高)方式工作的程序,它能
够监视
我
们
需要
监视
的文件描述符的
变
化情况
——
读
写或是异常。
下面详细介绍一下!
Select
的函数格式(
我所说的是Unix
系统下的伯克利socket
编程,和windows
下的有区别,一会儿说明)
:
int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);
先说明两个结构体:
第一,struct fd_set
可以理解
为
一个集合,
这
个集合中存放的是文件描述符(filedescriptor)
,即文件句柄
,这可以是我们所说的普通意义的文件,当然Unix
下任何设备、管道、FIFO
等都是文件形式,全部包括在内,所以毫无疑问一个socket
就是一个文件,socket
句柄就是一个文件描述符。
fd_set
集合可以通过一些宏由人为来操作,比如
清空集合FD_ZERO(fd_set *)
;
将一个给定的文件描述符加入集合之中FD_SET(int ,fd_set *)
;
将一个给定的文件描述符从集合中删除FD_CLR(int ,fd_set*)
;
检查集合中指定的文件描述符是否可以读写FD_ISSET(int ,fd_set* )
。一会儿
举
例
说
明。
第二,
struct timeval
是一个大家常用的
结
构,用来代表
时间值
,有两个成
员
,一个是秒数,另一个是毫秒数。
具体解
释
select
的参数:
int maxfdp
是一个整数
值
,是指集合中所有文件描述符的范
围
,即所有文件描述符的最大
值
加
1
,不能
错
!在
Windows
中
这
个参数的
值
无所
谓
,可以
设
置不正确。
fd_set*readfds
是指向
fd_set
结
构的指
针
,
这
个集合中
应该
包括文件描述符,我
们
是要
监视这
些文件描述符的
读变
化的,即我
们关
心是否可以从
这
些文件中
读
取数据了,如果
这
个集合中有一个文件可
读
,
select
就会返回一个大于
0
的
值
,表示有文件可
读
,如果没有可
读
的文件,
则
根据
timeout
参数再判断是否超
时
,若超出
timeout
的
时间
,
select
返回
0
,若
发
生
错误
返回
负值
。可以
传
入
NULL
值
,表示不
关
心任何文件的
读变
化。
fd_set*writefds
是指向
fd_set
结
构的指
针
,
这
个集合中
应该
包括文件描述符,我
们
是要
监视这
些文件描述符的写
变
化的,即我
们关
心是否可以向
这
些文件中写入数据了,如果
这
个集合中有一个文件可写,
select
就会返回一个大于
0
的
值
,表示有文件可写,如果没有可写的文件,
则
根据
timeout
参数再判断是否超
时
,若超出
timeout
的
时间
,
select
返回
0
,若
发
生
错误
返回
负值
。可以
传
入
NULL
值
,表示不
关
心任何文件的写
变
化。
fd_set *errorfds
同上面两个参数的意
图
,用来
监视
文件
错误
异常。
struct timeval *timeout
是
select
的超
时时间
,
这
个参数至
关
重要,它可以使
select
处
于三
种
状
态
,
第一,若将 NULL 以形参 传 入,即不 传 入 时间结 构,就是将 select 置于阻塞状 态 ,一定等到 监视 文件描述符集合中某个文件描述符 发 生 变 化 为 止;
第二,若将 时间值设为 0 秒 0 毫秒,就 变 成一个 纯 粹的非阻塞函数,不管文件描述符是否有 变 化,都立刻返回 继续执 行,文件无 变 化返回 0 ,有 变 化返回一个正 值 ;
第三,
timeout
的
值
大于
0
,
这
就是等待的超
时时间
,即
select
在
timeout
时间
内阻塞,超
时时间
之内有事件到来就返回了,否
则
在超
时
后不管怎
样
一定返回,返回
值
同上述。
返回
值
:
负值
:
select
错误
正
值
:某些文件可
读
写或出
错
0
:等待超
时
,没有可
读
写或
错误
的文件
在有了
select
后可以写出像
样
的网
络
程序来!
举
个
简单
的例子,就是从网
络
上接受数据写入一个文件中。
例子:
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
linux c
语言 select
函数用法
表头文件
#i nclude<sys/time.h>
#i nclude<sys/types.h>
#i nclude<unistd.h>
定义函数
int select(int n,fd_set * readfds,fd_set * writefds,fd_set * exceptfds,struct timeval * timeout);
函数说明 select()
用来等待文件描述词状态的改变。
参数
n
代表最大的文件描述
词
加
1
,参数
readfds
、
writefds
和
exceptfds
称
为
描述
词组
,是用来回
传该
描述
词
的
读
,写或例外的状况。底下的宏提供了
处
理
这
三
种
描述
词组
的方式
:
FD_CLR(inr fd,fd_set* set)
;用来清除描述
词组
set
中相
关
fd
的位
FD_ISSET(int fd,fd_set *set)
;用来
测试
描述
词组
set
中相
关
fd
的位是否
为
真
FD_SET
(
int fd,fd_set*set
);用来
设
置描述
词组
set
中相
关
fd
的位
FD_ZERO
(
fd_set *set
); 用来清除描述
词组
set
的全部位
(关于fd_set及相关宏的定义见/usr/include/sys/types.h)
参数
timeout
为结
构
timeval
,用来
设
置
select()
的等待
时间
,其
结
构定
义
如下
struct timeval
{
time_t tv_sec;
time_t tv_usec;
};
返回
值
如果参数
timeout
设为
NULL
则
表示
select
()没有
timeout
。
错误
代
码
执
行成功
则
返回文件描述
词
状
态
已改
变
的个数,如果返回
0
代表在描述
词
状
态
改
变
前已超
过
timeout
时间
,当有
错误发
生
时则
返回
-1
,
错误
原因存于
errno
,此
时
参数
readfds
,
writefds
,
exceptfds
和
timeout
的
值变
成不可
预测
。
EBADF
文件描述
词为
无效的或
该
文件已
关闭
EINTR
此
调
用被信号所中断
...
int sockfd;
fd_set fdR;
struct timeval timeout = ..;
...
for(;;) {
FD_ZERO(&fdR);
FD_SET(sockfd, &fdR);
switch (select(sockfd + 1, &fdR, NULL, &timeout)) {
case -1:
error handled by u;
case 0:
timeout hanled by u;
default:
if (FD_ISSET(sockfd)) {
now u read or recv something;
/* if sockfd is father and
server socket, u can now
accept() */
}
}
}
补充关于select在异步(非阻塞)connect中的应用:
1.将打开的socket设为非阻塞的,可以用fcntl(socket, F_SETFL, O_NDELAY)完
成(有的系统用FNEDLAY也可).
2.发connect调用,这时返回-1,但是errno被设为EINPROGRESS,意即connect仍旧
在进行还没有完成.
3.将打开的socket设进被监视的可写(注意不是可读)文件集合用select进行监视,
如果可写,用
getsockopt(socket, SOL_SOCKET, SO_ERROR, &error, sizeof(int));
来得到error的值,如果为零,则connect成功.
本文详细介绍了Select函数在Socket编程中的应用,包括如何通过非阻塞方式提高程序效率,Select函数的具体参数及其含义,以及一个简单的接收网络数据并写入文件的例子。
436

被折叠的 条评论
为什么被折叠?



