1. 程式人生 > >I/O複用(poll)

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;
}