IO多路服用poll

参考:《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的结果说明
POLLINYESYES普通或优先级带数据可读
POLLRDNORMYESYES普通数据可读
POLLRDBANDYESYES优先级带数据可读
POLLPRIYESYES高优先级数据可读
POLLOUTYESYES普通数据可读
POLLWRNORMYESYES普通数据可写
POLLWRBANDYESYES优先级带数据可写
POLLERRNOYES发生错误
POLLHUBNOYES发生挂起
POLLNVALNOYES描述符不是一个打开的文件

前四个是处理输入的四个常值,中三个是处理输出的常值,后三个是处理错误的三个常值。

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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值