1. 基本知识
poll的机制与select类似,与select在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是poll没有最大文件描述符数量的限制。poll与select同样存在一个缺点就是,包含大量的文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。
2. poll函数
函数格式如下所示:
#include <poll.h>
int poll(struct pollfd *fds, unsigned int nfds, int timeout);
pollfd结构体定义如下:
struct pollfd
{
int fd; /*文件描述符*/
short events; /*等待的事件*/
short revents; /*实际发生了的事件*/
};
每一个pollfd结构体指定了一个被监视的文件描述符,可以传递多个结构体,只是poll()监视多个文件描述符。每个结构体的events域是监视该文件描述符的事件掩码,由用户来设置这个域。revents域是文件描述符的操作结构事件掩码,内核在调用返回时设置这个域。events域中请求的任何事件都可能在revents域中返回。合法的事件如下:
POLLIN | 有数据可读 |
POLLRDNORM | 有普通数据可读 |
POLLRDBAND | 有优先数据可读 |
POLLPRI | 有紧迫数据可读 |
POLLOUT | 写数据不会导致阻塞 |
POLLWRNORM | 写普通数据不会导致阻塞 |
POLLWRBAND | 写优先数据不会导致阻塞 |
POLLMSGSIGPOLL | 消息可用 |
POLLER | 指定的文件描述符发送错误 |
POLLHUP | 指定的文件描述符挂起事件 |
POLLNVAL | 指定的文件描述符非法 |
使用poll()和select()不一样,不需要显式地请求异常情况报告
POLLIN | POLLPRI 等价于select的读事件
POLLOUT | POLLWRBAND 等价于select的写事件
POLLIN 等价于POLLRDNORM | POLLRDBAND
POLLOUT 等价于 POLLWRNORM。
例如,要同时监视一个文件描述符是否可读和可写,可以设置events为POLLIN|POLLOUT。在poll返回时,可以坚持revents中的标识,对应于文件描述符请求的events结构体。如果POLLIN事件被设置,则文件描述符可以背读而不阻塞。如果POLLOUT被设置,则文件描述符可以写入而不阻塞。这些标志并不是互斥的:它们肯呢个被同时设置,表示这个文件描述符的读取和写入操作都会正常返回而不阻塞。
timeout参数指定等待的毫秒数,无论IO是否准备好,poll都会返回。
timeout指定为负数值表示无限超时,使poll一直挂起直到一个指定事件发生;
timeout为0指示poll调用立即返回并列出准备好IO的文件描述符,但并不等待其他事件。在这种情况下,poll就像它的名字那样,一旦选举出来,立即返回。
返回值和错误代码
成功时,poll返回结构体中revents域不为0的文件描述符个数;如果在超时前没有任何事件发生,poll返回0,;失败时,poll返回-1,并设置errno为下列值之一:
EBADF: 一个或多个结构体中指定的文件描述符无效
EFAULTfds: 指针指向的地址超出进程的地址空间
EINTR:请求的事件之前产生一个信号,调用可以重新发起
EINVALnfds:参数超出PLIMIT_NOFILE值
ENOMEM:可用内存不足,无法完成请求
3 实例
编写一个echo server程序,功能是客户端向服务器发送信息,服务器接收输出并原样返回给客户端,客户端接收到输出到终端。
服务器代码如下
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <poll.h>
#include <unistd.h>
#include <sys/types.h>
#define IPADDRESS "127.0.0.1"
#define PORT 11343
#define MAXLINE 1024
#define LISTENQ 5
#define OPEN_MAX 1000
#define INFTIM -1
/*声明创建套接字并进行绑定函数*/
static int socket_bind(const char* p, int port);
/*声明io多路复用poll函数*/
static void do_poll(int listenfd);
/*处理多个连接*/
static void handle_connection(struct pollfd* connfds, int num);
int main(int argc, char** argv)
{
int listenfd;
int connfd;
int sockfd;
struct sockaddr_in cliaddr;
listenfd = socket_bind(IPADDRESS, PORT);
listen(listenfd, LISTENQ);
do_poll(listenfd);
return 0;
}
static int socket_bind(const char *ip, int port)
{
int listenfd;
struct sockaddr_in servaddr;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (listenfd == -1){
perror("socket error\n");
exit(1);
}
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, ip, &servaddr.sin_port);
servaddr.sin_port = htons(11343);
if(bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){
perror("bind error\n");
exit(1);
}
return listenfd;
}
static void do_poll(int listenfd)
{
int connfd;
int sockfd;
struct sockaddr_in cliaddr;
socklen_t cliaddrlen;
struct pollfd clientfds[OPEN_MAX];
int maxi;
int i;
int nready;
/*添加监听描述符*/
clientfds[0].fd = listenfd;
clientfds[0].events = POLLIN;
/*初始化客户连接描述符*/
for(i = 1; i < OPEN_MAX; i++){
clientfds[i].fd = -1;
}
maxi = 0;
/*循环处理*/
for(;;){
/*获取可用描述符的个数*/
nready = poll(clientfds, maxi+1, INFTIM);
if(nready == -1){
perror("poll error\n");
exit(1);
}
/*测试监听描述符是否准备好*/
if(clientfds[0].revents & POLLIN){
cliaddrlen = sizeof(cliaddr);
/*接受新的连接*/
if((connfd = accept(listenfd, (struct sockaddr*)&cliaddr, &cliaddrlen)) == -1){
if(errno == EINTR)
continue;
else{
perror("accept error");
exit(1);
}
}
printf("i = %d, OPEN_MAX =%d\n",i, OPEN_MAX);
fprintf(stdout, "accept a new client:%s:%d\n", inet_ntoa(cliaddr.sin_addr), cliaddr.sin_port);
/*将新的描述符添加到数组中*/
for(i = 1; i < OPEN_MAX; i++){
if(clientfds[i].fd < 0){
clientfds[i].fd = connfd;
break;
}
}
if(i == OPEN_MAX){
fprintf(stderr, "too many clients.\n");
exit(1);
}
/*将新的描述符添加到读描述符集合中*/
clientfds[i].events = POLLIN;
/*记录客户端连接套接字的个数*/
maxi = (i>maxi ? i : maxi);
if(--nready <= 0)
continue;
}
/*处理客户连接*/
handle_connection(clientfds, maxi);
}
}
static void handle_connection(struct pollfd* connfds, int num)
{
int i, n;
char buf[MAXLINE];
bzero(buf, MAXLINE);
for(i = 1; i <= num; i++){
if(connfds[i].fd < 0)
continue;
/*测试客户端描述符是否准备好*/
if(connfds[i].revents * POLLIN){
/*接收客户端发送的消息*/
n = read(connfds[i].fd, buf, MAXLINE);
if(n == 0){
close(connfds[i].fd);
connfds[i].fd = -1;
continue;
}
write(STDOUT_FILENO, buf, n);
write(connfds[i].fd, buf, n);
}
}
}
客户端代码如下:
#include <netinet/in.h>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <poll.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#define MAXLINE 1024
#define IPADDRESS "127.0.0.1"
#define SERV_PORT 11343
#define max(a,b) (a>b)?a:b
static void handle_connection(int sockfd);
int main(int argc, char** argv)
{
int sockfd;
struct sockaddr_in servaddr;
int retval;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd <= 0){
printf("socket error\n");
return -1;
}
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, IPADDRESS, &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);
retval = connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
if(retval < 0){
printf("connect error:%s\n", strerror(errno));
return -1;
}
printf("sockdfd = %d\n",sockfd);
handle_connection(sockfd);
return 0;
}
static void handle_connection(int sockfd)
{
char sendline[MAXLINE];
char recvline[MAXLINE];
struct pollfd pfds[2];
int maxfdp;
int stdineof;
int n;
int retval;
/*添加连接描述符*/
pfds[0].fd = sockfd;
pfds[0].events = POLLIN;
/*添加标准输入描述符*/
pfds[1].fd = STDIN_FILENO;
pfds[1].events = POLLIN;
for(;;){
retval = poll(pfds, 2, -1);
if(retval <= 0){
printf("poll fail.\n");
continue;
}
/*测试连接描述符是否准备好*/
if(pfds[0].revents & POLLIN){
n = read(sockfd, recvline, MAXLINE);
if(n == 0){
fprintf(stderr, "client: server is closed.\n");
close(sockfd);
}
write(STDOUT_FILENO, recvline, n);
}
/*测试标准输入描述符是否准备好*/
if(pfds[1].revents & POLLIN){
n = read(STDIN_FILENO, sendline, MAXLINE);
if(n == 0){
shutdown(sockfd, SHUT_WR);
continue;
}
write(sockfd, sendline, n);
}
}
}