I/O多路复用之poll系统调用

poll系统调用主要解决了select系统调用的2个问题:

1. 文件描述符数量(fd_setsize = 32)太小, 而且数值是使用宏写死的,这样在32位机器上最大文件描述符数量只有32*32=1024

2. 文件描述符集(fd_set)这种值-结果参数的api设计不是很好, select系统调用的时候要分别传读set,写set,更多事件不好细分

poll系统调用使用了pollfd数据结构来表示事件数组,没有了fd_setsize的限制,同时支持更多的事件类型,

1. pollfd结构


structpollfd{

intfd;// 需要检查的fd

shortevents;// 该fd感兴趣的事件

shortrevents;// 该fd当前发生的事件

}

其中事件类型有很多,比如POLLIN代表可读,POLLRDNORM代表普通消息可读, 相比于select可以更细化的监听事件, 同时分开使用字段来表示事件和结果(events和revents)。

  1. poll函数
#include <sys/poll.h>

int poll(struct pollfd *fdarray, unsigned long nfds, int timeout);

其中,第一个参数是指向1个pollfd结构数组的第一个元素的指针,第2个参数是pollfd数组元素的个数,第三个参数是poll函数返回前等待多长时间,单位是毫秒。

  1. poll版echo server实例

使用poll函数来实现echo server, 整个过程依然还是分5步

新建socket, 用于监听端口
socket的fd绑定端口
socket监听
初始化pollfd数组
发起poll系统调用获取所有pollfd,寻找和处理事件
#include <sys/poll.h>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syslimits.h>

int main() {
    // 1. 新建tcp流式socket
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd == -1) printf("创建socket失败, error: %s (errno: %d)\n", strerror(errno), errno);

    // 2. 绑定端口
    unsigned short listenPort = 8090;
    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(listenPort);

    int on = 1;
    if ((setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(int)))) {
        exit(1);
    }
    int bindRet = bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr));
    if (bindRet == -1) {
        printf("socket绑定地址失败, error: %s (errno: %d)\n", strerror(errno), errno);
        exit(1);
    }

    // 3. 监听端口
    int listenRet = listen(listenfd, 10);
    if (listenRet == -1) printf("socket监听端口失败, error: %s (errno: %d)\n", strerror(errno), errno);
    printf("socket 监听完毕, 地址: 127.0.0.1:%d", listenPort);

    // 3. 新建pollfd数组
    struct pollfd client[OPEN_MAX];
    client[0].fd = listenfd;
    client[0].events = POLL_IN;
    for (int i = 1; i < OPEN_MAX; i++) {
        client[i].fd = -1;
    }
    int maxi = 0, i = 0, MAXLEN = 1024;
    char buf[MAXLEN];

    // 4. 使用poll
    for (;;) {
        int nready = poll(client, (maxi + 1), -1);

        if (client[0].revents & POLL_IN) {
            // 接受新连接
            struct sockaddr_in clientaddr;
            int clilen = sizeof(clientaddr);
            int connfd = accept(listenfd, (struct sockaddr *) &clientaddr, &clilen);

            // 找到pollfd数组里第一个可用的pollfd
            for (i = 0; i < OPEN_MAX; i++) {
                if (client[i].fd < 0) {
                    client[i].fd = connfd;
                    break;
                }
            }

            if (i == OPEN_MAX) exit(1);
            // 设置fd感兴趣的事件
            client[i].events = POLL_MSG;
            if (i > maxi) maxi = i;
            if (--nready <= 0) continue;
        }

        for (i = 1; i <= maxi; i++) {
            struct pollfd sockfd = client[i];
            if (sockfd.fd < 0) continue;

            if (sockfd.revents & (POLL_MSG | POLL_ERR)) {
                int n = recv(sockfd.fd, buf, MAXLEN, 0);
                if (n == 0 || n < 0) {// 0表示连接关闭 <0表示连接重置
                    close(sockfd.fd);
                    client[i].fd = -1;
                } else write(sockfd.fd, buf, n);

                if (--nready < 0) break; // 没有更多的可读fd
            }
        }
    }
}

依然使用好用的nc, 多开几个终端用nc去发消息,程序也能正常处理

nc localhost 8090

  1. poll总结

poll系统调用解决了select的文件描述符限制,但是依然有select留下的性能缺点:

  1. fd数组(不管是fd_set还是pollfd)都要在用户空间和内核空间之间来回拷贝
  2. 被监控的fd数组有事件的时候,需要遍历整个数组

终极解决方案epoll

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • IO模型介绍 为了更好地了解IO模型,我们需要事先回顾下:同步、异步、阻塞、非阻塞 同步(synchronous)...
    可笑的黑耀斑阅读 1,342评论 0 2
  • 本文摘抄自linux基础编程 IO概念 Linux的内核将所有外部设备都可以看做一个文件来操作。那么我们对与外部设...
    VD2012阅读 1,071评论 0 2
  • 0. 背景 阻塞IO操作通常IO操作(比如read和write)都是阻塞I/O的,当调用read时,如果没有数据收...
    jdzhangxin阅读 1,335评论 0 0
  • select,poll,epoll都是IO多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符,一旦某...
    北山学者阅读 1,923评论 1 4
  • I/O复用模型多路复用是指使用一个线程来检查多个文件描述符(Socket)的就绪状态,比如调用select和pol...
    Catcher07阅读 2,545评论 0 4

友情链接更多精彩内容