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 消息可用。
此外,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指定為負數值表示無限超時,使poll()一直掛起直到一個指定事件發生;timeout為0指示poll調用立即返回並列出準備好I/O的文件描述符,但並不等待其它的事件。這種情況下,poll()就像它的名字那樣,一旦選舉出來,立即返回。
返回值和錯誤代碼
成功時,poll()返回結構體中revents域不為0的文件描述符個數;如果在超時前沒有任何事件發生,poll()返回0;失敗時,poll()返回-1,並設置errno為下列值之一:
EBADF 一個或多個結構體中指定的文件描述符無效。
EFAULTfds 指針指向的地址超出進程的地址空間。
EINTR 請求的事件之前產生一個信號,調用可以重新發起。
EINVALnfds 參數超出PLIMIT_NOFILE值。
ENOMEM 可用內存不足,無法完成請求。
3 demo演示編寫一個echo server程序,功能是客戶端向伺服器發送信息,伺服器接收輸出並原樣發送回給客戶端,客戶端接收到輸出到終端。
伺服器端程序如下:
// FileName: server.c
#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 8787
#define MAXLINE 1024
#define LISTENQ 5
#define OPEN_MAX 1000
#define INFTIM -1
//函數聲明
//創建套接字並進行綁定
static int socket_bind(const char* ip,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,connfd,sockfd;
struct sockaddr_in cliaddr;
socklen_t cliaddrlen;
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:");
exit(1);
}
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET,ip,&servaddr.sin_addr);
servaddr.sin_port = htons(port);
if (bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) == -1)
{
perror("bind error: ");
exit(1);
}
return listenfd;
}
static void do_poll(int listenfd)
{
int connfd,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:");
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);
}
}
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];
memset(buf,0,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;
}
// printf("read msg is: ");
write(STDOUT_FILENO,buf,n);
//向客戶端發送buf
write(connfds[i].fd,buf,n);
}
}
}客戶端代碼如下所示:
// FileName: client.c
#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>
#define MAXLINE 1024
#define IPADDRESS "127.0.0.1"
#define SERV_PORT 8787
#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;
sockfd = socket(AF_INET,SOCK_STREAM,0);
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET,IPADDRESS,&servaddr.sin_addr);
connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
//處理連接描述符
handle_connection(sockfd);
return 0;
}
static void handle_connection(int sockfd)
{
char sendline[MAXLINE],recvline[MAXLINE];
int maxfdp,stdineof;
struct pollfd pfds[2];
int n;
//添加連接描述符
pfds[0].fd = sockfd;
pfds[0].events = POLLIN;
//添加標準輸入描述符
pfds[1].fd = STDIN_FILENO;
pfds[1].events = POLLIN;
for (; ;)
{
poll(pfds,2,-1);
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);
}
}
}4、程序測試結果
server端:
5、參考資料
http://blog.endlesscode.com/2010/03/27/select-poll-epoll-intro/
http://hi.baidu.com/xzf20082004/item/622fb01a1018c7f5746a846f