poll
是一種在 Linux 系統中進行 I/O 多路復用的模型,它與 select
類似,但具有一些不同之處。poll
允許監視的文件描述符數量不受限制,而不像 select
有一定的限制。
基本概念:
-
poll
函數: 通過poll
函數,可以監視多個文件描述符的狀態。它使用struct pollfd
結構體數組來描述要監視的文件描述符集合,并返回準備好讀、寫或發生錯誤的文件描述符的數量。#include <poll.h>int poll(struct pollfd *fds, nfds_t nfds, int timeout);
fds
:一個指向struct pollfd
結構體數組的指針,每個元素描述了一個要監視的文件描述符。nfds
:數組中元素的數量。timeout
:設置poll
的超時時間,單位是毫秒。如果為負值,表示一直等待,如果為零,表示立即返回。
-
struct pollfd
結構體: 用于描述單個文件描述符的狀態。struct pollfd {int fd; // 文件描述符short events; // 要監視的事件(POLLIN、POLLOUT等)short revents; // 實際發生的事件 };
使用示例:
以下是一個簡單的使用 poll
進行 I/O 多路復用的示例:
#include <stdio.h>
#include <poll.h>int main() {struct pollfd fds[1];int timeout = 1000; // 超時時間,單位是毫秒// 設置要監視的文件描述符fds[0].fd = // 你的文件描述符;fds[0].events = POLLIN; // 監視可讀事件// 使用 poll 監視文件描述符int ready = poll(fds, 1, timeout);if (ready > 0) {if (fds[0].revents & POLLIN) {// 文件描述符可讀,處理數據}} else if (ready == 0) {// 超時} else {// 錯誤發生}return 0;
}
注意事項:
-
poll
模型的優勢在于不受文件描述符數量的限制,但在高并發的場景中,可能仍需要考慮性能。 -
poll
和select
一樣,都是阻塞調用,可以通過設置超時時間為零或者使用非阻塞文件描述符來實現非阻塞調用。 -
可以使用
poll
的返回值來判斷文件描述符是否就緒,以及發生了哪些事件。 -
poll
模型的使用和select
相似,但在性能和可擴展性方面略有不同。在實際應用中,可以根據具體需求選擇適合的模型。
poll
函數使用的事件宏是定義在 poll.h
頭文件中的,這些宏表示 poll
可以監視的不同事件。下面是 poll
事件宏的一些常見值:
-
POLLIN: 表示文件描述符可以讀取。如果設置了這個事件,表示數據可用于讀取。
fds[0].events = POLLIN;
-
POLLOUT: 表示文件描述符可以寫入。如果設置了這個事件,表示可以向文件描述符寫入數據。
fds[0].events = POLLOUT;
-
POLLPRI: 表示有緊急數據可讀。這通常是帶外數據或特殊條件的指示。
fds[0].events = POLLPRI;
-
POLLERR: 表示發生錯誤。如果設置了這個事件,表示文件描述符發生了錯誤條件。
fds[0].events = POLLERR;
-
POLLHUP: 表示文件描述符掛起。如果設置了這個事件,表示文件描述符被掛起(例如,對端關閉了連接)。
fds[0].events = POLLHUP;
-
POLLNVAL: 表示文件描述符不是一個打開的文件。如果設置了這個事件,表示文件描述符不是一個有效的打開文件。
fds[0].events = POLLNVAL;
這些事件宏可以通過位運算進行組合,以監視多個事件。例如,同時監視可讀和可寫事件:
fds[0].events = POLLIN | POLLOUT;
在使用 poll
函數時,需要注意的是,revents
字段表示實際發生的事件,可以使用這個字段來判斷文件描述符發生了哪些事件。
if (fds[0].revents & POLLIN) {// 文件描述符可讀,處理數據
}if (fds[0].revents & POLLOUT) {// 文件描述符可寫,寫入數據
}// 其他事件的處理類似
這樣可以根據實際發生的事件來執行相應的操作。
// 此程序演示采用poll模型實現網絡通訊的服務端#include <stdio.h>
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <poll.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/fcntl.h>using namespace std;int initserver(int port);int main (int argc, char *argv[])
{if(argc != 2){cout << "usage:./tcppoll port" << endl;return -1;}int listensock = initserver(atoi(argv[1]));if(listensock < 0) {cout << "initserver() failed" << endl; return -1;}pollfd fds[2048];for(int ii = 0; ii < 2048; ii ++ ) // 初始化全部socket為-1,poll會忽略值為-1的fds[ii].fd = -1;fds[listensock].fd = listensock; // socket和下標一一對應fds[listensock].events = POLLIN; // 只監視讀事件int maxfd = listensock;while(true){int infds = poll(fds, maxfd + 1, 10000); // 超時時間為10秒if(infds < 0) {perror("select() failed"); break; }if(infds == 0) {cout << "select() timeout." << endl; continue; }for(int eventfd = 0; eventfd <= maxfd; eventfd ++ ){if(fds[eventfd].fd < 0) continue;if((fds[eventfd].revents & POLLIN) == 0) continue; // 沒有讀事件,continueif(eventfd == listensock) // 如果發生事件的是監聽的socet,表示已連接隊列中有已經準備好的socket(有新的客戶端連上來了){struct sockaddr_in client;socklen_t len = sizeof(client);int clientsock = accept(listensock, (struct sockaddr*)&client, &len); // 調用一個客戶端連接if(clientsock < 0) {perror("accept() failed"); continue; }cout << "accept client(socket = " << clientsock << ")ok." << endl;fds[clientsock].fd = clientsock;fds[clientsock].events = POLLIN;if(maxfd < clientsock) maxfd = clientsock; // 整個if內代碼可以理解為如果有新的客戶端連上來,讓位圖對應位置標記為1}else // 如果是客戶端連接的socket有事件,表示接收緩存中有數據可以讀(對端發送的報文已到達),或者有客戶端已斷開連接{char buffer[1024];memset(buffer, 0, sizeof(buffer));if(recv(eventfd, buffer, sizeof(buffer), 0) <= 0) // 從接收緩沖區中讀取數據,<0已發生錯誤,=0表示斷開{cout << "client(eventfd = " << eventfd << ") disconnected." << endl;close(eventfd);fds[eventfd],fd = -1;if(eventfd == maxfd) // 關閉了當前的eventfd,應該重新設置maxfd{for(int ii = maxfd; ii > 0; ii ++ )if(fds[ii].fd != -1){maxfd = ii; break;}}}else // recv返回值大于0,表示對端發送的報文已到達,recv將返回成功讀取的字節數{cout << "recv(eventfd = " << eventfd << "): " << buffer << endl;send(eventfd, buffer, strlen(buffer), 0);} }}}return 0;}int initserver(int port)
{int sock = socket(AF_INET, SOCK_STREAM, 0);if(sock < 0){perror("socket() failed");return -1;}int opt = 1;unsigned int len = sizeof(opt);setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, len);struct sockaddr_in servaddr;servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(port);if(bind(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){perror("bind() failed");close(sock);return -1;}if(listen(sock, 5) != 0){perror("listen() failed");close(sock);return -1;}return sock;
}