解析Linux源码之epoll

一、前言

在本篇文章中,我们将深入探究Linux内核源码中的网络编程模型epoll。

首先,我们对epoll的整体结构进行说明。其次,我们将分析epoll的实现机制,包括epoll的两个核心数据结构以及相关操作的实现。最后,我们将结合示例代码对epoll的使用进行说明。

二、整体结构

在Linux内核源码中,epoll的实现分为多个文件,并被封装在一个名为epoll.c的文件中。其中,主要的模块包括:

  1. epoll_create和epoll_create1函数的实现

  2. epoll_ctl函数的实现,包括增加、删除和修改文件描述符

  3. epoll_wait函数的实现,等待I/O事件发生并返回就绪文件描述符

在epoll实现中,有两个核心的数据结构:

  1. epoll_event,代表一个I/O事件

  2. epitem,代表epoll中的一个文件描述符项

三、实现机制

  1. epoll_create和epoll_create1函数的实现

epoll_create和epoll_create1函数用于创建一个epoll句柄。它们的实现非常简单,可以直接参考系统调用实现文档。

  1. epoll_ctl函数的实现

epoll_ctl用于给epoll句柄中添加、修改或删除一个文件描述符。当添加或修改文件描述符时,我们需要将其加入到监听队列中,并设置对应的I/O事件类型。具体实现如下:

static int ep_insert(struct epitem *epi) {
    struct epoll_event e;
    e.events = epi->event.events;
    e.data.u32 = epi->handout->id;
    if (epi->event.events & (EPOLLRDHUP | EPOLLHUP)) {
        e.events |= EPOLLIN | EPOLLRDNORM | EPOLLRDBAND | EPOLLINIGNEOF |
                    EPOLLOUT | EPOLLWRNORM | EPOLLWRBAND;
    }
    return rb_insert(&epi->node, &ep.ep_events, ep_rb_cmp) != NULL;
}

在epi中,我们设置了要监听的I/O事件类型。然后,我们将其加入到epoll中的监听队列中。

  1. epoll_wait函数的实现

epoll_wait用于等待就绪文件描述符。在等待过程中,我们需要从epoll监听队列中找到已经就绪的文件描述符。具体实现如下:

static int ep_item_poll(struct epitem *epi, struct epoll_event *events,
                        int maxevents, int *timeout, bool epoll_wait) {
    // ...
    if (epi->nwait == epi->nready) {
        ep_remove(epi);
        // ...
        return 0;
    }
    // ...
    epi->nwait++;
    if (ep_waitqueue(&epi->wait, &e)) {
        epi->nwait--;
        ep_remove(epi);
        // ...
        return 0;
    }
    // ...
}

在epi中,我们记录了当前关注的I/O事件类型和状态。当等待过程中监听到就绪文件描述符时,我们将其移除。如果没有找到就绪的文件描述符,则传递超时值以等待就绪。

四、示例说明

现在,我们来看两个示例,以更好地理解epoll的使用。

第一个示例:简单使用epoll判断TCP连接是否关闭

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define MAX_EVENTS 10

int main(int argc, char *argv[]) {
    int sfd, efd;
    struct sockaddr_in listen_addr;
    struct epoll_event event;
    struct epoll_event events[MAX_EVENTS];

    listen_addr.sin_family = AF_INET;
    listen_addr.sin_port = htons(8080);
    listen_addr.sin_addr.s_addr = INADDR_ANY;

    sfd = socket(AF_INET, SOCK_STREAM, 0);
    bind(sfd, (struct sockaddr*)&listen_addr, sizeof(listen_addr));
    listen(sfd, 10);

    efd = epoll_create1(0);
    event.data.fd = sfd;
    event.events = EPOLLIN | EPOLLRDHUP;
    epoll_ctl(efd, EPOLL_CTL_ADD, sfd, &event);

    while (1) {
        int n = epoll_wait(efd, events, MAX_EVENTS, -1);
        for (int i = 0; i < n; i++) {
            if (events[i].data.fd == sfd) { // 有新的连接
                struct sockaddr_in client_addr;
                socklen_t client_len = sizeof(client_addr);
                int cfd = accept(sfd, (struct sockaddr*)&client_addr, &client_len);

                event.data.fd = cfd;
                event.events = EPOLLIN | EPOLLRDHUP;
                epoll_ctl(efd, EPOLL_CTL_ADD, cfd, &event);
            } else { // 接受或关闭连接
                if (events[i].events & EPOLLHUP || events[i].events & EPOLLRDHUP) {
                    // 关闭连接
                    close(events[i].data.fd);
                } else if (events[i].events & EPOLLIN) {
                    // 读取数据
                    char buf[1024];
                    int n = 0;
                    do {
                        n = read(events[i].data.fd, buf, 1024);
                        if (n > 0) { // 成功接受到数据
                            printf("Received %d bytes from %d\n", n, events[i].data.fd);
                        }
                    } while (n == 1024);
                }
            }
        }
    }
}

第二个示例:使用epoll来提高性能,支持更多连接

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>

#define MAX_EVENTS 10000

static int set_nonblock(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    if (flags == -1) {
        return -1;
    }
    flags |= O_NONBLOCK;
    if (fcntl(fd, F_SETFL, flags) == -1) {
        return -1;
    }
    return 0;
}

