I/O复用之poll的实现

本文详细解析了poll系统调用的工作原理,包括其与select的相似之处和独特优势,如更大的文件描述符范围和更灵活的事件处理。通过具体代码示例,展示了如何使用poll进行高效的事件监测。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

poll系统调用和select类似,也是在指定时间内轮询一定数量的文件描述符,以测试其中是否有就绪者。
poll的原型如下:

int poll(struct pollfd *fd, int nfds, int timeout)

fds:是一个pollfd结构类型的数组,它指定所有我们感兴趣的文件描述符上发生的可读、可写和异常等事件。pollfd结构体定义如下

struct pollfd
{
	int fd;/*关注的文件描述符*/
	short events;/*关注的文件描述符上的哪种事件*/
	short revents;/*由内核修改的,返回此文件描述符上发生的事件类型(必须是events指定的关注的事件)*/
};

poll的事件类型
poll的事件类型
注意:POLLRDHUP事件发生也会触发POLLIN事件
nfds:参数指定被监听事件集合的大小(类型为unsigned long int)。
timeout:参数指定poll的超时值,单位毫秒,当timeout为-1时,poll调用将永远阻塞,知道某个事件发生;当timeout为0时,poll调用将立即返回。
返回值:成功返回就绪文件描述符的个数,失败返回-1,超时时间到达无文件描述符就绪返回0。

poll相比于select的优点:

1.将用户关注的事件与内核修改的就绪事件分隔开,每次调用poll不需要重新设置
2.poll通过int类型记录文件描述符,文件描述符的取值范围扩大到系统最大值65535
3.用户关注的所有文件描述符通过fds执行的用户数组来记录,介意关注的文件描述符的个数由用户决定,可以扩大到系统最大值。

仍然存在的问题

1.返回值依旧是就绪文件描述符的个数,仍然需要去探测数组中文件描述符,时间复杂度为O(n)
2.内核也是采用轮询的方式处理
具体实现代码如下:

//初始化该用户数组,文件描述符置-1,事件置0
void InitFds(struct pollfd *fds)
{
	int i = 0;
	for(; i<FDMAX; ++i)
	{
		fds[i].fd = -1;
		fds[i].events = 0;
		fds[i].revents = 0;
	}
}
//将获取到的文件描述符,添加事件(POLLIN | POLLRDHUP)
void ADDFd(int fd, struct pollfd *fds)
{
	int i = 0;
	for(; i<FDMAX; ++i)
	{
		if(fds[i].fd == -1)
		{
			fds[i].fd = fd;
			fds[i].events = POLLIN  |  POLLRDHUP;
			return;
		}
	}
}
//将断开链接的文件描述符从用户数组删除,事件置0
void DeleteFd(int fd, struct pollfd *fds)
{
	int i = 0;
	for(; i<FDMAX; ++i)
	{
		if(fds[i].fd == fd)
		{
			fds[i].fd = -1;
			fds[i].events = 0;
			return;
		}
	}
}
//获取的文件描述符是新客户端的连接,接收(accept())新的链接,并将该文件描述符存入用户数组中并设置事件
void GetClientLink(int fd, struct pollfd *fds)
{
	
	struct sockaddr_in cli;
	int len = sizeof(cli);
	int c = accept(fd, (struct sockaddr*)&cli, &len);
	if(c == -1)
		return;

	ADDFd(c, fds);
}
//获取的文件描述符是客户端的信息时,处理收到的信息
void DealClientData(int fd, struct pollfd *fds)
{
	char buff[128] = {0};
	int n = recv(fd, buff, 127, 0);
	if(n <= 0)
	{
		DeleteFd(fd, fds);
		return;
	}
	printf("%s\n", buff);

	send(fd, "OK", 2, 0);

}
int main()
{
	//进行TCP连接
	int listenfd = socket(AF_INET, SOCK_STREAM, 0);
	assert(listenfd != -1);

	struct sockaddr_in ser;
	memset(&ser, 0, sizeof(ser));
	ser.sin_family = AF_INET;
	ser.sin_port = htons(6000);
	ser.sin_addr.s_addr = inet_addr("127.0.0.1");
	int res = bind(listenfd, (struct sockaddr*)&ser, sizeof(ser));

	listen(listenfd, 5);
	//定义存放文件描述符和事件结构的用户数组
	struct pollfd fds[FDMAX];
	//初始化该数组
	InitFds(fds);
	//第一个listenfd存入到用户数组中等待处理
	fds[0].fd = listenfd;
	fds[0].events = POLLIN;

	while(1)
	{
		//调用poll将用户数组,数组大小,超时时间传入
		int n = poll(fds, FDMAX, -1);
		if(n <= 0)
		{
			exit(0);
		}
		int i = 0;
		//轮询方式去检索符合数组中的文件描述符是否有对应的事件发生,对不同类型的文件描述符进行不同处理
		for(; i<FDMAX; ++i)
		{
			if(fds[i].fd != -1)
			{
				//若事件是关闭连接,则删除掉存在用户数组中的文件描述符
				if(fds[i].revents & POLLRDHUP)
				{
					close(fds[i].fd);
					DeleteFd(fds[i].fd, fds);
					printf("one Client unlink\n");
				}
				else if(fds[i].revents & POLLIN)//若事件是读则判断文件描述符是新客户端连接还是客户端信息
					{
						if(fds[i].fd == listenfd)
						{
							GetClientLink(fds[i].fd, fds);
						}
						else
						{
							DealClientData(fds[i].fd, fds);
						}
					}
			}
		}
	}

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值