Linux_epoll

这篇具有很好参考价值的文章主要介绍了Linux_epoll。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

思考:高效的餐厅服务如何实现?

一个顾客来就餐,一个服务员在顾客点菜期间全程陪同服务!
一个人来就餐,一个服务员去服务,然后客人会看菜单,点菜。 服务员将菜单给后厨。
二个人来就餐,二个服务员去服务……
五个人来就餐,五个服务员去服务……
一百个人呢?
如果你是餐厅的老板,你改怎么解决?
Linux_epoll

Epoll - Reactor 设计模式

Linux_epoll
Linux_epoll

Epoll 与 Reactor 设计模式的关系

Epoll是一种I/O多路复用机制,而Reactor是一种事件驱动的设计模式。它们之间有密切的联系,可以说Epoll是Reactor模式的一种实现方式。

Reactor模式是一种用于处理并发I/O操作的设计模式,其核心思想是将I/O操作分离成两个阶段:事件分发和事件处理。在事件分发阶段,Reactor模式使用一个事件循环来等待事件的发生,并将事件分发给相应的事件处理器。在事件处理阶段,事件处理器执行相应的业务逻辑,从而完成对事件的处理。

Epoll是Linux内核提供的一种I/O多路复用机制,它可以同时监控多个文件描述符,当其中任意一个文件描述符有事件发生时,Epoll会通知应用程序进行相应的处理。在Reactor模式中,事件分发阶段可以使用Epoll来实现,通过将文件描述符注册到Epoll中,等待事件的发生,并将事件通知给相应的事件处理器来处理。

因此,可以说Epoll是Reactor模式的一种实现方式,它提供了高效的I/O事件通知机制,可以帮助应用程序实现高并发、高性能的网络通信。在实际应用中,通常会结合使用Epoll和Reactor模式,以实现更加高效的网络编程。

Reactor优点

1)响应快,不必为单个同步事件所阻塞,虽然Reactor本身依然是同步的;
2)编程相对简单,可以最大程度的避免复杂的多线程及同步问题,并且避免了多线程/进程的切换开销;
3)可扩展性,可以方便的通过增加Reactor实例个数来充分利用CPU资源;
4)可复用性,reactor框架本身与具体事件处理逻辑无关,具有很高的复用性;

