Linux poll()解析以及与select()对比

来源:互联网 发布:韩国漫画下载软件 编辑:程序博客网 时间:2024/06/05 04:52

Linux poll()解析


 poll()函数:这个函数是某些Unix系统提供的用于执行与select()函数同等功能的函数,下面是这个函数的声明:
    #include <poll.h>
    int poll(struct pollfd fds[], nfds_t nfds, int timeout);
参数说明:
fds:是一个struct pollfd结构类型的数组,用于存放需要检测其状态的Socket描述符;每当调用这个函数之后,系统不会清空这个数组,操作起来比较方便;特别是对于 socket连接比较多的情况下,在一定程度上可以提高处理的效率;这一点与select()函数不同,调用select()函数之后,select() 函数会清空它所检测的socket描述符集合,导致每次调用select()之前都必须把socket描述符重新加入到待检测的集合中;因 此,select()函数适合于只检测一个socket描述符的情况,而poll()函数适合于大量socket描述符的情况;
nfds:nfds_t类型的参数,用于标记数组fds中的结构体元素的总数量;
timeout:是poll函数调用阻塞的时间,单位:毫秒;


返回值:

>0:数组fds中准备好读、写或出错状态的那些socket描述符的总数量;
==0:数组fds中没有任何socket描述符准备好读、写,或出错;此时poll超时,超时时间是timeout毫秒;换句话说,如果所检测的 socket描述符上没有任何事件发生的话,那么poll()函数会阻塞timeout所指定的毫秒时间长度之后返回,如果timeout==0,那么 poll() 函数立即返回而不阻塞,如果timeout==INFTIM,那么poll() 函数会一直阻塞下去,直到所检测的socket描述符上的感兴趣的事件发 生是才返回,如果感兴趣的事件永远不发生,那么poll()就会永远阻塞下去;

-1:  poll函数调用失败,同时会自动设置全局变量errno;

如果待检测的socket描述符为负值,则对这个描述符的检测就会被忽略,也就是不会对成员变量events进行检测,在events上注册的事件也会被忽略,poll()函数返回的时候,会把成员变量revents设置为0,表示没有事件发生;


另外,poll() 函数不会受到socket描述符上的O_NDELAY标记和O_NONBLOCK标记的影响和制约,也就是说,不管socket是阻塞的还是非阻塞 的,poll()函数都不会受到影响;而select()函数则不同,select()函数会受到O_NDELAY标记和O_NONBLOCK标记的影 响,如果socket是阻塞的socket,则调用select()跟不调用select()时的效果是一样的,socket仍然是阻塞式TCP通讯,相 反,如果socket是非阻塞的socket,那么调用select()时就可以实现非阻塞式TCP通讯;


所以poll() 函数的功能和返回值的含义与 select() 函数的功能和返回值的含义是完全一样的,两者之间的差别就是内部实现方式不一样,select()函数基本上可以在所有支持文件描述符操作的系统平台上运 行(如:Linux 、Unix 、Windows、MacOS等),可移植性好,而poll()函数则只有个别的的操作系统提供支持(如:SunOS、Solaris、AIX、HP提供 支持,但是Linux不提供支持),可移植性差;
strust pollfd结构说明:
typedef struct pollfd {
        int fd;                               /* 需要被检测或选择的文件描述符*/
        short events;                   /* 对文件描述符fd上感兴趣的事件 */
        short revents;                  /* 文件描述符fd上当前实际发生的事件*/
} pollfd_t;

typedef unsigned long   nfds_t;


