linux select应用程序,Linux Select 使用

在Linux中,我们可以使用select函数实现I/O端口的复用,同时监视多个文件描述符变化,同时具备超时返回特点。

传递给select函数的参数会告诉内核:

*我们所关心的文件描述符

*对每个描述符,我们所关心的状态。(我们是要想从一个文件描述符中读或者写,还是关注一个描述符中是否出现异常)

*我们要等待多长时间。(我们可以等待无限长的时间,等待固定的一段时间,或者根本就不等待)

从select函数返回后,内核告诉我们一下信息:

*对我们的要求已经做好准备的描述符的个数

*对于三种条件哪些描述符已经做好准备.(读,写,异常)

有了这些返回信息,我们可以调用合适的I/O函数(通常是read或write),并且这些函数不会再阻塞.

#include

int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset,struct timeval

*timeout);

返回:

>0:就绪描述字的正数目

0:超时

-1:出错

首先我们先看一下最后一个参数。它指明我们要等待的时间:

struct timeval{

longtv_sec;   /*秒*/

longtv_usec;  /*微秒*/

}

有三种情况:

timeout == NULL等待无限长的时间。等待可以被一个信号中断。当有一个描述符做好准备或者是捕获到一个信号时函数会返回。如果捕获到一个信号,select函数将返回-1,并将变量erro设为EINTR。

timeout->tv_sec == 0 &&timeout->tv_usec == 0不等待,直接返回。加入描述符集的描述符都会被测试,并且返回满足要求的描述符的个数。这种方法通过轮询,无阻塞地获得了多个文件描述符状态。

timeout->tv_sec !=0 ||timeout->tv_usec!= 0等待指定的时间。当有描述符符合条件或者超过超时时间的话,函数返回。在超时时间即将用完但又没有描述符合条件的话,返回0。对于第一种情况,等待也会被信号所中断。

中间的三个参数readset, writset, exceptset,指向描述符集。这些参数指明了我们关心哪些描述符,和需要满足什么条件(可写,可读,异常)。一个文件描述集保存在fd_set类型中。fd_set类型变量每一位代表了一个描述符。我们也可以认为它只是一个由很多二进制位构成的数组。如下图所示:

f506484d6bbded33e3d443e7f282014e.png

对于fd_set类型的变量我们所能做的就是声明一个变量,为变量赋一个同种类型变量的值,或者使用以下几个宏来控制它

void FD_CLR (int fd,fd_set *fdset); // turn off the bit for fd in fdset

int

FD_ZERO宏将一个fd_set类型变量的所有位都设为0,使用FD_SET将变量的某个位置位。清除某个位时可以使用FD_CLR,我们可以使用FD_ISSET来测试某个位是否被置位。

具体解释select的参数:

(1)intmaxfdp是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错。

说明:对于这个原理的解释可以看上边fd_set的详细解释,fd_set是以位图的形式来存储这些文件描述符。maxfdp也就是定义了位图中有效的位的个数。

注意select函数的第一个参数,是所有加入集合的句柄值的最大那个值还要加1。比如我们创建了3个句柄:

intsa, sb, sc;

sa = socket(...); /* 分别创建3个句柄并连接到服务器上 */

connect(sa,...);

sb = socket(...);

connect(sb,...);

sc = socket(...);

connect(sc,...);

FD_SET(sa, &rdfds);/* 分别把3个句柄加入读监视集合里去 */

FD_SET(sb, &rdfds);

FD_SET(sc, &rdfds);

(2)fd_set*readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读;如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。

(3)fd_set*writefds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。

(4)fd_set*errorfds同上面两个参数的意图,用来监视文件错误异常文件。

(5)structtimeval* timeout是select的超时时间,这个参数至关重要,它可以使select处于三种状态,第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;第三,timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。

说明:

函数返回:

(1)当监视的相应的文件描述符集中满足条件时,比如说读文件描述符集中有数据到来时,内核(I/O)根据状态修改文件描述符集,并返回一个大于0的数。

(2)当没有满足条件的文件描述符,且设置的timeval监控时间超时时,select函数会返回一个为0的值。

(3)当select返回负值时,发生错误。

4 select 机制的优势

为什么会出现select模型?

先看一下下面的这句代码:

int iResult = recv(s, buffer,1024);

这是用来接收数据的,在默认的阻塞模式下的套接字里,recv会阻塞在那里,直到套接字连接上有数据可读,把数据读到buffer里后recv函数才会返

回,不然就会一直阻塞在那里。在单线程的程序里出现这种情况会导致主线程(单线程程序里只有一个默认的主线程)被阻塞,这样整个程序被锁死在这里,如果永

远没数据发送过来,那么程序就会被永远锁死。这个问题可以用多线程解决,但是在有多个套接字连接的情况下,这不是一个好的选择,扩展性很差。

再看代码:

int iResult = ioctlsocket(s, FIOBIO, (unsigned long *)&ul);

iResult = recv(s, buffer,1024);

这一次recv的调用不管套接字连接上有没有数据可以接收都会马上返回。原因就在于我们用ioctlsocket把套接字设置为非阻塞模式了。不过