Epoll - IO多路复用的用法

  1. 创建EPOLL 句柄(这一步和select、poll都不一样,select与poll都是在while循环中,直接使用select函数、poll函数,不需要创建句柄)
    int epoll_create(int size);

  2. 向EPOLL对象中添加、修改或者删除感兴趣的事件
    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

  • op取值:
    EPOLL_CTL_ADD 添加新的事件到epoll中
    EPOLL_CTL_MOD 修改EPOLL中的事件
    EPOLL_CTL_DEL 删除epoll中的事件

  • events取值:
    EPOLLIN 表示有数据可以读出(接受连接、关闭连接)
    EPOLLOUT 表示连接可以写入数据发送(向服务器发起连接,连接成功事件)
    EPOLLERR 表示对应的连接发生错误
    EPOLLHUP 表示对应的连接被挂起

  1. 收集在epoll监控的事件中已经发生的事件(拿到已就绪的事件
    int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

epfd: epoll的描述符。

events:则是分配好的 epoll_event结构体数组,epoll将会把发生的事件复制到 events数组中(events不可以是空指针,内核只负责把数据复制到这个 events数组中,不会去帮助我们在用户态中分配内存。内核这种做法效率很高)。

maxevents: 本次可以返回的最大事件数目,通常maxevents参数与预分配的events数组的大小是相等的

timeout: 表示在没有检测到事件发生时最多等待的时间(单位为毫秒),如果 timeout为0,立刻返回,不会等待-1表示无限期阻塞

  1. 关键结构:
struct epoll_event{
	__uint32_t  events;
	epoll_data_t data;
};

typedef union epoll_data{
	void *ptr;
	int fd;//该事件的文件描述符
	uint32_t u32;
	uint64_t u64;
}epoll_data_t;

web_server示例代码

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

//    int fd;
typedef struct _ConnectStat ConnectStat;

typedef void (*response_handler)(ConnectStat *stat);

struct _ConnectStat
{
    int fd;
    char name[64];
    char age[64];
    struct epoll_event _ev;
    int status;               // 0 -未登录   1 - 已登陆
    response_handler handler; // 不同页面的处理函数
};

// http协议相关代码
ConnectStat *stat_init(int fd);
void connect_handle(int new_fd);
void do_http_respone(ConnectStat *stat);
void do_http_request(ConnectStat *stat);
void welcome_response_handler(ConnectStat *stat);
void commit_respone_handler(ConnectStat *stat);

const char *main_header = "HTTP/1.0 200 OK\r\nServer: Martin Server\r\nContent-Type: text/html\r\nConnection: Close\r\n";

static int epfd = 0;

void usage(const char *argv)
{
    printf("%s:[ip][port]\n", argv);
}

void set_nonblock(int fd)
{
    int fl = fcntl(fd, F_GETFL);
    fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}

int startup(char *_ip, int _port) // 创建一个套接字,绑定,检测服务器
{
    // sock
    // 1.创建套接字
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0)
    {
        perror("sock");
        exit(2);
    }
    printf("来到端口复用\n");
    // 端口复用
    int opt = 1;
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    // 2.填充本地 sockaddr_in 结构体(设置本地的IP地址和端口)
    struct sockaddr_in local;
    local.sin_port = htons(_port);
    local.sin_family = AF_INET;
    local.sin_addr.s_addr = inet_addr(_ip);
    printf("来到绑定\n");
    // 3.bind()绑定
    if (bind(sock, (struct sockaddr *)&local, sizeof(local)) < 0)
    {
        perror("bind");
        exit(3);
    }
    printf("服务端绑定成功\n");
    // 4.listen()监听 检测服务器
    if (listen(sock, 5) < 0)
    {
        perror("listen");
        exit(4);
    }
    printf("服务端监听成功\n");
    // sleep(1000);
    return sock; // 这样的套接字返回
}

int main(int argc, char *argv[]) // 提供两个参数 ip port
{
    printf("============1================\n");
    if (argc != 3) // 检测参数个数是否正确
    {
        usage(argv[0]);
        exit(1);
    }
    printf("============2================\n");
    int listen_sock = startup(argv[1], atoi(argv[2])); // 创建一个绑定了本地 ip 和端口号的套接字描述符
    printf("============3================\n");
    // 1.创建epoll
    epfd = epoll_create(256); // 可处理的最大句柄数256个
    if (epfd < 0)
    {
        perror("epoll_create");
        exit(5);
    }
    printf("============4================\n");
    struct epoll_event _ev; // epoll结构填充
    ConnectStat *stat = stat_init(listen_sock);
    _ev.events = EPOLLIN; // 初始关心事件为读
    _ev.data.ptr = stat;
    //_ev.data.fd = listen_sock;    //
    printf("============5================\n");
    // 2.托管
    epoll_ctl(epfd, EPOLL_CTL_ADD, listen_sock, &_ev); // 将listen sock添加到epfd中,关心读事件

    struct epoll_event revs[64];

    int timeout = -1;
    int num = 0;
    int done = 0;
    printf("============5================\n");
    while (!done)
    {
        printf("============6================\n");
        // epoll_wait()相当于在检测事件//比如说来了10事件,他会copy到revs里面来,然后你遍历i=0;i<10;i++即可
        switch ((num = epoll_wait(epfd, revs, 64, timeout))) // 返回需要处理的事件数目  64表示 事件有多大
        {
        case 0: // 返回0 ,表示监听超时
            printf("timeout\n");
            printf("============7================\n");
            break;
        case -1: // 出错
            perror("epoll_wait");
            printf("============8================\n");
            break;
        default: // 大于零 即就是返回了需要 处理事件的数目
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            printf("============9================\n");
            int i;
            for (i = 0; i < num; i++)
            {
                ConnectStat *stat = (ConnectStat *)revs[i].data.ptr;

                int rsock = stat->fd;                                    // 准确获取哪个事件的描述符
                if (rsock == listen_sock && (revs[i].events) && EPOLLIN) // 如果是初始的 就接受,建立链接
                {
                    int new_fd = accept(listen_sock, (struct sockaddr *)&peer, &len);

                    if (new_fd > 0)
                    {
                        printf("get a new client:%s:%d\n", inet_ntoa(peer.sin_addr), ntohs(peer.sin_port));
                        // sleep(1000);
                        connect_handle(new_fd);
                    }
                }
                else // 接下来对num - 1 个事件处理
                {
                    if (revs[i].events & EPOLLIN)
                    {
                        do_http_request((ConnectStat *)revs[i].data.ptr);
                    }
                    else if (revs[i].events & EPOLLOUT)
                    {
                        do_http_respone((ConnectStat *)revs[i].data.ptr);
                    }
                    else
                    {
                    }
                }
            }
        }
        break;
        } // end switch
    }     // end while
    return 0;
}

