天天看點

Linux【網絡程式設計】——I/O多路轉接之Poll伺服器

Poll:

poll或select為大部分Unix/Linux程式員所熟悉,這倆個東西原理類似。性能上也不存在明顯差異,但select對所監控的檔案描述符數量有限制。

poll是一個系統調用,其核心入口函數為sys_poll,sys_poll幾乎不做任何處理直接調用do_sys_poll,do_sys_poll的執行過程可以分為三個部分:

1,将使用者傳入的pollfd數組拷貝到核心空間,因為拷貝操作和數組長度相關,時間上這是一個O(n)操作,這一步的代碼在do_sys_poll中包括從函數開始到調用do_poll前的部分。

2,查詢每個檔案描述符對應裝置的狀态,如果該裝置尚未就緒,則在該裝置的等待隊列中加入一項并繼續查詢下一裝置的狀态。查詢完所有裝置後如果沒有一個裝置就緒,這時則需要挂起目前程序等待,直到裝置就緒或者逾時,挂起操作是通過調用schedule_timeout執行的。裝置就緒後程序被通知繼續運作,這時再次周遊所有裝置,以查找就緒裝置。這一步因為兩次周遊所有裝置,時間複雜度也是O(n),這裡面不包括等待時間。相關代碼在do_poll函數中。

3,将獲得的資料傳送到使用者空間并執行釋放記憶體和剝離等待隊列等善後工作,向使用者空間拷貝資料與剝離等待隊列等操作的的時間複雜度同樣是O(n),具體代碼包括do_sys_poll函數中調用do_poll後到結束的部分。

poll()系統調用是System V的多元I/O解決方案。它解決了select()的幾個不足,盡管select()仍然經常使用。

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
           

和select()不一樣,poll()沒有使用低效的三個基于位的檔案描述符set,而是采用了一個單獨的結構體pollfd數組,由fds指針指向這個組。pollfd結構體定義如下:

struct pollfd {
               int   fd;         /* file descriptor */
               short events;     /* requested events */
               short revents;    /* returned events */
           };
           

每一個pollfd結構體指定了一個被監視的檔案描述符,可以傳遞多個結構體,訓示poll()監視多個檔案描述符。每個結構體的events域是監視該檔案描述符的事件掩碼,由使用者來設定這個域。revents域是檔案描述符的操作結果事件掩碼。核心在調用傳回時設定這個域。events域中請求的任何事件都可能在revents域中傳回。合法的事件如下:

POLLIN(有資料可讀)。

POLLRDNORM(有普通資料可讀)。

POLLRDBAND(有優先資料可讀)。

POLLPRI(有緊迫資料可讀)。

POLLOUT(寫資料不會導緻阻塞)。

POLLWRNORM(寫普通資料不會導緻阻塞)。

POLLWRBAND(寫優先資料不會導緻阻塞)。

POLLMSG(SIGPOLL消息可用)。

此外,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指定為負數值表示無限逾時;timeout為0訓示poll調用立即傳回并列出準備好I/O的檔案描述符,但并不等待其它的事件。這種情況下,poll()就像它的名字那樣,一旦選舉出來,立即傳回。

傳回值和錯誤代碼:

成功時,poll()傳回結構體中revents域不為0的檔案描述符個數;如果在逾時前沒有任何事件發生,poll()傳回0;失敗時,poll()傳回-1,并設定errno為下列值之一:

EBADF

一個或多個結構體中指定的檔案描述符無效。

EFAULT

fds指針指向的位址超出程序的位址空間。

EINTR

請求的事件之前産生一個信号,調用可以重新發起。

EINVAL

nfds參數超出PLIMIT_NOFILE值。

ENOMEM

可用記憶體不足,無法完成請求。

poll伺服器端:

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/select.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>
#include<poll.h>
#include<unistd.h>

#define SIZE 1024

static void usage(const char* proc)
{
        printf("usage:%s [local_ip] [local_port\n]",proc);
}

