天天看點

【Linux】I/O多路轉接poll

不同與select使用三個位圖來表示三個fdset的方式,poll使用⼀一個 pollfd的指針實作。

poll函數

【Linux】I/O多路轉接poll

poll函數和select函數的任務相似:等待一組檔案描述符來準備執行I/0。

參數

(1)第一個參數 fds:

struct pollfd{
  int fd;          //檔案描述符,如果fd小于0, 則events字段被忽略,而revents被置為0.
  short events;    //輸入參數,請求的事
  short revents;   //輸出參數,傳回的事件,已經發生的事件
  };
           

(2)第二個參數 nfds:

nfds用來表示要監視檔案描述符的數目

(3)第三個參數timeout:

timeout是一個用毫秒表示的時間,是指定poll在傳回前沒有接收事件時應該等待的時間。如果 它的值為-1,poll就永遠都不會逾時。如果整數值為32個比特,那麼最大的逾時周期大約是30分鐘。

poll與select相比

  1. poll與select不同,通過一個pollfd數組向核心傳遞需要關注的事件,故沒有描述符個數的限制
  2. pollfd中的events字段和revents分别用于标示關注的事件和發生的事件,故pollfd數組隻需要被初始化一次
  3. poll的實作機制與select類似,其對應核心中的sys_poll,隻不過poll向核心傳遞pollfd數組,然後對pollfd中的每個描述符進行poll,相比處理fdset來說,poll效率更高
  4. poll傳回後,需要對pollfd中的每個元素檢查其revents值,來得指事件是否發生

poll的優點

  1. poll() 不要求開發者計算最大檔案描述符加一的大小
  2. poll() 在應付大數目的檔案描述符的時候相比于select速度更快
  3. 它沒有最大連接配接數的限制,原因是它是基于連結清單來存儲的。

poll的缺點

  1. 大量的fd的數組被整體複制于使用者态和核心位址空間之間,而不管這樣的複制是不是有意義
  2. 與select一樣,poll傳回後,需要輪詢pollfd來擷取就緒的描述符

使用poll編寫一個tcp伺服器

伺服器監聽sock,若有用戶端連接配接伺服器端,列印用戶端的ip和port,

如果伺服器端監聽的是讀事件,則直接讀取内容

如果伺服器端監聽的是寫事件,則回寫一條html資訊。

poll伺服器的思路

1、有一個struct pollfd的數組array_pollfd來存放監聽的sock;

2、擷取一個監聽的listen_scok;(get_listen函數擷取)

3、編寫poll_server函數,監聽檔案描述符的讀寫

  • 初始化array_pollfd數組,fd 置為負數-1
  • 将擷取到的listen_sock注冊,監聽讀事件,即:放置到數組array_pollfd 0号位置,events = POLLIN
  • 調用poll函數,循環監聽數組中的sock的讀寫事件,如果傳回值大于0 表示有檔案描述符已就緒:

    (1)如果0号位置的listen_sock的讀事件就緒,伺服器接收accpet,并将accpet新擷取到的new_sock放置到數組中,監聽其寫事件。

    (2)其他位置監聽到讀事件:read

    (3)其他位置監聽到寫事件:write html資訊

完整代碼:

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

#define POLLFD_SIZE 1024
//一個 對檔案描述符事件
struct pollfd array_pollfd[POLLFD_SIZE];

/* 結構體成員詳情 
struct pollfd  
{ 
    int fd;        // 關心的描述符 
    short events;  // 關心的事件 
    short revents; // 發生的事件 
}; 
*/

/*擷取一個監聽的socket*/
int get_listen(char *ip, short port)
{
    int sock = socket(AF_INET,SOCK_STREAM,);
    if(sock <)
    {
        perror("socket");
        exit();
    }
    int opt =;
    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);

    //bind
    if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < )
    {
        perror("bind");
        exit();
    }
    //listen
    if(listen(sock,)<)
    {
        perror("listen");
        exit();
    }

    return sock;
}

/*運作poll_server伺服器*/

void poll_server(int listen_sock)
{
    /*将負責的監聽的sock注冊*/
    array_pollfd[].fd = listen_sock;
    array_pollfd[].events = POLLIN;

    int idx = ;
    for(;idx < POLLFD_SIZE; ++idx)
        array_pollfd[idx].fd= -;

    int timeout = ;/*1000毫秒*/


    while()
    {
        int res = poll(array_pollfd,POLLFD_SIZE,timeout);
        if(res == )
            printf("timeout\n");
        else if(res < )
            perror("poll");
        else 
        {
            //有關心的事件已就緒
            int index = ;
            for(;index < POLLFD_SIZE;++index)
            {
                if(index ==  && array_pollfd[].revents & POLLIN)
                {

                    //listen_sock 讀事件就緒,響應accpet

                    struct sockaddr_in cliaddr;
                    socklen_t len = sizeof(cliaddr);

                    int new_sock = accept(listen_sock,(struct sockaddr*)&cliaddr,&len);
                    if(new_sock < )
                    {
                        perror("accept");
                        continue;
                    }
                    else 
                    {
                        printf("get a client:%s, %d\n",\
                        inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port));

                        //将新的sock添加到數組中
                        int k = ;
                        for(;k < POLLFD_SIZE;++k)
                        {
                            if(array_pollfd[k].fd < )
                            {
                                array_pollfd[k].fd = new_sock;
                                //将新的sock關注讀事件
                                array_pollfd[k].events = POLLIN;
                                break;
                            }
                        }
                        //表示沒有可用的檔案接口
                        if(k == POLLFD_SIZE)
                        {
                            close(new_sock);
                            return ;
                        }
                    }

                }
                else if(index !=  && array_pollfd[index].revents & POLLIN)
                {
                    //其他檔案描述符讀事件就緒
                    char buf[];
                    memset(buf,,);
                    ssize_t s = read(array_pollfd[index].fd,buf,sizeof(buf)-);
                    if(s > )
                    {
                        buf[s] = ;
                        printf("client say#:%s\n",buf);
                        array_pollfd[index].events = POLLOUT;
                    }
                    else if(s <= )
                    {
                        printf("client quit\n");                        
                        close(array_pollfd[index].fd);
                        //必須修改檔案描述符為初始狀态,
                        array_pollfd[index].fd = -;
                    }
                }
                else if(index !=  && array_pollfd[index].revents & POLLOUT)
                {
                    //其他檔案描述符寫事件就緒

                    const char* msg = "HTTP/1.1 200 OK\r\n\r\n<html><br/><h1>Hello poll!</h1></html>";  
                    write(array_pollfd[index].fd,msg,strlen(msg));
                    close(array_pollfd[index].fd);
                    array_pollfd[index].fd = -;
                }
            }
        }

    }

}
int main(int argc, char* argv[])
{
    if(argc != )
    {
        printf("Usge:%s [ip] [port]\n",argv[]);
        return ;
    }
    int listen_sock = get_listen(argv[],atoi(argv[]));

    poll_server(listen_sock);

    return ;
}
           

啟動伺服器

【Linux】I/O多路轉接poll

使用用戶端浏覽器連接配接

【Linux】I/O多路轉接poll

浏覽器回顯

【Linux】I/O多路轉接poll

繼續閱讀