I/O复用 poll系统调用

poll系统调用的用途是:在一段指定时间内,轮询一定数量的文件描述符,以测试其中是否有就绪者。

poll API

poll的原型如下:

#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

poll 系统调用成功返回就绪文件描述符的总数,超时返回 0,失败返回-1。
nfds 参数指定被监听事件集合 fds 的大小。
timeout 参数指定 poll 的超时值,单位是毫秒,timeout 为-1 时,poll 调用将永久阻塞,直到某个事件发生,timeout 为 0 时,poll 调用将立即返回。
fds 参数是一个 struct pollfd 结构类型的数组,它指定所有用户感兴趣的文件描述符上发生的可读、可写和异常等事件。pollfd 结构体定义如下:

struct pollfd
{
	int fd; // 文件描述符
	short events; // 注册的关注事件类型
	short revents; // 实际发生的事件类型,由内核填充
};

其中,fd 成员指定文件描述符,events 成员告诉 poll 监听 fd 上的哪些事件类型。它是一系列事件的按位或,revents 成员则由内核修改,通知应用程序 fd 上实际发生了哪些事件。poll 支持的事件类型如下:

事件 描述 是否可作为输入 是否可作为输出
POLLIN 数据(包括普通数据和优先数据)可读
POLLRDNORM 普通数据可读
POLLRDBAND 优先级带数据可读(Linux不支持)
POLLPRI 高优先级数据可读,比如TCP带外数据
POLLOUT 数据(包括普通数据和优先数据)可写
POLLWRNORM 普通数据可读写
POLLWRBAND 优先级带数据可写
POLLRDHUP TCP连接被对方关闭,或者对方关闭了写操作。它有GNU引入
POLLERR 错误
POLLHUP 挂起。比如管道的写端被关闭后,读端描述符上将收到POLLHUP事件
POLLNVAL 文件描述符没有打开

POLLRDNORM、POLLRDBAND、POLLWRNORM、POLLWRBAND由XOPEN规范定义。它们实际上是将POLLIN事件和POLLOUT事件分得更细致,以区别对待普通数据和优先数据。但Linux并不完全支持它们。
通常,应用程序需要根据recv调用的返回值来区分socket上接收到的是有效数据还是对方关闭连接的请求,并做相应的处理。不过,自Linux内核2.6.17开始,GNU 为poll系统调用增加了一个POLLRDHUP事件,它在socket上接收到对方关闭连接的请求之后触发。这为我们区分上述两种情况提供了一种更简单的方式。但使用POLLRDHUP事件时,我们需要在代码最开始处定义_GNU_ SOURCE。

poll内核实现

内核通过一个事件集参数struct pollfd *fds统一处理所有事件类型。用户通过通过pollfd.events传入感兴趣的事件,内核通过修改pollfd.revents反馈其中的就绪事件。

poll的代码示例

使用poll实现的TCP服务器代码如下:

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <poll.h>
#define DATALEN 1024
#define MAX_FD 128
// 初始化服务器端的 sockfd 套接字
int InitSocket()
{
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sockfd == -1) return -1;
	struct sockaddr_in saddr;
	memset(&saddr, 0, sizeof(saddr));
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(6000);
	saddr.sin_addr.s_addr = inet_addr("127.0.0.1");

	int res = bind(sockfd, (struct sockaddr*)&saddr, sizeof(saddr));
	if(res == -1) return -1;

	res = listen(sockfd, 5);
	if(res == -1) return -1;

	return sockfd;
}

void InitPollFds(struct pollfd fds[])
{
	int i = 0;
	for (; i < MAX_FD; ++i)
	{
		fds[i].fd = -1;
	}
}

void InsertFdToPollfds(struct pollfd fds[], int fd, short events)
{
	int i = 0;
	for (; i < MAX_FD; ++i)
	{
		if (fds[i].fd == -1)
		{
			fds[i].fd = fd;
			fds[i].events = events;
			break;
		}
	}
}

void GetClinetLink(struct pollfd fds[], int sockfd)
{
	struct sockaddr_in caddr;
	memset(&caddr, 0, sizeof(caddr));
	socklen_t len = sizeof(caddr);
	int c = accept(sockfd, (struct sockaddr*)&caddr, &len);
	if (c < 0)
	{
		return;
	}

	printf("A client connection was successful\n");

	InsertFdToPollfds(fds, c, POLLIN | POLLRDHUP);
}

// 处理客户端数据
int DealClientData(int clifd)
{
	char data[DATALEN] = { 0 };
	int n = recv(clifd, data, DATALEN - 1, 0);
	if(n <= 0)
	{
		return -1;
	}

	printf("%d: %s\n", clifd, data);
	send(clifd, "OK", 2, 0);
	return 0;
}

// 处理 poll 返回的就绪事件
void DealReadyEvent(struct pollfd fds[], int sockfd)
{
	int i = 0;
	for (; i < MAX_FD; ++i)
	{
		if (fds[i].fd == -1)
		{
			continue;
		}
		else if (fds[i].revents & POLLRDHUP)
		{
			close(fds[i].fd);
			fds[i].fd = -1;
			printf("A client disconnected\n");
			continue;
		}
		else if (fds[i].revents & POLLIN)
		{
			if (fds[i].fd == sockfd)
			{
				GetClinetLink(fds, sockfd);
			}
			else
			{
				if(-1 == DealClientData(fds[i].fd))
				{
					close(fds[i].fd);
					fds[i].fd = -1;
					printf("A client disconnected\n");
				}
			}
		}
	}
}

int main()
{
	int sockfd = InitSocket();
	assert(sockfd != -1);

	struct pollfd fds[MAX_FD];
	InitPollFds(fds);
	InsertFdToPollfds(fds, sockfd, POLLIN);

	while (1)
	{
		int n = poll(fds, MAX_FD, 2000);
		if (n < 0)
		{
			printf("poll error\n");
			continue;
		}
		else if (n == 0)
		{
			printf("timeout\n");
			continue;
		}
		else
		{
			DealReadyEvent(fds, sockfd);
		}
	}
	exit(0);
}

测试结果:
在这里插入图片描述

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