Linux NIO 系列(04-2) polllinux
Netty 系列目錄(http://www.javashuo.com/article/p-hskusway-em.html)編程
select() 和 poll() 系統調用的本質同樣,管理多個描述符也是進行輪詢,根據描述符的狀態進行處理,可是 poll() 沒有最大文件描述符數量的限制(可是數量過大後性能也是會降低)。poll() 和 select() 一樣存在一個缺點就是,包含大量文件描述符的數組被總體複製於用戶態和內核的地址空間之間,而不論這些文件描述符是否就緒,它的開銷隨着文件描述符數量的增長而線性增大。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: 可用內存不足,沒法完成請求。
# 當前計算機所能打開的最大文件個數。受硬件影響,這個值也能夠改(經過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 # 最大上限值
#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; }
參考:
天天用心記錄一點點。內容也許不重要,但習慣很重要!