多路复用poll函数

poll函数

  • poll 是 Linux 下 I/O 多路复用的实现函数之一,解决了 select文件描述符数量受限的核心问题,采用结构体数组管理待监听的文件描述符,无需计算最大 fd+1,使用更灵活,是 select 的优化版本

函数原型

c 复制代码
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

参数

参数 类型 含义与使用说明
fds struct pollfd * 指向pollfd 结构体数组的指针,存放待监听的文件描述符及对应的事件(输入 / 输出)
nfds nfds_t fds数组的有效元素个数(即待监听的文件描述符数量),无 fd+1 的要求
timeout int 超时时间,单位为毫秒 负数:永久阻塞,直到有事件发生 0:非阻塞模式,立即返回检测结果 正数:阻塞指定毫秒数,超时无事件则返回 0

结构体pollfd

  • poll 通过独立的结构体分离请求事件(events)和返回事件(revents),无需像 select 那样每次调用重新初始化事件集合,是核心设计优化点
c 复制代码
struct pollfd {
    int   fd;         /* 待监听的文件描述符;设为-1时,内核会忽略该结构体 */
    short events;     /* 【入参】注册的、需要监听的事件(由宏定义,可组合) */
    short revents;    /* 【出参】内核返回的、实际发生的事件(内核自动赋值,只读) */
};

事件类型(宏定义)

  • events/revents 均通过以下宏指定事件,多个事件可通过 按位或(|)组合,常用核心事件:
事件宏 含义
POLLIN 对应文件描述符有数据可读(核心读事件,如套接字收到数据、监听套接字有新连接)
POLLPRI 紧急数据需要读取(如 TCP 带外数据 OOB)
POLLOUT 对应文件描述符可写(核心写事件,如套接字发送缓冲区有空闲空间)
POLLERR 对应文件描述符发生错误(由内核自动设置,仅出现在 revents 中)
POLLHUP 对应文件描述符被挂断(如 TCP 连接对端关闭,仅出现在 revents 中)

返回值

返回值 含义 处理逻辑
> 0 成功 实际发生事件的文件描述符总数 ;需遍历fds数组,通过revents判断具体哪个 fd 发生了什么事件
0 超时 阻塞时间内无任何文件描述符触发事件,revents无有效赋值
-1 失败 错误码存入errno;若为EINTR(被信号中断),通常需重启 poll 循环;其他错误(如参数无效)需退出处理

特点

优点

  • 无 fd 数量硬限制:select 受FD_SETSIZE(默认 1024)限制,poll 通过数组管理,仅受系统最大文件描述符数(ulimit -n)限制,支持监听更多连接
  • 事件集合无需重复初始化:select 的 fd_set 每次调用前需重新置零、添加 fd;poll 的events是入参,revents是出参,内核不会修改events,只需一次注册,后续可复用
  • fd 管理更灵活:将某个 pollfd 的fd设为 - 1,内核会直接忽略,无需从数组中删除,适合动态添加 / 移除监听fd
  • 返回值更直观:直接返回发生事件的 fd 总数,无需像 select 那样遍历所有 fd 判断是否就绪

缺点

  • 效率仍有瓶颈:内核仍需轮询整个 fds 数组判断事件是否发生,当监听 fd 数量极多(如上万)时,轮询开销大,性能下降
  • 仅支持水平触发(LT):与 select 一致,只要 fd 的事件未处理完成(如数据未读完),每次调用 poll 都会重复返回该事件
  • 跨平台性差:仅支持 Linux/Unix 类系统,无 Windows 原生支持(select 是跨平台的)

demo

c 复制代码
#ifndef _NET_H_
#define _NET_H_

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <strings.h>
#include <errno.h>

typedef struct sockaddr Addr;
typedef struct sockaddr_in Addr_in;
#define BACKLOG 5
#define ErrExit(msg) do { perror(msg); exit(EXIT_FAILURE); } while(0)

void Argment(int argc, char *argv[]);
int CreateSocket(char *argv[]);
int DataHandle(int fd);


#endif
c 复制代码
#include "net.h"

