在Linux系统编程中,poll
是一个强大的多路复用(I/O 多路复用)函数,用于同时监控多个文件描述符的事件,特别是在处理网络套接字或其他I/O设备时。相比于select
,poll
支持监控更多的文件描述符,并且没有像select
那样的文件描述符数量限制。
一、poll
函数介绍
poll
函数用于在指定的超时时间内监视一组文件描述符,并返回文件描述符上是否有指定的I/O事件发生。
函数原型
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
fds
:是一个数组,每个元素是一个pollfd
结构,描述一个文件描述符及其要监视的事件。nfds
:要监视的文件描述符个数。timeout
:等待的超时时间(以毫秒为单位)。-1表示无限等待,0表示立即返回(非阻塞模式)。
pollfd
结构体
struct pollfd { int fd; // 要监视的文件描述符
short events; // 等待的事件
short revents; // 实际发生的事件
};
fd
:要监视的文件描述符,例如套接字或管道。events
:感兴趣的事件,可以是以下的值的组合:POLLIN
:有数据可读。POLLOUT
:可以写数据(不会阻塞)。POLLERR
:发生错误。POLLHUP
:挂起事件(对方关闭连接)。POLLNVAL
:非法的文件描述符。
revents
:poll
返回时,实际发生的事件。
返回值
- 成功时,返回大于0的值,表示有多少文件描述符有事件发生。
- 如果超时且无事件发生,返回0。
- 失败时,返回-1,并设置
errno
。
二、poll
的使用步骤
- 创建并初始化
pollfd
数组:为需要监控的文件描述符设置监视事件。 - 调用
poll
函数 :传入pollfd
数组、数组大小和超时时间。 - 处理事件 :根据返回的
revents
判断哪个文件描述符有事件发生,并做出相应处理。
三、poll
示例
下面是一个使用 poll
监视两个套接字的简单例子:
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <poll.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 8080
#define MAX_EVENTS 2
int main() {
int listenfd, connfd;
struct sockaddr_in serv_addr;
struct pollfd fds[MAX_EVENTS];
int nfds = 1;
// 创建监听套接字
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(PORT);
// 绑定并监听端口
if (bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
perror("bind failed");
close(listenfd);
exit(EXIT_FAILURE);
}
if (listen(listenfd, 3) < 0) {
perror("listen failed");
close(listenfd);
exit(EXIT_FAILURE);
}
// 初始化pollfd数组
fds[0].fd = listenfd;
fds[0].events = POLLIN;
printf("Waiting for connections...\n");
while (1) {
int ret = poll(fds, nfds, -1); // 无限等待事件
if (ret < 0) {
perror("poll failed");
exit(EXIT_FAILURE);
}
// 检查监听套接字是否有新连接
if (fds[0].revents & POLLIN) {
struct sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);
if ((connfd = accept(listenfd, (struct sockaddr*)&client_addr, &addr_len)) < 0) {
perror("accept failed");
exit(EXIT_FAILURE);
}
printf("New connection accepted\n");
}
}
close(listenfd);
return 0;
}
这个例子中,程序首先创建了一个监听套接字,然后使用 poll
函数监视这个套接字的 POLLIN
事件(有新的连接到来)。当有新连接时,程序通过 accept
函数接收连接。
四、常用API介绍
在使用poll
和其他多路复用函数时,通常会涉及以下API:
-
socket
:创建一个套接字,用于网络通信。int socket(int domain, int type, int protocol);
-
bind
:将一个套接字绑定到一个特定的地址和端口。int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
-
listen
:将一个套接字设置为监听模式,等待客户端的连接。int listen(int sockfd, int backlog);
-
accept
:从监听套接字接受一个新的连接。int accept(int sockfd, struct sockaddr *addr,
socklen_t *addrlen);
-
connect
:客户端用于连接到服务端的套接字。int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
-
recv
和send
:分别用于接收和发送数据。ssize_t recv(int sockfd, void *buf, size_t len, int flags); ssize_t send(int sockfd, const void *buf, size_t len,
int flags);
-
close
:关闭文件描述符。int close(int fd);
五、poll
与select
的对比
- 灵活性 :
poll
可以处理更多的文件描述符,不受select
的硬性限制。 - 事件通知 :
poll
的pollfd
数组更加直观,每个文件描述符有自己的事件和返回事件。 - 效率 :
poll
的实现较select
高效,特别是在需要监控大量文件描述符的场景中。
六、结语
poll
提供了一种高效且灵活的方式来监控多个文件描述符的事件,特别适用于网络编程和I/O密集型应用。在实际应用中,poll
被广泛应用于高并发服务器、事件驱动框架等场景中。
如果对文件描述符数量和性能要求更高,还可以考虑使用 epoll
,它是 Linux 下的增强版 poll
,在处理大规模并发连接时更加高效。