经常检测的事件标记: POLLIN/POLLRDNORM(可读)、POLLOUT/POLLWRNORM(可写)、POLLERR(出错)
如果是对一个描述符上的多个事件感兴趣的话,可以把这些常量标记之间进行按位或运算就可以了;
比如:对socket描述符fd上的读、写、异常事件感兴趣,就可以这样做:struct pollfd  fds;
fds[nIndex].events=POLLIN | POLLOUT | POLLERR;
当 poll()函数返回时,要判断所检测的socket描述符上发生的事件,可以这样做: struct pollfd  fds;
检测可读TCP连接请求:
if((fds[nIndex].revents & POLLIN) == POLLIN){ //接收数据/调用accept()接收连接请求 }
检测可写:
if((fds[nIndex].revents & POLLOUT) == POLLOUT){ //发送数据 }
检测异常:
if((fds[nIndex].revents & POLLERR) == POLLERR){ //异常处理 }


 每一个pollfd结构体指定了一个被监视的文件描述符,可以传递多个结构体,指示poll()监视多个文件描述符。每个结构体的events域是监视该文件描述符的事件掩码,由用户来设置这个域。revents域是文件描述符的操作结果事件掩码。内核在调用返回时设置这个域。events域中请求的任何事件都可能在revents域中返回。合法的事件如下:
POLLIN                   有数据可读。
POLLRDNORM      有普通数据可读。
POLLRDBAND       有优先数据可读。
POLLPRI                有紧迫数据可读。
POLLOUT               写数据不会导致阻塞。
POLLWRNORM      写普通数据不会导致阻塞。
POLLWRBAND       写优先数据不会导致阻塞。

POLLMSG              SIGPOLL 消息可用。


此外,revents域中还可能返回下列事件:
POLLER                指定的文件描述符发生错误。
POLLHUP             指定的文件描述符挂起事件。
POLLNVAL            指定的文件描述符非法。
这些事件在events域中无意义,因为它们在合适的时候总是会从revents中返回。使用poll()和select()不一样,你不需要显式地请求异常情况报告。

POLLIN | POLLPRI等价于select()的读事件,POLLOUT |POLLWRBAND等价于select()的写事件。POLLIN等价于POLLRDNORM |POLLRDBAND,而POLLOUT则等价于POLLWRNORM。


例如,要同时监视一个文件描述符是否可读和可写,我们可以设置 events为POLLIN |POLLOUT。在poll返回时,我们可以检查revents中的标志,对应于文件描述符请求的events结构体。如果POLLIN事件被设置,则文件描述符可以被读取而不阻塞。如果POLLOUT被设置,则文件描述符可以写入而不导致阻塞。这些标志并不是互斥的:它们可能被同时设置,表示这个文件描述符的读取和写入操作都会正常返回而不阻塞。
timeout参数指定等待的毫秒数,无论I/O是否准备好,poll都会返回。timeout指定为负数值表示无限超时;timeout为0指示poll调用立即返回并列出准备好I/O的文件描述符,但并不等待其它的事件。这种情况下,poll()就像它的名字那样,一旦选举出来,立即返回。


返回值和错误代码
成功时,poll()返回结构体中revents域不为0的文件描述符个数;如果在超时前没有任何事件发生,poll()返回0;失败时,poll()返回-1,并设置errno为下列值之一:
EBADF           一个或多个结构体中指定的文件描述符无效。
EFAULT         fds指针指向的地址超出进程的地址空间。
EINTR           请求的事件之前产生一个信号,调用可以重新发起。
EINVAL         nfds参数超出PLIMIT_NOFILE值。
ENOMEM     可用内存不足,无法完成请求。



10、Multiplexed I/O 
Linux提供了三个multiplexed I/O:select, poll, 和 epoll 
select提供了同步multiplexing I/O: 
Cpp代码   
1. #include <sys/time.h>  
2. #include <sys/types.h>  
3. #include <unistd.h>  
4.  
5. int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);  
6. FD_CLR(int fd, fd_set *set);//从set中清除fd  
7. FD_ISSET(int fd, fd_set *set);//判断fd是否在set中  
8. FD_SET(int fd, fd_set *set);//将fd添加到set中  
9. FD_ZERO(fd_set *set);//fd_set很多系统实现为bit arrays,将所有的文件描述符从fd_set中清空  


调用select将会被阻塞直到所给的文件描述准备好I/O操作或则给定的timeout超时。 
n 为 文件描述符集合中的最大值+1 调用者需要自行计算 
readfds     被监控是否可以读 
writefds     被监控是否可以写 
exceptfds  被监控是否发生异常 
timeout     超时时间,Linux返回时会被修改成剩余的时间。 
timeval的定义: 
Cpp代码   
1. #include <sys/time.h>  
2.  
3. struct timeval {  
4.    long tv_sec; /*second */  
5.    long tv_usec; /* microsecods */  
6. };  


成功返回准备好I/O操作的文件描述符数,指定了timeout,则可能返回0,error返回-1 
例子: 
Cpp代码   
1. #include <stdio.h>  
2. #include <sys/time.h>  
3. #include <sys/types.h>  
4. #inlcude <unistd.h>  
5.  
6. #define TIMEOUT 5  
7. #define BUF_LEN 1024  
8.  
9. int main(void){  
10.    struct timeval tv;  
11.    fd_set readfds;  
12.    int ret;  
13.  
14.    FD_ZERO(&readfds);  
15.    FD_SET(STDIN_FILENO,&readfds);  
16.      
17.    tv_tv_sec = TIMEOUT;  
18.    tv.tv_usec = 0;  
19.  
20.    ret = select(STDIN_FILENO + 1,  
21.        &readfds,  
22.        NULL,  
23.        NULL,  
24.        &tv);  
25.    if(ret == -1){  
26.        perror("select");  
27.        return 1;  
28.    }else if(!ret){  
29.        printf("%d seconds elapse.\n", TIMEOUT);  
30.        return 0;     
31.    }  
32.      
33.    if(FD_ISSET(STDIN_FILENO, &readfds)){  
34.        char buf[BUF_LEN + 1];  
35.        int len;  
36.          
37.        len = read(STDIN_FILENO,buf,BUF_LEN);  
38.        if(len == -1){  
39.            perror("read");  
40.            return 1;  
41.        }  
42.        if(len){  
43.            buf[len] = '\0';  
44.            printf("read: %s\n", buf);  
45.            return 0;  
46.        }  
47.    }  
48.    fprintf(stderr,"This should not happen!\n");  
49.    return 1;         
50. }   


可以将select做为a portable way to sleep使用 
Cpp代码   
1. struct timeval tv;  
2.  
3. tv.tv_sec = 0;  
4. tv.tv_usec = 500;  
5.  
6. select(0, NULL, NULL, NULL, &tv);  


pselect: 
Cpp代码   
1. #include<sys/select.h>  
2.  
3. int pselect(int n, fd_set *readfds, fd_set *writefds,fd_set *exceptfds,  
4.        const struct timespec *timeout, const sigset_t *sigmask);  


pselect和select的不同点: 
1、使用timespec而不是timeval结构作为timeout参数,timespec使用秒和纳秒,理论上更高级,但是实际上 
即使是微妙也不是可靠地。 
2、pselect不会修改timeout参数,所以timeout不需要重新初始化。 
3、select没有sigmask参数。 
pselect解决一下竞争问题: 
signal可能会设置一些全局的flag,调用select之前要检查flag.这样,如果信号在检查flag和调用select之间到来, 
修改了flag,那么程序将会永远的阻塞。pselect解决了这个问题。 
poll: 
Cpp代码   
1. #include<sys/poll.h>  
2.  
3. int poll(struct pollfd *fds, unsigned int nfds, int timeout);  


fds pollfd的数组,用来监控是否可以读写,nfds数组的大小 
timeout 超时时间。 
pollfd: 
Cpp代码   
1. #include <sys/poll.h>  
2.  
3. struct pollfd{  
4.    int fd;/* file descriptor */  
5.    short events; /* requested event to watch,is a bitmask */  
6.    short revents; /* returned events witnessed,is a bitmask */  
7. };  


合法的events: 
POLLIN:                      有数据可读 
POLLRDNORM:       有normal data可以读 
POLLRDBAND:        有priority data可读 
POLLPRI:                 有urgent data可读 
POLLOUT:                可以无阻塞的写 
POLLWRNORM:       可以无阻塞的写normal data 
POLLWRBAND:        可以无阻塞的写priority data 
POLLMSG:                  一个SIGPOLL message存在 
除了以上revents: 
POLLER:                    文件描述符错误 
POLLHUP:              文件描述符发生hung up 事件 
POLLINVAL:              文件描述符不合法 
和select相比,不需要显示指定exception fds 
POLLIN|POLLPRI == select read event 
POLLOUT|POLLWRBAND == select write event 
POLLIN == POLLRDNORM | POLLRDBAND 
POLLOUT == POLLWRNORM 
timeout超时时间millisecond 
例子: 
Cpp代码   
1. #include <stdio.h>  
2. #include <unistd.h>  
3. #include <sys/poll.h>  
4.  
5. #define TIMEOUT 5  
6.  
7. int main(void){  
8.    struct pollfd fds[2];  
9.    int ret;  
10.      
11.    fds[0].fd = STDIN_FILENO;  
12.    fds[0].events = POLLIN;  
13.      
14.    fds[1].fd = STDOUT_FILENO;  
15.    fds[1].events = POLLOUT;  
16.  
17.    ret = poll(fds,2,TIMEOUT * 1000);  
18.      
19.    if(ret == -1){  
20.        perror("poll");  
21.        return 1;  
22.    }  
23.  
24.    if( ! ret ){  
25.        printf("%s second elapsed.\n",TIMEOUT);  
26.        return 0;  
27.    }  
28.  
29.    if(fds[0].revents & POLLIN)  
30.        printf("stdin is readable\n");  
31.    if(fds[1].revents & POLLOUT)  
32.        printf("stdout is writable\n");  
33.    return 0;  
34. }  


不需要每次重新设置pollfd结构,revents每次会被内核先清空。 
ppoll: 
ppoll之poll和pselect之select一样,但是ppoll是linux特有的接口。 
Cpp代码   
1. #include <sys/poll.h>  
2.  
3. int ppoll(struct pollfd *fds,nfds_t nfds,const struct timespec *timeout,  
4.        const sigset_t *sigmask);  


poll VS select: 
poll有点: 
1、poll不需要用户计算最大文件描述符+1作为第一个参数 
2、poll对于文件描述符比较大的,更高效,select使用bit map,需要比较每一个bit 
3、poll文件描述符集合大小是静态的,一个固定大小的pollfd数组。 
4、select文件描述符结合参数作为返回值被重新构造,所以每次需要重新初始化,poll使用了 
分开的input(events filed)和out(revents fields),使得pollfd数组可以被重用。 
5、select中的timeout在返回的时候没有被定义,所以可移植的代码需要重新初始化,pselect没有这个问题。 
select优点: 
1、select更具有可移植性,有些UNIX不支持poll 
2、select提供了更好的timeout机制:精确到微秒。 


epoll比poll和select更高级,是Linux专有的接口。 
0 0
原创粉丝点击