poll和select.docx
《poll和select.docx》由会员分享,可在线阅读,更多相关《poll和select.docx(5页珍藏版)》请在冰豆网上搜索。
![poll和select.docx](https://file1.bdocx.com/fileroot1/2022-12/30/83aa33f1-f9df-452c-b114-14faa5440c67/83aa33f1-f9df-452c-b114-14faa5440c671.gif)
poll和select
详述socket编程之select()和poll()函数
select()函数和poll()函数均是主要用来处理多路I/O复用的情况。
比如一个服务器既想等待输入终端到来,又想等待若干个套接字有客户请求到达,这时候就需要借助select或者poll函数了。
(一)select()函数、
原型如下:
1intselect(intfdsp1,fd_set*readfds,fd_set*writefds,fd_set*errorfds,conststructtimeval*timeout);
各个参数含义如下:
intfdsp1:
最大描述符值+1
fd_set*readfds:
对可读感兴趣的描述符集
fd_set*writefds:
对可写感兴趣的描述符集
fd_set*errorfds:
对出错感兴趣的描述符集
structtimeval*timeout:
超时时间
select函数会在发生以下情况时返回:
1readfds集合中有描述符可读
2writefds集合中有描述符可写
3errorfds集合中有描述符遇到错误条件
4指定的超时时间timeout到了
过去,一个fd_set通常只能包含<32的fd(文件描述字),因为fd_set其实只用了一个32位矢量来表示fd;现在,UNIX系统通常会在头文件中定义常量FD_SETSIZE,它是数据类型fd_set的描述字数量,其值通常是1024,这样就能表示<1024的fd
问题2:
当select返回的时候,如何知道是哪个fd发生变化了,fs_set各个位发生了什么变化?
当select返回的时候,rset位都将被置0,除了那些有变化的fd位..假设有两个文件描述符,值分别是7和9,被放在readfds中。
当select()返回时,如果7仍然在set中,则这个文件描述符已经准备好被读取而不会阻塞。
如果9已经不在set中,则读取它将可能会阻塞(我说可能是因为数据可能正好在select返回后就可用,这种情况下,下一次调用select()将返回文件描述符准备好读取)。
当select返回时,描述符集合将被修改以指示哪些个描述符正处于可读、可写或有错误状态。
可以用FD_ISSET宏对描述符进行测试以找到状态变化的描述符。
如果select因为超时而返回的话,所有的描述符集合都将被清空。
select函数返回状态发生变化的描述符总数。
返回0意味着超时。
失败则返回-1并设置errno。
可能出现的错误有:
EBADF(无效描述符)、EINTR(因终端而返回)、EINVAL(nfds或timeout取值错误)。
设置描述符集合通常用如下几个宏定义:
1FD_ZERO(fd_set*fdset);/*clearallbitsinfdset*/
2FD_SET(intfd,fd_set*fdset);/*turnonthebitforfdinfd_set*/
3FD_CLR(intfd,fd_set*fdset);/*turnoffthebitforfdinfd_set*/
4intFD_ISSET(intfd,fd_set*fdset);/*isthebitforfdoninfdset?
*/
如:
1fd_setrset;
2FD_ZERO(&rset);/*initializetheset:
allbitsoff*/
3FD_SET(1,&rset);/*turnonbitforfd1*/
4FD_SET(4,&rset);/*turnonbitforfd4*/
5FD_SET(5,&rset);/*turnonbitforfd5*/
当select返回的时候,rset位都将被置0,除了那些有变化的fd位。
当发生如下情况时认为是可读的:
1,socket的receivebuffer中的字节数大于socket的receivebuffer的low-watermark属性值。
(low-watermark值类似于分水岭,当receivebuffer中的字节数小于low-watermark值的时候,认为socket还不可读,只有当receivebuffer中的字节数达到一定量的时候才认为socket可读)
2,连接半关闭(读关闭,即收到对端发来的FIN包)
3,发生变化的描述符是被动套接字,而连接的三路握手完成的数量大于0,即有新的TCP连接建立
4,描述符发生错误,如果调用read系统调用读套接字的话会返回-1。
(二)poll()函数
poll()函数则与select()采用的方式不同,它通过一个结构数组保存各个描述符的状态,每个结构体第一项fd代表描述符,第二项代表要监听的事件,也就是感兴趣的事件,而第三项代表poll()返回时描述符的返回状态
原型如下:
1intpoll(structpollfd*fdarray,unsignedlongnfds,inttimeout);
各参数含义如下:
structpollfd*fdarray:
一个结构体,用来保存各个描述符的相关状态。
unsignedlongnfds:
fdarray数组的大小,即里面包含有效成员的数量。
inttimeout:
设定的超时时间。
(以毫秒为单位)
poll函数返回值及含义如下:
-1:
有错误产生
0:
超时时间到,而且没有描述符有状态变化
>0:
有状态变化的描述符个数
着重讲fdarray数组,因为这是它和select()函数主要的不同的地方:
pollfd的结构如下:
1structpollfd{
2intfd;/*descriptortocheck*/
3shortevents;/*eventsofinterestonfd*/
4shortrevents;/*eventsthatoccuredonfd*/
5};
其实poll()和select()函数要处理的问题是相同的,只不过是不同组织在几乎相同时刻同时推出的,因此才同时保留了下来。
select()函数把可读描述符、可写描述符、错误描述符分在了三个集合里,这三个集合都是用bit位来标记一个描述符,一旦有若干个描述符状态发生变化,那么它将被置位,而其他没有发生变化的描述符的bit位将被clear,也就是说select()的readset、writeset、errorset是一个value-result类型,通过它们传值,而也通过它们返回结果。
这样的一个坏处是每次重新select的时候对集合必须重新赋值。
而poll()函数则与select()采用的方式不同,它通过一个结构数组保存各个描述符的状态,每个结构体第一项fd代表描述符,第二项代表要监听的事件,也就是感兴趣的事件,而第三项代表poll()返回时描述符的返回状态。
合法状态如下:
POLLIN:
有普通数据或者优先数据可读
POLLRDNORM:
有普通数据可读
POLLRDBAND:
有优先数据可读
POLLPRI:
有紧急数据可读
POLLOUT:
有普通数据可写
POLLWRNORM:
有普通数据可写
POLLWRBAND:
有紧急数据可写
POLLERR:
有错误发生
POLLHUP:
有描述符挂起事件发生
POLLNVAL:
描述符非法
对于POLLIN|POLLPRI等价与select()的可读事件;POLLOUT|POLLWRBAND等价与select()的可写事件;POLLIN等价与POLLRDNORM|POLLRDBAND,而POLLOUT等价于POLLWRBAND。
如果你对一个描述符的可读事件和可写事件以及错误等事件均感兴趣那么你应该都进行相应的设置。
对于timeout的设置如下:
INFTIM:
waitforever
0:
returnimmediately,donotblock
>0:
waitspecifiednumberofmilliseconds
poll使用样式示例
代码:
引用
#include
#include
#include
#include
#include
#include
#include
#include
intmain(intargc,char**argv)
{
intsfd1,sfd2,sfd3;
structpollfdEvents[3];
intretval;
charbuff[256];
intreadcnt;
sfd1=open("/dev/ttyS1",O_RDWR|O_NOCTTY);
sfd2=open("/dev/ttyS2",O_RDWR|O_NOCTTY);
sfd3=open("/dev/ttyS3",O_RDWR|O_NOCTTY);
...
/*各个串行环境设定程序*/
...
memset(Event,0,sizeof(Events));
Event[0].fd=sfd1;
Event[0].events=POLLIN|POLLERR;/*关心读取和出错事件*/
Event[1].fd=sfd2;
Event[1].events=POLLIN|POLLERR;/*关心读取和出错事件*/
Event[2].fd=sfd3;
Event[2].events=POLLIN|POLLERR;/*关心读取和出错事件*/
while
(1){
/*等待事件*/
retval=poll((structpollfd*)&Events,3,5000);
if(retval<0){
perror("poll");
exit(EXIT_FAILURE);
}
if(retval==0){
prinntf("nodatain5seconds.\n");
continue;
}
for(i=0;i<3;i++){
/*检查错误*/
if(Events[i].revents&POLLERR){
printf("deviceerror!
\n");
exit(EXIT_FAILURE);
}
/*检查是否存在传递的数据(可读)*/
if(Events[i].revents&POLLIN){
readcnt=read(Events[i].fd,buff,256);
write(Events[i].fd,buff,readcnt);
}
}
}
close(sfd1);
close(sfd2);
colse(sfd3);
}
程序说明:
sfd1,sfd2,sfd3变量经过open()成功后,会记录各个串口的有效文件描述符,使用这些文件描述符来设置串行通信速的等适当的串口环境。
poll()处理输入输出的复用。
在structpollfdEvents[3]上设置了将要处理的事件以及事件发生的条件---POLLIN和POLLERR。
在对poll()函数中相关参数初始化后,调用poll()函数等待感兴趣的事件(POLLIN和POLLERR)的发生。
在事件发生前,进程进入睡眠状态,并把相应进程的运行权限移交给其他的进程。
如果发生了以上的注册事件,那么进程会重新运行,也就是说如果发生了可接收数据或因其出错时,那么poll的返回值retval<0;如果没有出错,那么可以利用返回值检查是发生了哪个感兴趣的事件。
在没有发生任何事件时,由于使用了等待事件5000ms,在超时时,进程会被唤醒,此时返回值为0。