ConnectStat *stat_init(int fd)
{
    ConnectStat *temp = NULL;
    temp = (ConnectStat *)malloc(sizeof(ConnectStat));

    if (!temp)
    {
        fprintf(stderr, "malloc failed. reason: %m\n");
        return NULL;
    }

    memset(temp, '\0', sizeof(ConnectStat));
    temp->fd = fd;
    temp->status = 0;
    // temp->handler = welcome_response_handler;
}

// 初始化连接,然后等待浏览器发送请求
void connect_handle(int new_fd)
{
    ConnectStat *stat = stat_init(new_fd);
    set_nonblock(new_fd);

    stat->_ev.events = EPOLLIN;
    stat->_ev.data.ptr = stat;

    epoll_ctl(epfd, EPOLL_CTL_ADD, new_fd, &stat->_ev); // 二次托管
}

void do_http_respone(ConnectStat *stat)
{
    stat->handler(stat);
}

void do_http_request(ConnectStat *stat)
{

    // 读取和解析http 请求
    char buf[4096];
    char *pos = NULL;
    // while  header \r\n\r\ndata
    ssize_t _s = read(stat->fd, buf, sizeof(buf) - 1);
    if (_s > 0)
    {
        buf[_s] = '\0';
        printf("receive from client:%s\n", buf);

        pos = buf;

        // Demo 仅仅演示效果,不做详细的协议解析
        if (!strncasecmp(pos, "GET", 3))
        {
            stat->handler = welcome_response_handler;
        }
        else if (!strncasecmp(pos, "Post", 4))
        {
            // 获取 uri
            printf("---Post----\n");
            pos += strlen("Post");
            while (*pos == ' ' || *pos == '/')
                ++pos;

            if (!strncasecmp(pos, "commit", 6))
            { // 获取名字和年龄
                int len = 0;

                printf("post commit --------\n");
                pos = strstr(buf, "\r\n\r\n");
                char *end = NULL;
                if (end = strstr(pos, "name="))
                {
                    pos = end + strlen("name=");
                    end = pos;
                    while (('a' <= *end && *end <= 'z') || ('A' <= *end && *end <= 'Z') || ('0' <= *end && *end <= '9'))
                        end++;
                    len = end - pos;
                    if (len > 0)
                    {
                        memcpy(stat->name, pos, end - pos);
                        stat->name[len] = '\0';
                    }
                }

                if (end = strstr(pos, "age="))
                {
                    pos = end + strlen("age=");
                    end = pos;
                    while ('0' <= *end && *end <= '9')
                        end++;
                    len = end - pos;
                    if (len > 0)
                    {
                        memcpy(stat->age, pos, end - pos);
                        stat->age[len] = '\0';
                    }
                }
                stat->handler = commit_respone_handler;
            }
            else
            {
                stat->handler = welcome_response_handler;
            }
        }
        else
        {
            stat->handler = welcome_response_handler;
        }

        // 生成处理结果 html ,write

        stat->_ev.events = EPOLLOUT;
        // stat->_ev.data.ptr = stat;
        epoll_ctl(epfd, EPOLL_CTL_MOD, stat->fd, &stat->_ev); // 二次托管
    }
    else if (_s == 0) // client:close
    {
        printf("client: %d close\n", stat->fd);
        epoll_ctl(epfd, EPOLL_CTL_DEL, stat->fd, NULL);
        close(stat->fd);
        free(stat);
    }
    else
    {
        perror("read");
    }
}