void Argment(int argc, char *argv[]){
	if(argc < 3){
		fprintf(stderr, "%s<addr><port>\n", argv[0]);
		exit(0);
	}
}
int CreateSocket(char *argv[]){
	/*创建套接字*/
	int fd = socket(AF_INET, SOCK_STREAM, 0);
	if(fd < 0)
		ErrExit("socket");
	/*允许地址快速重用*/
	int flag = 1;
	if( setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag) ) )
		perror("setsockopt");
	/*设置通信结构体*/
	Addr_in addr;
	bzero(&addr, sizeof(addr) );
	addr.sin_family = AF_INET;
	addr.sin_port = htons( atoi(argv[2]) );
	/*绑定通信结构体*/
	if( bind(fd, (Addr *)&addr, sizeof(Addr_in) ) )
		ErrExit("bind");
	/*设置套接字为监听模式*/
	if( listen(fd, BACKLOG) )
		ErrExit("listen");
	return fd;
}
int DataHandle(int fd){
	char buf[BUFSIZ] = {};
	Addr_in peeraddr;
	socklen_t peerlen = sizeof(Addr_in);
	if( getpeername(fd, (Addr *)&peeraddr, &peerlen) )
		perror("getpeername");
	int ret = recv(fd, buf, BUFSIZ, 0);
	if(ret < 0)
		perror("recv");
	if(ret > 0){
		printf("[%s:%d]data: %s\n", 
				inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port), buf);
	}
	return ret;
}
c 复制代码
#include "net.h"
#include <poll.h>

#define MAX_SOCK_FD 1024
int main(int argc, char *argv[])
{
	int i, j, fd, newfd;
	nfds_t nfds = 1;
	struct pollfd fds[MAX_SOCK_FD] = {};
	Addr_in addr;
	socklen_t addrlen = sizeof(Addr_in);
	/*检查参数,小于3个 直接退出进程*/
	Argment(argc, argv);
	/*创建已设置监听模式的套接字*/
	fd = CreateSocket(argv);
	fds[0].fd = fd;
	fds[0].events = POLLIN;
	while(1){
		if( poll(fds, nfds, -1) < 0)
			ErrExit("poll");
		for(i = 0; i < nfds; i++){
			/*接收客户端连接,并生成新的文件描述符*/
			if(fds[i].fd == fd && fds[i].revents & POLLIN){
				if( (newfd = accept(fd, (Addr *)&addr, &addrlen) ) < 0)
					perror("accept");
				fds[nfds].fd = newfd;
				fds[nfds++].events = POLLIN;
				printf("[%s:%d][nfds=%lu] connection successful.\n", 
						inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), nfds);
			}
			/*处理客户端数据*/
			if(i > 0 && fds[i].revents & POLLIN){
				if(DataHandle(fds[i].fd) <= 0){
					if( getpeername(fds[i].fd, (Addr *)&addr, &addrlen) < 0)
						perror("getpeername");
					printf("[%s:%d][fd=%d] exited.\n", 
							inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), fds[i].fd);
					close(fds[i].fd);
					for(j=i; j<nfds-1; j++)
						fds[j] = fds[j+1];
					nfds--;
					i--;
				}
			}
		}
	}
	close(fd);
	return 0;
}
相关推荐
我在人间贩卖青春10 小时前
多路复用select函数
select·多路复用
Trouvaille ~1 天前
【Linux】epoll 深度剖析:高性能 IO 多路复用的终极方案
linux·运维·服务器·c++·epoll·多路复用·io模型
Trouvaille ~2 天前
【Linux】poll 多路转接:select 的改良版,以及它留下的遗憾
linux·运维·服务器·操作系统·select·poll·多路复用
CSCN新手听安14 天前
【linux】高级IO,I/O多路转接之poll,接口和原理讲解,poll版本的TCP服务器
linux·运维·服务器·c++·计算机网络·高级io·poll
xu_yule19 天前
网络和Linux网络-14(IO多路转接)poll和epoll编程-服务器
linux·运维·服务器·epoll·poll
Ronin3052 个月前
【Linux网络】多路转接poll
linux·网络·io·多路转接·poll
橘子真甜~3 个月前
C/C++ Linux网络编程6 - poll解决客户端并发连接问题
服务器·c语言·开发语言·网络·c++·poll
无聊的小坏坏3 个月前
Poll 服务器实战教学:从 Select 迁移到更高效的多路复用
linux·服务器·poll·io多路复用
敲上瘾6 个月前
Linux I/O 多路复用实战:Select/Poll 编程指南
linux·服务器·c语言·c++·select·tcp·poll