poll使用介绍 - 简单服务器

20 篇文章 0 订阅
3 篇文章 0 订阅

IO模型 - Poll

Linux系统提供了select, poll, epoll三种更加高效的IO模型,这里使用一个例子简单介绍一下poll在套接字上的使用。

传统的阻塞读写方式需要给每个连接分配一个线程,读取到一个请求后就响应,之后再读取,若客户端没有发送请求数据,则读取操作会阻塞。这样的方式显然浪费了系统资源,并且连接过多时线程数的增加也会给系统带来较大负担。

基于事件的IO模型允许服务端使用事件触发的方式,在客户端发送请求时触发读取read操作,并在写操作write不会阻塞的时候写数据。这种IO模型允许使用更少的线程处理更多的客户端连接,能够降低系统负担,提高应用的服务效率。

如何使用

简单明了:

/* ... 套接字创建 ... */
int sd = socket(AF_INET, SOCK_STREAM, 0);
bind(...);
listen(...); // sd 上的POLLIN事件表示可以accept
//---------------------------------------------

// pollfd: {fd, events, revents} -> {文件描述符,感兴趣事件(bitmask),返回的事件}
struct pollfd *fds = (struct pollfd *)(calloc(100, sizeof(pollfd)));
// 设置监听的套接字,关心的事件
fds[0].fd = sd, fds[0].events = POLLIN;
// 需要监听的pollfd数目
nfds_t nfds = 1;
// 超时值: -1 表示一直阻塞
int timeout = -1;
// 返回值: -1 错误,0 没有事件发送,>0 发生事件的描述符个数,事件设置在revents中
ret = poll(fds, nfds, timeout);

// 事件的值有 POLLIN POLLOUT POLLHUP 等,参考poll(2)

在这里插入图片描述

TCP服务器响应流程

对于TCP服务器来说,bind+listen+accept然后处理客户端的连接是必不可少的,不过在使用poll的时候,accept与客户端的read+write都可以在事件触发后执行,客户端连接需要设置为非阻塞的,避免read和write的阻塞,大致流程如下:

  1. socket() + bind() + listen()创建套接字sd

  2. sd加入到poll的描述符集fds中,并且监听上面的POLLIN事件

  3. 调用poll()等待描述符集中的事件

    1. fds[0].revents & POLLIN,则表示客户端请求建立连接

      • 调用accept接收请求得到新连接childSd,设置新连接时非阻塞的fcntl(childSd, F_SETFL, O_NONBLOCK)

      • childSd加入到poll的描述符集中,监听其上的POLLIN事件: fds[i].events = POLLIN

    2. 若其他套接字tmpSd上有POLLIN事件,表示客户端发送请求数据

      • 读取数据,若读取完则监听tmpSd上的读和写事件: fds[j].events = POLLIN | POLLOUT

        读取遇到EAGAIN | EWOULDBLOCK,表示会阻塞,需要停止读等待下一次读事件
        若read返回0(EOF),则表示连接已断开

      • 否则,记录这次读取的数据,下一个读事件时继续执行读操作

    3. 若其他套接字tmpSd上有POLLOUT事件,表示客户端可写

      • 写入数据,若写入完,则清除tmpSd上的写事件

        同样,写如遇到EAGAIN | EWOULDBLOCK,表示会阻塞,需要停止写等待下一次写事件

      • 否则,下次写事件继续写

由于套接字上写事件一般都是可行的,所以初始不监听POLLOUT事件,否则poll会不停报道套接字上可写。

例子

#if !defined(POLL_TEST_H)
#define POLL_TEST_H
#include <unistd.h>
#include <fcntl.h>
#include <poll.h>
#include <time.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <cstdio>
#include <cstdlib>
#include <errno.h>
#include <cstring>

using std::initializer_list;
using std::vector;
/*

accept - POLLIN

对于客户端连接
    1. 只接受读事件 - POLLIN
    2. 读取到一个请求后,可以读和写 - POLLIN | POLLOUT
    3. 写完一个响应
        1. 如果有待写响应 - 不变
        2. 没有待写则,只允许读 - POLLIN

*/

