poll是I/O複用多路轉接的另一種方法,其優化了select兩個缺點:
- poll服務器支持的文件描述符數目沒有上限;
- poll服務器函數接口的參數與select不同,其將輸入與輸出參數進行了分離(用結構體實現).
函數如下:
#include <poll.h>
int poll(struct pollfd* fds,nfds_t nfds,int timeout);
參數含義:
- fds:結構體數組,(可自定義數組的大小,以連接客戶端的數目,所以以上可說其支持的文件描述符無上限,因爲個數大小由用戶可自定義大小),每一個結構體內容如下,並且可以看到其將輸入輸出型參數分離:
struct pollfd
{
int fd; //關心的文件描述符
short events; //其作輸入型參數指定fd關心的事件
short revents;//其作輸出型參數指定fd發生的哪一個事件就緒
};
- nfds:結構體數組的大小;
- timeout:與select含義相同,不過此處timeout不爲結構體,只爲一個整數值可直接設定,代表ms,含義如下:
NULL:poll則一直阻塞等待事件就緒;
0:則僅檢測fd狀態後不等待立即返回;
特定值:在指定值時間內沒有相應事件就緒則超時返回。
poll服務器代碼實現如下:
poll_server.c
#include <stdio.h>
#include <poll.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#define SIZE 64
static void usage(const char* porc)
{
printf("%s [local_ip] [local_porc]\n",porc);
}
int startup(char* ip,int port)
{
int sock=socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
{
perror("sock");
close(sock);
exit(2);
}
int opt=1;
setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
struct sockaddr_in local;
local.sin_family=AF_INET;
local.sin_port=htons(port);
local.sin_addr.s_addr=inet_addr(ip);
if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
{
perror("bind");
close(sock);
exit(3);
}
if(listen(sock,10)<0)
{
perror("sock");
close(sock);
exit(4);
}
return sock;
}
int main(int argc,char* argv[])
{
if(argc != 3)
{
usage(argv[0]);
return 1;
}
int listen_sock=startup(argv[1],atoi(argv[2])); //創建監聽套接字
struct pollfd fds[SIZE]; //poll數組以此來保存每一個客戶端信息
int i=0;
for( ;i<SIZE;++i) //初始化
{
fds[i].fd=-1;
fds[i].events=0;
fds[i].revents=0;
}
fds[0].fd=listen_sock;
fds[0].events=POLLIN;
fds[0].revents=0;
int timeout=500;
while(1)
{
switch(poll(fds,SIZE,timeout))
{
case -1:
perror("poll");
break;
case 0:
printf("timeout...\n");
break;
default: //成功
{
struct sockaddr_in client;
socklen_t len=sizeof(client);
char buf[1024];
int i=0;
for( ;i<SIZE;++i)
{
if(i==0 && fds[i].revents==POLLIN) //爲0則爲監聽套接字建立連接
{
int new_sock=accept(listen_sock,(struct sockaddr*)& client,&len);
if(new_sock<0)
{
perror("accept");
close(new_sock);
continue;
}
printf("get a new client:[%s:%d]\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
int j=1;
for( ;j<SIZE;++j)
{
if(fds[j].fd==-1)
break;
}
if(j==SIZE)
{
close(new_sock);
continue;
}
fds[j].fd=new_sock;
fds[j].events=POLLIN;
}
else if(i!=0 && fds[i].revents==POLLIN) //normal fd ready,不爲0則直接讀取
{
ssize_t s=read(fds[i].fd,buf,sizeof(buf)-1);
if(s>0)
{
buf[s]=0;
printf("client# %s\n",buf);
fds[i].events=POLLOUT; //讀取成功則將此關心事件改爲寫
}
else if(s==0)
{
printf("client quit!!!\n");
close(fds[i].fd);
fds[i].fd=-1;
continue;
}
else
{
perror("read");
continue;
}
}
else if(i!=0 && fds[i].revents==POLLOUT)
{
ssize_t s=write(fds[i].fd,buf,strlen(buf));
if(s<0)
{
perror("write");
continue;
}
fds[i].events=POLLIN; //寫成功則將此關心事件改爲讀
}
}
}
break;
}
}
return 0;
}
從上可知,select和poll都需要在返回後,通過遍歷文件描述符來獲取已經就緒的socket,事實上,同時連接的大量客戶端在一時刻可能只有很少的處於就緒狀態,因此隨着監視的描述符數量的增長,其效率也會線性下降。