你跟踪

一下就会发现,在没有数据的情况下,recv确实是马上返回了,但是也返回了一个错误:WSAEWOULDBLOCK,意思就是请求的操作没有成功完成。

看到这里很多人可能会说,那么就重复调用recv并检查返回值,直到成功为止,但是这样做效率很成问题,开销太大。

select模型的出现就是为了解决上述问题。

select模型的关键是使用一种有序的方式,对多个套接字进行统一管理与调度 。

理解select模型:

理解select模型的关键在于理解fd_set,为说明方便,取fd_set长度为1字节,fd_set中的每一bit可以对应一个文件描述符fd。则1字节长的fd_set最大可以对应8个fd。

(1)执行fd_set set;FD_ZERO(&set);则set用位表示是0000,0000。

(2)若fd=5,执行FD_SET(fd,&set);后set变为0001,0000(第5位置为1)

(3)若再加入fd=2,fd=1,则set变为0001,0011

(4)执行select(6,&set,0,0,0)阻塞等待

(5)若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000,0011。注意:没有事件发生的fd=5被清空。

基于上面的讨论,可以轻松得出select模型的特点:

(1)可监控的文件描述符个数取决与sizeof(fd_set)的值。我这边服务器上sizeof(fd_set)=512,每bit表示一个文件描述符,则我服务器上支持的最大文件描述符是512*8=4096。据说可调,另有说虽然可调,但调整上限受于编译内核时的变量值。

(2)将fd加入select监控集的同时,还要再使用一个数据结构array保存放到select监控集中的fd,一是用于再select返回后,array作为源数据和fd_set进行FD_ISSET判断。二是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个参数。

(3)可见select模型必须在select前循环加fd,取maxfd,select返回后利用FD_ISSET判断是否有事件发生。

利用select而不是fork来解决socket中的多客户问题,例程如下。

服务器端

#include

#include

#include

#include

#include

#include

#include

int main()

{

int server_sockfd, client_sockfd;

int server_len, client_len;

struct sockaddr_in server_address;

struct sockaddr_in client_address;

int result;

fd_set readfds, testfds;

server_sockfd = socket(AF_INET, SOCK_STREAM, 0);//建立服务器端socket

server_address.sin_family = AF_INET;

server_address.sin_addr.s_addr = htonl(INADDR_ANY);

server_address.sin_port = htons(9734);

server_len = sizeof(server_address);

bind(server_sockfd, (struct sockaddr *)&server_address, server_len);

listen(server_sockfd, 5);

FD_ZERO(&readfds);

FD_SET(server_sockfd, &readfds);//将服务器端socket加入到集合中

while(1)

{

char ch;

int fd;

int nread;

testfds = readfds;//将需要监视的描述符集copy到select查询队列中,select会对其修改,所以一定要分开使用变量

printf("server waiting/n");

/*无限期阻塞,并测试文件描述符变动 */

result = select(FD_SETSIZE, &testfds, (fd_set *)0,(fd_set *)0, (struct timeval *) 0);

if(result < 1)

{

perror("server5");

exit(1);

}

/*扫描所有的文件描述符*/

for(fd = 0; fd < FD_SETSIZE; fd++)

{

/*找到相关文件描述符*/

if(FD_ISSET(fd,&testfds))

{

/*判断是否为服务器套接字,是则表示为客户请求连接。*/

if(fd == server_sockfd)

{

client_len = sizeof(client_address);

client_sockfd = accept(server_sockfd,

(struct sockaddr *)&client_address, &client_len);

FD_SET(client_sockfd, &readfds);//将客户端socket加入到集合中

printf("adding client on fd %d/n", client_sockfd);

}

/*客户端socket中有数据请求时*/

else

{

ioctl(fd, FIONREAD, &nread);//取得数据量交给nread

/*客户数据请求完毕,关闭套接字,从集合中清除相应描述符 */

if(nread == 0)

{

close(fd);

FD_CLR(fd, &readfds); //去掉关闭的fd

printf("removing client on fd %d/n", fd);

}

/*处理客户数据请求*/

else

{

read(fd, &ch, 1);

sleep(5);

printf("serving client on fd %d/n", fd);

ch++;

write(fd, &ch, 1);

}

}

}

}

}

}

客户端

#include

#include

#include

#include

#include

#include

int main()

{

int client_sockfd;

int len;

struct sockaddr_in address;//服务器端网络地址结构体

int result;

char ch = 'A';

client_sockfd = socket(AF_INET, SOCK_STREAM, 0);//建立客户端socket

address.sin_family = AF_INET;

address.sin_addr.s_addr = inet_addr(“127.0.0.1”);

address.sin_port = 9734;

len = sizeof(address);

result = connect(client_sockfd, (struct sockaddr *)&address, len);

if(result == -1)

{

perror("oops: client2");

exit(1);

}

write(client_sockfd, &ch, 1);

read(client_sockfd, &ch, 1);

printf("char from server = %c/n", ch);

close(client_sockfd);

zexit(0);

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值