const char resp[] = "HTTP/1.1 200\r\n\
Content-Type: application/json\r\n\
Content-Length: 13\r\n\
Date: Thu, 13 Aug 2020 08:02:00 GMT\r\n\
Keep-Alive: timeout=60\r\n\
Connection: keep-alive\r\n\r\n\
[HELLO WORLD]\r\n\r\n";

void workPollTest() {
    // ----------------------------------- 创建套接字
    const int port = 2333;
    int sd, ret;
    sd = socket(AF_INET, SOCK_STREAM, 0);
    fprintf(stderr, "created socket\n");
    if (sd == -1)
        errExit();
    int opt = 1;
    // 重用地址
    if (setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int)) == -1)
        errExit();
    fprintf(stderr, "socket opt set\n");
    sockaddr_in addr;
    addr.sin_family = AF_INET, addr.sin_port = htons(port);
    addr.sin_addr.s_addr = INADDR_ANY;
    socklen_t addrLen = sizeof(addr);
    if (bind(sd, (sockaddr *)&addr, sizeof(addr)) == -1)
        errExit();
    fprintf(stderr, "socket binded\n");
    if (listen(sd, 1024) == -1)
        errExit();
    fprintf(stderr, "socket listen start\n");
    // ----------------------------------- 套接字创建完毕

    // -----------------------------------初始化监听列表
    // number of poll fds
    int currentFdNum = 1;
    pollfd *fds = static_cast<pollfd *>(calloc(100, sizeof(pollfd)));
    fds[0].fd = sd, fds[0].events = POLLIN;
    nfds_t nfds = 1;
    int timeout = -1;

    fprintf(stderr, "polling\n");
    while (1) {
        // ----------------------------------- 执行poll操作
        ret = poll(fds, nfds, timeout);
        fprintf(stderr, "poll returned with ret value: %d\n", ret);
        if (ret == -1)
            errExit();
        else if (ret == 0) {
            fprintf(stderr, "return no data\n");
        } else { // ret > 0
            // got accept
            fprintf(stderr, "checking fds\n");
            // ----------------------------------- 检查是否有新客户端建立连接
            if (fds[0].revents & POLLIN) {
                sockaddr_in childAddr;
                socklen_t childAddrLen;
                int childSd = accept(sd, (sockaddr *)&childAddr, &(childAddrLen));
                if (childSd == -1)
                    errExit();
                fprintf(stderr, "child got\n");
                // set non_block
                int flags = fcntl(childSd, F_GETFL);
                // ----------------------------------- accept并设置为非阻塞
                if (fcntl(childSd, F_SETFL, flags | O_NONBLOCK) == -1)
                    errExit();
                fprintf(stderr, "child set nonblock\n");
                // add child to list
                // ----------------------------------- 假如到poll的描述符集,关心POLLIN事件
                fds[currentFdNum].fd = childSd, fds[currentFdNum].events = (POLLIN | POLLRDHUP);
                nfds++, currentFdNum++;
                fprintf(stderr, "child: %d pushed to poll list\n", currentFdNum - 1);
            }
            // child read & write
            // ----------------------------------- 检查其他描述符的事件
            for (int i = 1; i < currentFdNum; i++) {
                if (fds[i].revents & (POLLHUP | POLLRDHUP | POLLNVAL)) {
                    // ----------------------------------- 客户端描述符关闭
                    // ----------------------------------- 设置events=0, fd=-1,不再关心
                    // set not interested
                    fprintf(stderr, "child: %d shutdown\n", i);
                    close(fds[i].fd);
                    fds[i].events = 0;
                    fds[i].fd = -1;
                    continue;
                }
                //  read
                if (fds[i].revents & POLLIN) {
                    char buffer[1024] = {};
                    while (1) {
                        // ----------------------------------- 读取请求数据
                        ret = read(fds[i].fd, buffer, 1024);
                        fprintf(stderr, "read on: %d returned with value: %d\n", i, ret);
                        if (ret == 0) {
                            fprintf(stderr, "read returned 0(EOF) on: %d, breaking\n", i);
                            break;
                        }
                        if (ret == -1) {
                            const int tmpErrno = errno;
                            // ----------------------------------- 会阻塞,这里认为读取完毕
                            // ----------------------------------- 实际需要检查读取数据是否完毕
                            if (tmpErrno == EWOULDBLOCK || tmpErrno == EAGAIN) {
                                fprintf(stderr, "read would block, stop reading\n");
                                // read is over
                                // http pipe line? need to put resp into a queue
                                // ----------------------------------- 可以监听写事件了 POLLOUT
                                fds[i].events |= POLLOUT;
                                break;
                            } else {
                                errExit();
                            }
                        }
                    }
                }
                // write
                if (fds[i].revents & POLLOUT) {
                    // ----------------------------------- 写事件,把请求返回
                    ret = write(fds[i].fd, resp, sizeof(resp));
                    fprintf(stderr, "write on: %d returned with value: %d\n", i, ret);
                    // ----------------------------------- 这里需要处理 EAGAIN EWOULDBLOCK
                    if (ret == -1) {
                        errExit();
                    }
                    fds[i].events &= !(POLLOUT);
                }
            }
        }
    }
}

