poll服務器

poll是I/O複用多路轉接的另一種方法,其優化了select兩個缺點:

  1. poll服務器支持的文件描述符數目沒有上限;
  2. 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,事實上,同時連接的大量客戶端在一時刻可能只有很少的處於就緒狀態,因此隨着監視的描述符數量的增長,其效率也會線性下降。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章