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;
}