#endif // POLL_TEST_H

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是一个简单的应用层poll使用例子: ```c #include <stdio.h> #include <stdlib.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <poll.h> #define MAX_CLIENTS 10 #define BUFFER_SIZE 1024 int main(int argc, char* argv[]) { // 创建socket int server_fd = socket(AF_INET, SOCK_STREAM, 0); if (server_fd < 0) { perror("socket"); return -1; } // 绑定地址和端口 struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(8888); addr.sin_addr.s_addr = INADDR_ANY; if (bind(server_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { perror("bind"); return -1; } // 监听端口 if (listen(server_fd, 5) < 0) { perror("listen"); return -1; } // 创建pollfd数组 struct pollfd fds[MAX_CLIENTS + 1]; for (int i = 0; i < MAX_CLIENTS + 1; i++) { fds[i].fd = -1; fds[i].events = POLLIN; } fds[0].fd = server_fd; while (1) { // 使用poll等待读取事件 int ret = poll(fds, MAX_CLIENTS + 1, -1); if (ret < 0) { perror("poll"); break; } // 处理服务器socket的读取事件 if (fds[0].revents & POLLIN) { int client_fd = accept(server_fd, NULL, NULL); if (client_fd < 0) { perror("accept"); continue; } // 将新客户端加入pollfd数组 int i; for (i = 1; i < MAX_CLIENTS + 1; i++) { if (fds[i].fd == -1) { fds[i].fd = client_fd; break; } } if (i == MAX_CLIENTS + 1) { close(client_fd); printf("Too many clients, connection rejected.\n"); } else { printf("New client connected: %d\n", client_fd); } } // 处理客户端socket的读取事件 for (int i = 1; i < MAX_CLIENTS + 1; i++) { if (fds[i].fd == -1) { continue; } if (fds[i].revents & POLLIN) { char buffer[BUFFER_SIZE]; int n = recv(fds[i].fd, buffer, BUFFER_SIZE - 1, 0); if (n < 0) { perror("recv"); close(fds[i].fd); fds[i].fd = -1; } else if (n == 0) { printf("Client disconnected: %d\n", fds[i].fd); close(fds[i].fd); fds[i].fd = -1; } else { buffer[n] = '\0'; printf("Received from client %d: %s", fds[i].fd, buffer); } } } } // 关闭socket close(server_fd); return 0; } ``` 这个例子使用poll来同时处理多个客户端的socket读取事件。在主循环中,poll等待读取事件,然后处理服务器socket的读取事件和客户端socket的读取事件。如果有新客户端连接,将其加入pollfd数组中。如果客户端断开连接或者出现错误,将其从pollfd数组中删除。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值