参考:《UNIX 网络编程 · 卷1 : 套接字联网API》
poll
poll 提供了与 select 相似的功能,不过在处理流设备时,能提供额外的信息。
poll相关接口
需要的头文件:
#include <poll.h>
需要的函数:
int poll(struct pollfd *fdarray, unsigned long nfds, int timeout);
fdarray
是指向结构体 struct pollfd
数组第一个元素的指针。每个数组元素都是一个 pollfd 结构,nfds
决定数组中元素的个数,timeout
指出 poll 函数返回前等待的时间(INFTIM:永远等待;0:立即返回不阻塞;>0:等待指定数目的毫秒数)。
其中 struct pollfd
定义如下:
struct pollfd
{
int fd; //文件描述符
short int events; //关心的事件
short int revents; //发生的事件
};
poll 函数返回值:发生错误返回 -1,没有任何事件发生返回 0,否则返回有事件发生的描述符的个数。
如果我们不关心某个描述符,可以将它的 pollfd 结构的 fd 成员设置成一个负值。poll 函数将忽略这样的 pollfd 结构的 event 成员,返回时将它的 reverts 成员的值置为 0。
在 select 中,每个描述符的大小受到了 FD_SETSIZE 限制,但在 poll 中就不会有此问题了,因为分配一个 pollfd 结构的数组并把该数组中元素的数目通知内核成了调用者的责任。内核不再需要知道类似 fd_set 的固定大小的数据类型。
从可移植性考虑,支持 select 的系统比支持 poll 的系统多,另外 posix 还定义了 pselect,能处理信号阻塞并提供了更高时间分辨率的 select 增强版本,但 poll 没有类似的东西。
poll 的事件
struct pollfd 结构体关心的事件由 events 指定,在相应的 revents 成员中返回该描述符的状态。每个描述符结构体都有两个变量,从而避免了使用传入-传出参数(而 select 的设计三个参数都是传入-传出参数)。events 和 revents 的一些常值如下:
常值 | 能否作为events的输入 | 能否作为revents的结果 | 说明 |
---|---|---|---|
POLLIN | YES | YES | 普通或优先级带数据可读 |
POLLRDNORM | YES | YES | 普通数据可读 |
POLLRDBAND | YES | YES | 优先级带数据可读 |
POLLPRI | YES | YES | 高优先级数据可读 |
POLLOUT | YES | YES | 普通数据可读 |
POLLWRNORM | YES | YES | 普通数据可写 |
POLLWRBAND | YES | YES | 优先级带数据可写 |
POLLERR | NO | YES | 发生错误 |
POLLHUB | NO | YES | 发生挂起 |
POLLNVAL | NO | YES | 描述符不是一个打开的文件 |
前四个是处理输入的四个常值,中三个是处理输出的常值,后三个是处理错误的三个常值。
poll 识别三类数据:普通(normal)、优先级带(priority)、高级优先(high priority)。
TCP 套接字和 UDP 套接字以下条件会引起 poll 返回特定的 revent:
-
所有正规 TCP 数据和 UDP 数据都被认为是普通数据。
-
TCP 的带外数据被认为是优先级带数据。
-
TCP 连接的读半部关闭时,也被认为是普通数据,随后的读操作将返回 0。
-
TCP 连接存在错误即可认为是普通数据,也可认为是错误。无论哪种情况,随后的读操作都将返回 -1,并设置 errno 值。
-
在监听套接字上有新的链接可用即可认为是普通的数据,也可认为是优先级数据。
-
非阻塞式的 connect 的完成被认为使相应的套接字可写。
poll 实例
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <poll.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <vector>
#include <iostream>
using namespace std;
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)
typedef std::vector<struct pollfd> POLLFDLIST;
int main(int argc, char **argv)
{
signal(SIGPIPE, SIG_IGN);
signal(SIGCHLD, SIG_IGN);
int idlefd = open("/dev/null", O_RDONLY | O_CLOEXEC);
//创建套接字
int listenfd;
if ((listenfd = socket(PF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_TCP)) < 0)
ERR_EXIT("socket.");
//填充服务器地址信息
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(8000);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
//地址&端口复用
int on = 1;
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
ERR_EXIT("setsockopt reuseaddr.");
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on)) < 0)
ERR_EXIT("setsockopt reuseport.");
//绑定
if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
ERR_EXIT("bind.");
//监听
if (listen(listenfd, SOMAXCONN) < 0)
ERR_EXIT("listen.");
struct pollfd pfd;
pfd.fd = listenfd;
pfd.events = POLLIN;
POLLFDLIST pollfds;
pollfds.push_back(pfd);
int nready;
struct sockaddr_in peeraddr;
socklen_t peerlen;
int connfd;
while (1)
{
nready = poll(&*pollfds.begin(), pollfds.size(), -1);
if (nready == -1)
{
if (errno == -1)
continue;
ERR_EXIT("poll.");
}
if (nready == 0)
{
continue;
}
if (pollfds[0].revents & POLLIN) //新的客户端连接
{
peerlen = sizeof(peeraddr);
connfd = accept4(listenfd, (struct sockaddr *)&peeraddr, &peerlen, SOCK_NONBLOCK | SOCK_CLOEXEC);
if (connfd == -1)
{
if (errno == EMFILE) //描述符被使用完,应该关闭与对方的连接
{
close(idlefd);
idlefd = accept(listenfd, NULL, NULL);
close(idlefd);
idlefd = open("dev/null", O_RDONLY | O_CLOEXEC);
continue;
}
else
{
ERR_EXIT("accept4.");
}
}
//将新连接的套接字描述符以监听事件加入到pollfds中
pfd.fd = connfd;
pfd.events = POLLIN;
pfd.revents = 0;
pollfds.push_back(pfd);
--nready;
//连接成功
cout << "ip=" << inet_ntoa(peeraddr.sin_addr) << "port=" << ntohs(peeraddr.sin_port) << endl;
if (nready == 0) //只有一个描述符有事件且该事件为接受新的客户端
continue;
}
for (POLLFDLIST::iterator it = pollfds.begin() + 1; it != pollfds.end() && nready > 0; ++it)
{
if (it->revents & POLLIN) //检查可读事件
{
--nready;
connfd = it->fd;
char buf[1024] = {0};
int ret = read(connfd, buf, 1024);
if (ret == -1)
ERR_EXIT("read.");
if (ret == 0) //对方断开连接
{
cout << "client close." << endl;
it = pollfds.erase(it);
--it;
close(connfd);
continue;
}
cout << buf << endl;
write(connfd, buf, strlen(buf));
}
}
}
return 0;
}