void welcome_response_handler(ConnectStat *stat)
{
    const char *welcome_content = "\
<html lang=\"zh-CN\">\n\
<head>\n\
<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">\n\
<title>This is a test</title>\n\
</head>\n\
<body>\n\
<div align=center height=\"500px\" >\n\
<br/><br/><br/>\n\
<h2>大家好,欢迎来到奇牛学院VIP 课!</h2><br/><br/>\n\
<form action=\"commit\" method=\"post\">\n\
尊姓大名: <input type=\"text\" name=\"name\" />\n\
<br/>芳龄几何: <input type=\"password\" name=\"age\" />\n\
<br/><br/><br/><input type=\"submit\" value=\"提交\" />\n\
<input type=\"reset\" value=\"重置\" />\n\
</form>\n\
</div>\n\
</body>\n\
</html>";

    char sendbuffer[4096];
    char content_len[64];

    strcpy(sendbuffer, main_header);
    snprintf(content_len, 64, "Content-Length: %d\r\n\r\n", (int)strlen(welcome_content));
    strcat(sendbuffer, content_len);
    strcat(sendbuffer, welcome_content);
    printf("send reply to client \n%s", sendbuffer);

    write(stat->fd, sendbuffer, strlen(sendbuffer));

    stat->_ev.events = EPOLLIN;
    // stat->_ev.data.ptr = stat;
    epoll_ctl(epfd, EPOLL_CTL_MOD, stat->fd, &stat->_ev);
}

void commit_respone_handler(ConnectStat *stat)
{
    const char *commit_content = "\
<html lang=\"zh-CN\">\n\
<head>\n\
<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">\n\
<title>This is a test</title>\n\
</head>\n\
<body>\n\
<div align=center height=\"500px\" >\n\
<br/><br/><br/>\n\
<h2>欢迎学霸同学&nbsp;%s &nbsp;,你的芳龄是&nbsp;%s!</h2><br/><br/>\n\
</div>\n\
</body>\n\
</html>\n";

    char sendbuffer[4096];
    char content[4096];
    char content_len[64];
    int len = 0;

    len = snprintf(content, 4096, commit_content, stat->name, stat->age);
    strcpy(sendbuffer, main_header);
    snprintf(content_len, 64, "Content-Length: %d\r\n\r\n", len);
    strcat(sendbuffer, content_len);
    strcat(sendbuffer, content);
    printf("send reply to client \n%s", sendbuffer);

    write(stat->fd, sendbuffer, strlen(sendbuffer));

    stat->_ev.events = EPOLLIN;
    // stat->_ev.data.ptr = stat;
    epoll_ctl(epfd, EPOLL_CTL_MOD, stat->fd, &stat->_ev);
}

/*
sudo gcc epoll_web_server.c -o epoll_web_server
./epoll_web_server 192.168.52.128 8888
http://192.168.52.128:8888/

*/

水平触发和边缘触发

  • Redis 水平触发

  • Nginx 边缘触发

  • Level_triggered(水平触发):当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据一次性全部读写完(如读写缓冲区太小),那么下次调用 epoll_wait()时,它还会通知你在上没读写完的文件描述符上继续读写,当然如果你一直不去读写,它会一直通知你!!!如果系统中有大量你不需要读写的就绪文件描述符,而它们每次都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率!!!
    设置方式: 默认即水平触发

  • Edge_triggered(边缘触发):当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你!!!这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符!!!
    设置方式: stat->_ev.events = EPOLLIN | EPOLLET

  • 如果你不设置,默认就是水平触发文章来源地址https://www.toymoban.com/news/detail-474639.html