int main(int argc, char* argv[]) {
    int sfd, sfd1, efd, res;
    struct sockaddr_in sa;
    struct epoll_event event;
    struct epoll_event events[MAX_EVENTS];
    int i;

    if ((sfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
        perror("socket()");
        exit(EXIT_FAILURE);
    }

    set_nonblock(sfd);

    memset(&sa, 0, sizeof(sa));
    sa.sin_family = AF_INET;
    sa.sin_port = htons(9000);
    sa.sin_addr.s_addr = htonl(INADDR_ANY);

    if (bind(sfd, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
        perror("bind()");
        exit(EXIT_FAILURE);
    }

    if (listen(sfd, 5) == -1) {
        perror("listen()");
        exit(EXIT_FAILURE);
    }

    if ((efd = epoll_create1(0)) == -1) {
        perror("epoll_create1()");
        exit(EXIT_FAILURE);
    }

    update_epoll(efd, sfd, EPOLLIN | EPOLLET);
    while (1) {
        int n = epoll_wait(efd, events, MAX_EVENTS, 150000);
        if (n == -1 && errno != EINTR) {
            perror("epoll_pwait()");
            exit(EXIT_FAILURE);
        }

        for (i = 0; i < n; i++) {
            if ((events[i].events & EPOLLERR) ||
                    (events[i].events & EPOLLHUP) ||
                    (!(events[i].events & (EPOLLIN | EPOLLOUT)))) {
                close(events[i].data.fd);
                continue;
            }

            if (events[i].data.fd == sfd) {
                while (1) {
                    sfd1 = accept(sfd, NULL, NULL);
                    if (sfd1 == -1) {
                        break;
                    }
                    update_epoll(efd, sfd1, EPOLLIN | EPOLLET);
                }
                if (errno != EAGAIN && errno != EWOULDBLOCK) {
                    perror("accept()");
                    exit(-1);
                }
                continue;
            }

            do_use_fd(events[i].data.fd);
        }
    }

    return 0;
}

以上就是epoll的解析攻略的完整内容,希望能对你有所帮助!

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:解析Linux源码之epoll - Python技术站

(0)
上一篇 2023年5月22日
下一篇 2023年5月22日

相关文章

  • redis启动错误: Warning: no config file specified, using the default config. In order to specify a config

      windows下redis启动时遇到错误: D:\redis>redis-server.exe[13732] 29 Nov 11:35:57.446 # Warning: no config file specified, using the default config. In order to specify a config file use…

    Redis 2023年4月12日
    00
  • Linux下执行java程序的方法

    Linux下执行Java程序,需要遵从以下步骤: 1. 安装Java环境 首先需要在Linux系统上安装Java环境,可以运行java -version命令检查当前系统是否已经安装了Java环境。如果没有安装,可以按照以下步骤进行安装: 下载Java SE Development Kit (JDK),建议从Oracle官网下载最新版本: https://ww…

    database 2023年5月22日
    00
  • Mysql数据库性能优化之子查询

    Mysql数据库性能优化之子查询 什么是子查询? 以一个完整的 SELECT 语句为基础,嵌套一个子 SELECT 语句,这个子 SELECT 语句被用作基础 SELECT 语句中的一个条件或表达式,就叫做子查询。 子查询可以出现在 SELECT、FROM、WHERE、HAVING、SET 和 VALUES 等子句中,常见的有 exists、IN 和子查询作…

    database 2023年5月19日
    00
  • MySQL慢查询以及重构查询的方式记录

    MySQL慢查询是指执行时间较长的SQL语句,这些语句会对MySQL的性能产生比较明显的影响。因此,了解MySQL慢查询的原因以及如何进行重构查询是非常重要的。 什么是MySQL慢查询? 在MySQL数据库中,当一个查询语句执行时间超过一定阈值(通常为1秒)时,就会被称为慢查询。慢查询会对MySQL的性能和用户体验产生影响,因此需要对其进行优化。 通常会使用…

    database 2023年5月19日
    00
  • 如何使用Pycharm连接SQL Sever(详细教程)

    下面是使用Pycharm连接SQL Sever的详细教程: 1. 下载安装Pycharm 首先,您需要在官网上下载并安装Pycharm。Pycharm是一款功能强大的Python IDE,包括智能代码编写、代码调试、版本控制等多种功能。您可以在该网站上下载适合您系统版本的Pycharm: https://www.jetbrains.com/pycharm/d…

    database 2023年5月21日
    00
  • Oracle 12.2监听无法启动解决方法

    为了解决Oracle 12.2监听无法启动的问题,需要按照以下步骤进行操作: 确认监听进程是否在运行 在解决问题之前,先要确认是否存在监听进程。可以通过执行以下命令来检查监听进程: ps -ef | grep tns 如果没有监听进程运行,需要执行以下步骤来启动监听进程。 启动监听进程 检查“listener.ora”文件的配置 在运行监听进程之前,需要确保…

    database 2023年5月22日
    00
  • mysql中is null语句的用法分享

    当我们需要查询某个字段的值是否为 null 时,可以使用 MySQL 中的 is null 语句进行查询。 具体用法如下: SELECT * FROM table_name WHERE column_name IS NULL; 在此语句中,我们使用了 SELECT 语句来选择表格中所有的列,然后使用 WHERE 语句来筛选出其中的行。 而在 WHERE 语句…

    database 2023年5月22日
    00
  • Linux下编译安装MySQL-Python教程

    以下是“Linux下编译安装MySQL-Python教程”的完整攻略: 1. 准备 在开始编译安装MySQL-Python之前,我们需要确保已经安装了MySQL和Python以及开发所需的相关依赖库。 在Linux终端执行以下命令安装MySQL和Python: sudo apt-get install mysql-server mysql-client py…

    database 2023年5月22日
    00
合作推广
合作推广
分享本页
返回顶部