Linux NIO 系列(04-2) poll

Linux NIO 系列(04-2) polllinux

Netty 系列目錄(http://www.javashuo.com/article/p-hskusway-em.html)編程

1、select 和 poll 比較

select() 和 poll() 系統調用的本質同樣,管理多個描述符也是進行輪詢,根據描述符的狀態進行處理,可是 poll() 沒有最大文件描述符數量的限制(可是數量過大後性能也是會降低)。poll() 和 select() 一樣存在一個缺點就是,包含大量文件描述符的數組被總體複製於用戶態和內核的地址空間之間,而不論這些文件描述符是否就緒,它的開銷隨着文件描述符數量的增長而線性增大。api

2、poll API

poll()函數介紹數組

#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

(1) 功能:網絡

監視並等待多個文件描述符的屬性變化數據結構

(2) 參數:socket

  • fds:指向一個結構體數組的第 0 個元素的指針,每一個數組元素都是一個 struct pollfd 結構,用於指定測試某個給定的 fd 的條件函數

    struct pollfd {
        int fd;         // 文件描述符
        short events;   // 等待的事件
        short revents;  // 實際發生的事件
    };
  • nfds:用來指定第一個參數數組元素個數。性能

  • timeout:指定等待的毫秒數,不管 I/O 是否準備好,poll() 都會返回。

(3) pollfd 數據結構

  • fd:每個 pollfd 結構體指定了一個被監視的文件描述符,能夠傳遞多個結構體,指示 poll() 監視多個文件描述符。

  • events:指定監測 fd 的事件(輸入、輸出、錯誤),每個事件有多個取值,以下:

    POLLIN          有數據可讀
    POLLRDNORM      有普通數據可讀,等效與POLLIN
    POLLPRI         有緊迫數據可讀
    POLLOUT         寫數據不會致使阻塞
    POLLER          指定的文件描述符發生錯誤
    POLLHUP         指定的文件描述符掛起事件
    POLLNVAL        無效的請求,打不開指定的文件描述符
  • revents:revents 域是文件描述符的操做結果事件,內核在調用返回時設置這個域。events 域中請求的任何事件均可能在 revents 域中返回。

注意:每一個結構體的 events 域是由用戶來設置,告訴內核咱們感興趣的事件是什麼,而 revents 域是返回時內核設置的,以說明對該描述符發生了什麼事件。

(4) 返回值

  • 成功時,poll() 返回結構體中 revents 域不爲 0 的文件描述符個數;若是在超時前沒有任何事件發生,poll() 返回 0;

  • 失敗時,poll() 返回 -1,並設置 errno 爲下列值之一:

    EBADF:  一個或多個結構體中指定的文件描述符無效。
    EFAULT: fds 指針指向的地址超出進程的地址空間。
    EINTR:  請求的事件以前產生一個信號,調用能夠從新發起。
    EINVAL: nfds 參數超出 PLIMIT_NOFILE 值。
    ENOMEM: 可用內存不足,沒法完成請求。

附1:linux 每一個進程IO限制

# 當前計算機所能打開的最大文件個數。受硬件影響,這個值也能夠改(經過limits.conf)
cat /proc/sys/fs/file-max

# 查看一個進程能夠打開的socket描述符上限。缺省爲1024
ulimit -a 
# 修改成默認的最大文件個數。【註銷用戶,使其生效】
ulimit -n 2000

# soft軟限制 hard硬限制。所謂軟限制是能夠用命令的方式修改該上限值,但不能大於硬限制
vi /etc/security/limits.conf
* soft nofile 3000      # 設置默認值。可直接使用命令修改
* hard nofile 20000     # 最大上限值

附2:poll 網絡編程

#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 SERVER_PORT 8888
#define OPEN_MAX  3000
#define BACKLOG   10
#define BUF_SIZE 1024

int main() {
    int i, j, maxi;
    int listenfd, connfd, sockfd; // 定義套接字描述符
    int nready;     // 接受 pool 返回值
    int recvbytes;  // 接受 recv 返回值

    char recv_buf[BUF_SIZE];   // 發送緩衝區
    struct pollfd client[OPEN_MAX]; // struct pollfd* fds

    // 定義 IPV4 套接口地址結構
    struct sockaddr_in seraddr;     // server 地址
    struct sockaddr_in cliaddr;     // client 地址
    int cliaddr_len;

    // 初始化IPV4套接口地址結構
    seraddr.sin_family = AF_INET;   // 指定該地址家族
    seraddr.sin_port = htons(SERVER_PORT);    // 端口
    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 error");
        exit(1);
    }

    // 地址重複利用
    int on = 1;
    if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
        perror("setsockopt error");
        exit(1);
    }

    // bind() 函數
    if(bind(listenfd, (struct sockaddr *)&seraddr, sizeof(struct sockaddr)) == -1) {
        perror("bind error");
        exit(1);
    }

    // listen()函數
    if(listen(listenfd, BACKLOG) == -1) {
        perror("listen error");
        exit(1);
    }

    client[0].fd = listenfd;    // 將 listenfd 加入監聽序列
    client[0].events = POLLIN;  // 監聽讀事件

    // 初始化client[]中剩下的元素
    for(i = 1;i < OPEN_MAX;i++) {
        client[i].fd = -1;      //不能用 0,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的讀事件就緒        
            cliaddr_len = sizeof(cliaddr);
            if((connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &cliaddr_len))==-1) {
                perror("accept error");
                exit(1);
            }
            printf("client IP: %s\t PORT : %d\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));
            //將sockfd加入監聽序列
            for(i = 1; i < OPEN_MAX; i++) {
                if(client[i].fd < 0) {
                    client[i].fd = connfd;
                    break;
                }
            }
            if(i == OPEN_MAX) {
                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(recv_buf, 0, sizeof(recv_buf));
                recvbytes = recv(sockfd, recv_buf, BUF_SIZE, 0);

                if(recvbytes < 0) {
                    // `errno == EINTR` 被異常中斷,須要重啓。收到 RST 標誌
                    // `errno == EAGIN 或 EWOULDBLOCK` 以非阻塞方式讀數據,但沒有數據,須要再次讀
                    // `errno == ECONNRESET` 鏈接被重置,須要 close,移除鏈接
                    // `errno == other` 其它異常
                    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(recvbytes == 0) {
                    printf("client[%d],close!\n",i);
                    close(sockfd);
                    client[i].fd = -1;
                } else {
                    send(sockfd, recv_buf, recvbytes, 0);
                }
                if(--nready == 0) {
                    break;
                }

            }
        }
    }
    return 0;
}

參考:


天天用心記錄一點點。內容也許不重要,但習慣很重要!

相關文章
相關標籤/搜索