I/O複用(poll)
上文說到的實現IO複用的函式中的select,本文接著介紹第二種poll。
poll
Poll相對於select來說突破了監聽的檔案描述符上限1024,最大檔案描述符是系統所能允許的最大值,可以通過檢視proc/sys/fs/file-max檔案檢視,這個值也可以改(通過limits.conf)。另一方面是實現了監聽和就緒事件的分離。其他和select類似,也是在指定時間內輪詢一定數量的檔案描述符,檢測其中是否有就緒的。還是先從API說起。
poll API
函式原形:
# include <poll.h>
int poll ( struct pollfd * fds, unsigned int nfds, int timeout);
引數
poll函式的第一個引數fds是struct pollfd結構體型別的事件集,一般傳入一個該型別的陣列。
pollfd結構體定義如下:
struct pollfd {
int fd; /* 檔案描述符 */
short events; /* 等待的事件 */
short revents; /* 實際發生了的事件,由核心填充 */
} ;
該結構體有三個成員:
第一個fds是要監聽的檔案描述符,
第二個events是要監聽的事件(POLLIN、POLLOUT、POLLERR),
第三個revents是監控事件中滿足條件返回的事件,核心通過修改這個引數來反饋監聽的就緒事件。
事件主要有以下幾個:
POLLIN 有資料可讀。
POLLRDNORM 有普通資料可讀。
POLLRDBAND 有優先資料可讀。
POLLPRI 有緊迫資料可讀。
POLLOUT 寫資料不會導致阻塞。
POLLWRNORM 寫普通資料不會導致阻塞。
POLLWRBAND 寫優先資料不會
第二個引數nfds是監聽的檔案描述符的個數,
第三個引數timeout是設定超時時間,用法和select一樣。是一個struct timeval結構體指標,該結構體定義如下:
struct timeval{
long tv_sec; //second
long tv_usec; //minisecond
}
超時時間可以設定到毫秒級別,有三種設定情況:
NULL:阻塞等待,直到某個檔案描述符上發生了事件。
0:僅檢測描述符集合的狀態,然後立即返回。
> 0: 指定超時時間,如果在該時間段裡沒有事件發生,select將超時返回。
返回值
成功: poll()返回結構體中revents域不為0的檔案描述符個數;
失敗: poll()返回-1,並設定errno;
超時: poll()返回0;
不足
雖然epoll突破了最大監聽1024個檔案描述符的限制,但它還是存在缺點就是當大量連線上只有少量活躍的連線時,它也是採用輪詢的機制每次還要遍歷整個陣列,效率太低。在下文中,將介紹一種更為高效的實現IO複用的函式epoll。
程式示例
最後還是以一個poll實現的伺服器的示例程式結束本文:
#include<stdio.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<sys/wait.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<ctype.h>
#include<poll.h>
#define MYPORT 8888
#define BACKLOG 10
#define MAXDATASIZE 1024
#define FILEMAX 3000
int main()
{
int i,j,maxi;
int listenfd,connfd,sockfd; //定義套接字描述符
int nready; //接受pool返回值
int numbytes; //接受recv返回值
char buf[MAXDATASIZE]; //傳送緩衝區
struct pollfd client[FILEMAX]; //struct pollfd* fds
//定義IPV4套介面地址結構
struct sockaddr_in seraddr; //service 地址
struct sockaddr_in cliaddr; //client 地址
int sin_size;
//初始化IPV4套介面地址結構
seraddr.sin_family =AF_INET; //指定該地址家族
seraddr.sin_port =htons(MYPORT); //埠
seraddr.sin_addr.s_addr = INADDR_ANY; //IPV4的地址
bzero(&(seraddr.sin_zero),8);
//socket()函式
if((listenfd = socket(AF_INET,SOCK_STREAM,0))==-1)
{
perror("socket");
exit(1);
}
//地址重複利用
int on = 1;
if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)) < 0)
{
perror("setsockopt");
exit(1);
}
//bind()函式
if(bind(listenfd,(struct sockaddr *)&seraddr,sizeof(struct sockaddr))==-1)
{
perror("bind");
exit(1);
}
//listen()函式
if(listen(listenfd,BACKLOG)==-1)
{
perror("listen");
exit(1);
}
client[0].fd = listenfd; //將listenfd加入監聽序列
client[0].events = POLLIN; //監聽讀事件
//初始化client[]中剩下的元素
for(i = 1;i < FILEMAX;i++)
{
client[i].fd = -1; //不能用0,其也是檔案描述符
}
maxi = 0; //client[]中最大元素下標
while(1)
{
nready = poll(client,maxi+1,-1);//阻塞監聽
if(nready < 0)
{
perror("poll error!\n");
exit(1);
}
if(client[0].revents & POLLIN)//位與操作;listenfd的讀事件就緒
{
sin_size = sizeof(cliaddr);
if((connfd=accept(listenfd,(struct sockaddr *)&cliaddr,&sin_size))==-1)
{
perror("accept");
exit(1);
}
printf("client IP: %s\t PORT : %d\n",inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port));
//將sockfd加入監聽序列
for(i = 1;i < FILEMAX;i++)
{
if(client[i].fd < 0)
{
client[i].fd = connfd;
break;
}
}
if(i == FILEMAX)
{
perror("too many clients!\n");
exit(1);
}
client[i].events = POLLIN;//監聽connfd的讀事件
if(i > maxi)
{
maxi = i;
}
//判斷是否已經處理完事件
if(--nready == 0)
{
continue;
}
}
//檢測客戶端是否發來訊息
for(i = 1;i <= maxi;i++)
{
if((sockfd = client[i].fd) < 0)
{
continue;
}
if(client[i].revents & POLLIN)
{
memset(buf,0,sizeof(buf));
numbytes = recv(sockfd,buf,MAXDATASIZE,0);
if(numbytes < 0)
{
if(errno == ECONNRESET) //RET標誌
{
printf("client[%d] aborted connection!\n",i);
close(sockfd);
client[i].fd = -1;
}
else
{
perror("recv error!\n");
exit(1);
}
}
else if(numbytes == 0)
{
printf("client[%d],close!\n",i);
close(sockfd);
client[i].fd = -1;
}
else
{
send(sockfd,buf,numbytes,0);
}
if(--nready == 0)
{
break;
}
}
}
}
return 0;
}