對於poll的理論講解,可參考文章:http://www.cnblogs.com/Anker/p/3261006.html
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 消息可用。
此外,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 可用內存不足,無法完成請求。
實戰:寫一個echo程序,服務器原樣返回客戶端發送過來的內容。用poll寫。
client:
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <stdlib.h>
#include <poll.h>
#define err_exit(m)\
{\
perror(m);\
exit(EXIT_FAILURE);\
}
#define SERV_PORT 9877
#define BUFSIZE 4096
int max(int a, int b)
{
if (a > b)
return a;
else
return b;
}
//客戶端具體操作函數
void str_cli(FILE *fp, int sockfd);
int main(int argc, char **argv)
{
if (argc != 2)
{
printf("argument error\n");
exit(0);
}
int sockfd;
struct sockaddr_in servaddr;
int status;
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET, argv[1], &servaddr.sin_addr);//將點分十進制IP地址轉化爲網絡字節序的二進制地址
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1)
err_exit("socket");
status = connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));//連接服務器
if (status == -1)
err_exit("connect");
str_cli(stdin, sockfd);
exit(0);
}
void str_cli(FILE *fp, int sockfd)
{
int n;
char buf[BUFSIZE];
struct pollfd fds[2];
fds[0].fd = sockfd;
fds[0].events = POLLIN;
fds[1].fd = fileno(fp);
fds[1].events = POLLIN;
for (;;)
{
poll(fds, 2, -1);//等待套接字或標準輸入就緒
if (fds[0].revents & POLLIN)//套接字可讀,說明服務器發送數據過來
{
if ((n = read(sockfd, buf, BUFSIZE)) == 0)
{
printf("server terminated prematurely");
exit(1);
}
write(fileno(stdout), buf, n);
}
if (fds[1].revents & POLLIN)//標準輸入可讀,說明客戶端有數據要寫往服務器
{
if ((n = read(fileno(fp), buf, BUFSIZE)) == 0)
{
shutdown(sockfd, SHUT_WR);
continue;
}
write(sockfd, buf, n);
}
}
}
server:
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
#include <poll.h>
//出錯函數
#define err_exit(m)\
{\
perror(m);\
exit(EXIT_FAILURE);\
}
//最大連接數
#define LISTENQ 1024
//服務器端口號
#define SERV_PORT 9877
//接收和發送的緩衝區大小
#define BUFSIZE 4096
#define OPEN_MAX 1000
//處理客戶端請求函數
void str_echo(int confd);
int main(int argc, char **argv)
{
int confd, listenfd;
struct sockaddr_in cliaddr, servaddr;
socklen_t clilen;
int status;
char buff[BUFSIZE];
struct pollfd fds[OPEN_MAX];
int nready;
int i;
ssize_t n;
//設置協議地址結構內容
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
listenfd = socket(AF_INET, SOCK_STREAM, 0);//創建套接字
if (listenfd == -1)
err_exit("socket");
status = bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));//將協議、IP地址、端口綁定到套接字
if (status == -1)
err_exit("bind");
status = listen(listenfd, LISTENQ);//使套接字變爲監聽套接字
if (status == -1)
err_exit("listen");
fds[0].fd = listenfd;
fds[0].events = POLLIN;
for (i = 1; i < OPEN_MAX; i++)
fds[i].fd = -1;
int max = 0;
while (1)
{
nready = poll(fds, max + 1, -1);//等待描述符集中有描述符就緒
if (fds[0].revents & POLLIN)//監聽套接字可讀
{
clilen = sizeof(cliaddr);
confd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);//等待連接完成
if (confd == -1)
{
if (errno = EINTR)
continue ;
else
err_exit("accept");
}
printf("connection from %s, port %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, buff, sizeof(buff)),
ntohs(cliaddr.sin_port));
for (i = 1; i < OPEN_MAX; i++)
{
if (fds[i].fd < 0)
{
fds[i].fd = confd;//將已連接套接字加入描述符集
break;
}
}
if (i == OPEN_MAX)
{
printf("too many clients\n");
exit(1);
}
fds[i].events = POLLIN;
if (i > max)
max = i;
if (--nready <= 0)
continue;//只有監聽套接字就緒
}
for (i = 1; i <= max; i++) //有已連接套接字可讀,輪詢處理
{
if (fds[i].fd < 0)//對描述如進行測試是否就緒
continue;
n = read(fds[i].fd, buff, BUFSIZE);
if (n == 0)
{
close(fds[i].fd);
fds[i].fd = -1;
continue;
}
write(fds[i].fd, buff, n);
}
}
}
測試結果: