美文网首页
服务器IO多路复用之poll机制

服务器IO多路复用之poll机制

作者: 二进制人类 | 来源:发表于2022-09-20 19:15 被阅读0次

特征

  1. 和select机制一样,实现了IO多路复用;

  2. 不需要根据操作创建不同集合,所有事件在同一个集合中实现,用events成员输入事件,revents成员返回事件;

  3. 事件集合中每一个元素所占空间量更大,需要存储文件描述符、输入事件和返回事件;

  4. 对于分散的文件描述符可以使用事件集合顺序存储,在监视之后,顺序遍历,提高访问的效率。

流程

1.创建事件集合

2.将需要监视的文件描述符fd和读写事件添加到集合中

3.使用poll函数来检测是否有资源准备就绪

4.遍历事件集合数组,找到事件和对应的文件描述符,进行IO操作

相关API

#include <poll.h>
/**
 * [poll 函数]
 * @param  fds     [pollfd结构体集合]
 * @param  nfds    [数组个数]
 * @param  timeout [超时时间]
 * @return         [-1 表示poll调用失败,修改errno的值; 0 阻塞超时返回 >0 有资源准备就绪返回]
 */
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

实例

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <poll.h>

int main()
{
    int listenfd;
    int connfd;
    int ret;
    int optval;
    struct sockaddr_in srvaddr;
    struct sockaddr_in cltaddr;
    socklen_t addrlen = sizeof(cltaddr);
    int fd;
    char buf[128];

    /* 1. 创建TCP流式套接字文件 */
    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd == -1)
    {
        perror("socket");
        return -1;
    }

    /* 设置套接字允许IP地址和端口被重新设置 */
    optval = 1;/* 允许IP地址和端口被重新设置 */
    ret = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
    if (ret == -1)
    {
        perror("setsockopt");
        return -1;
    }

    /* 2. 设置服务器主机的IP地址和端口号 */
    srvaddr.sin_family = AF_INET;
    srvaddr.sin_port = htons(8888);
    srvaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    ret = bind(listenfd, (const struct sockaddr *)&srvaddr, sizeof(srvaddr));
    if (ret == -1)
    {
        perror("bind");
        return -1;
    }

    /* 3. 启动服务器监听客户端的连接请求 */
    ret = listen(listenfd, 1024);
    if (ret == -1)
    {
        perror("listen");
        return -1;
    }
    printf("listenfd = %d server init success\n", listenfd);

    /* 创建事件集合 */
    struct pollfd fds[1024];
    int nfds;
    int i;
    /* 清空事件集合 */
    for (i = 0; i < sizeof(fds) / sizeof(fds[0]); i++)
    {
        fds[i].fd = -1;/* 文件描述符是>=0的,设置为-1表示不关心 */
    }
    /* 将需要关心读事件的文件描述符listenfd添加到读事件中 */
    fds[0].fd = listenfd;
    fds[0].events = POLLIN | POLLOUT;
    nfds = 1;/* 集合中有效数组元素的个数 */

    while(1)
    {
        /* 以阻塞模式检测集合中是否有资源准备就绪的文件描述符,有则返回,没有会一直阻塞 */
        ret = poll(fds, nfds, -1);
        if(ret == -1)
        {
            perror("poll");
            return -1;
        }
        else if (ret == 0)
        {
            fprintf(stderr, "poll timeout ...\n");
            continue;
        }

        /* 循环处理资源准就绪的文件描述符的IO操作 */
        for (i = 0; i < nfds; i++)
        {
            /* 判断返回事件 */
            if (fds[i].revents == POLLIN)
            {
                /* 读事件 */
                fd = fds[i].fd;
                printf("fd = %d\n", fd);
                if (fd == listenfd)  /* 监听套接字资源准备就绪:说明有新的客户端发起连接请求 */
                {
                    /* 4. 服务器等待监听客户端的连接请求并建立连接 */
                    connfd = accept(listenfd, (struct sockaddr *)&cltaddr, &addrlen);
                    if (connfd == -1)
                    {
                        perror("accept");
                        return -1;
                    }
                    printf("connfd = %d client connect success\n", connfd);

                    /* 有新的客户端连接成功,将新的通信套接字文件描述符和与之对应的事件添加事件集合fds中 */
                    fds[nfds].fd = connfd;
                    fds[nfds].events = POLLIN;
                    nfds += 1;
                }
                else    /* 通信套接字资源准备就绪:说明有已经连接的客户端发送数据请求 */
                {
                    /* 接收客户端的数据请求 */
                    memset(buf, 0, sizeof(buf));
                    ret = read(fd, buf, sizeof(buf));
                    if (ret == -1)
                    {
                        perror("read");
                        return -1;
                    }
                    else if (ret == 0)
                    {
                        /*说明对端(客户端)退出*/
                        /* 将退出的文件描述符从事件集合中删除 */
                        int j;
                        for (j = i; j < nfds - 1; j++)
                        {
                            fds[j] = fds[j + 1];
                        }
                        fds[j].fd = -1;
                        nfds -= 1;
                        close(fd);
                    }
                    else
                    {
                        /* 调用write数据的回写 */
                        ret = write(fd, buf, sizeof(buf));
                        if (ret == -1)
                        {
                            perror("write");
                            return -1;
                        }
                    }
                }
            }
            else if(fds[i].revents == POLLOUT)
            {
                /* 写事件 */
            }
            else
            {
                /* 其它事件 */
            }
        }
    }
    close(listenfd);
}

相关文章

网友评论

      本文标题:服务器IO多路复用之poll机制

      本文链接:https://www.haomeiwen.com/subject/czhiortx.html