到了这里,关于Linux_epoll的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Linux网络编程:多路I/O转接服务器(select poll epoll)

    文章目录: 一:select 1.基础API  select函数 思路分析 select优缺点 2.server.c 3.client.c 二:poll 1.基础API  poll函数  poll优缺点 read函数返回值 突破1024 文件描述符限制 2.server.c 3.client.c 三:epoll 1.基础API epoll_create创建   epoll_ctl操作  epoll_wait阻塞 epoll实现多路IO转接思路 epoll优缺点

    2024年02月11日
    浏览(53)
  • 五、Linux C/C++ 对epoll-reactor服务器的百万级高并发实现

    前言:基于epoll的反应堆模式(reactor)的服务器程序,进行百万并发量的连接测试。通过代码优化,以及服务器与客户端的硬件配置优化,达到百万并发。 代码实现 代码实现: 1台服务器:8G运行内存 8核CPU 3台客户端:4G运行内存 4核CPU 这些硬件配置可以通过虚拟机配置。 按照

    2024年02月20日
    浏览(89)
  • day-08 基于Linux的网络编程(套接字和标准I/O、分离I/O流、epoll、多线程服务器)

    标准I/O函数(stdio)是在C语言中用于进行输入和输出操作的库函数 。它们包括了一组标准的输入和输出函数,如printf、scanf、fopen、fclose等。标准I/O函数具有以下优点: 简单易用 :标准I/O函数提供了简洁的接口,使得输入和输出操作变得简单易用。开发人员无需自行处理底层

    2024年02月09日
    浏览(62)
  • 游戏思考26:游戏服务器压力测试文档(新增linux相关命令,02/10未完待续)

    ①流量 ②内存 ③一些主要的功能才做压力测试,比如 同时注册 , 最大在线 , 战斗 , 地图移动 , 数据存取 等。 ④2个压力宏观数据保持不变: a. 各接口的压力比例不变 , 首先从同类型游戏或者本游戏内测阶段,日志插桩,收集各个接口的调用比例;然后,将接口比例

    2024年02月01日
    浏览(48)
  • 利用 ChatGPT 高效搜索:举一反三的思考方式,高效查找解决方案

    本文只是我的一些尝试,基于 ChatGPT 实现系统化快速搜索某编程语言的特定领域相关包或者基于其他语言类推荐落地方案的尝试。 这篇文章中描述的方式不一定是好方式,但应该会有一定的启示作用吧。让 ChatGPT 为我们的开发效率添砖加瓦。 在学习和使用一门新的编程语言

    2024年01月18日
    浏览(44)
  • EMO:重新思考高效的基于注意力的移动块模型

    论文链接:https://arxiv.org/pdf/2301.01146.pdf 本文的重点是在权衡参数、FLOPs和性能的同时,为密集预测开发现代、高效、轻量级的模型。倒立残差块(IRB)是轻量级CNN的基础结构#

    2024年02月15日
    浏览(37)
  • Linux之epoll理解

    IO多路复用有几种实现方式:select poll和epoll。本篇文章对epoll进行总结理解。 IO多路复用的含义,我个人的理解是通过一个线程实现对多个socket的侦听,epoll与select和poll的区别是epoll效率最高。 select的最高管理1024个socket并且是通过轮询的方式实现的管理,管理的socket个数越多

    2024年02月07日
    浏览(23)
  • linux--epoll

    参考文献 https://www.cnblogs.com/lojunren/p/3856290.html https://www.51cto.com/article/717096.html linux下的I/O复用epoll详解 要深刻理解epoll,首先得了解epoll的三大关键要素:mmap、红黑树、链表。 首先需要了解什么是IO多路复用 IO多路复用是一种同步的IO模型。利用IO多路复用模型,可以实现一个

    2024年02月12日
    浏览(17)
  • Linux_epoll

    一个顾客来就餐,一个服务员在顾客点菜期间全程陪同服务! 一个人来就餐,一个服务员去服务,然后客人会看菜单,点菜。 服务员将菜单给后厨。 二个人来就餐,二个服务员去服务…… 五个人来就餐,五个服务员去服务…… 一百个人呢? 如果你是餐厅的老板,你改怎么

    2024年02月08日
    浏览(17)
  • 【Linux】多路转接 -- epoll

    epoll系统调用和select以及poll是一样的,都是可以让我们的程序同时监视多个文件描述符上的事件是否就绪。 epoll在命名上比poll多了一个poll,这个e可以理解为extend, epoll就是为了同时处理大量文件描述符而改进的poll。 epoll在2.5.44内核中被引进,它几乎具备了select和poll的所有

    2024年02月14日
    浏览(53)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包