int startup(char* ip,int port)
{
    int sock = socket(AF_INET,SOCK_STREAM,);
    if(sock<){
        perror("socket");
        exit();
    }
    struct sockaddr_in client;
    client.sin_family = AF_INET;
    client.sin_port = htons(port);
    client.sin_addr.s_addr = inet_addr(ip);
    if(bind(sock,(struct sockaddr*)&client,sizeof(client))<){
        perror("bind");
        exit();
    }
    if(listen(sock,)<){
        perror("bind");
        exit();
    }
    return sock;
}

int main(int argc,char* argv[])
{
    if(argc!=){
        usage(argv[]);
        return ;
    }

    int listen_sock = startup(argv[],atoi(argv[]));
    struct pollfd pd[SIZE];
    int i=;
    for(;i<SIZE;++i){
        pd[i].fd=-;
    }
    pd[].fd = listen_sock;
    pd[].events = POLLIN;
    pd[].revents = ;
    int timeout = ;
    while(){
        switch(poll(pd,SIZE,timeout)){
            case -:
                perror("poll");
                break;
            case :
                 printf("timeout..\n");
                 break;
            default:
            {                           
                int i=;
                for(;i<SIZE;++i){
                        if(i== && pd[].revents & POLLIN){
                            struct sockaddr_in client;
                            socklen_t len = sizeof(client);
                            int new_sock = accept(listen_sock,(struct sockaddr*)&client,&len);
                            if(new_sock<){
                                    perror("accept");
                                    exit();
                            }
                            printf("get a client:[%s:%d]\n",inet_ntoa(client.sin_addr),\
                            ntohs(client.sin_port));
                            int j=;
                            for(j=;j<SIZE;++j){
                                     if(pd[j].fd<){
                                         break;
                                        }//if
                                        }//for
                             if(j==SIZE){
                                    printf("pd is full\n");
                                    break;
                                }else{
                                        pd[j].fd = new_sock;
                                        pd[j].events = POLLIN;
                                }//if
                                }else if(i!=){
                                     if(pd[i].revents & POLLOUT){
                                         char buf[];
                                         printf("Please Enter:");
                                         fflush(stdout);
                                         read(,buf,sizeof(buf)-);
                                         write(pd[i].fd,buf,strlen(buf)-);
                                        pd[i].events = POLLIN;
                                    }else if(pd[i].revents & POLLIN){
                                         char msg[];
                                         ssize_t s = read(pd[i].fd,msg,sizeof(msg)-);
                                        if(s<){
                                         perror("read");
                                         exit();
                                         }else if(s==){
                                        printf("clinet is quit\n");
                                        close(pd[i].fd);
                                        pd[i].fd = -;
                                         }else{
                                         msg[s] = ;
                                         printf("client say:%s",msg);
                                         pd[i].events = POLLOUT;
                                         }//read
                                        }else{
                                         }//
                                }//else if
                }//for
            }//default
        }//switch
    }//while
    return ;
}
           

用戶端:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>
#include<sys/stat.h>
#include<fcntl.h>


static void usage(const char* proc)
{
        printf("usage:%s [local_ip] [local_proc]\n",proc);
}

int main(int argc,char* argv[])
{
        if(argc!=){
            usage(argv[]);
            exit();
        }
         int sock = socket(AF_INET,SOCK_STREAM,);   
         struct sockaddr_in server;
         server.sin_family = AF_INET;
        server.sin_port = htons(atoi(argv[]));
        server.sin_addr.s_addr = inet_addr(argv[]);
        if(connect(sock,(struct sockaddr*)&server,sizeof(server))<){
                    perror("connect");
                    exit();
         }
        char buf[];
        while(){
            printf("please enter#");
            fflush(stdout);
            int sfd = dup();                   
            close();
            dup2(sock,);
            ssize_t s = read(,buf,sizeof(buf)-);  
             printf("%s",buf);
             dup2(sfd,STDOUT_FILENO);
            s = read(sock,buf,sizeof(buf)-);
            buf[s] = ;
            printf("server# %s",buf);
        }
            return ;
}
           

先啟動伺服器:./poll 127.0.0.1 8080

timeout..

timeout..

再運作用戶端:./client 127.0.0.1 8080

please enter#

在伺服器端get a client:[127.0.0.1:59946]

此時伺服器端與用戶端就可以進行任意通信

繼續閱讀