使用 epoll 的全面指南

一、什么是 epoll?

Epoll 是 Linux 下一种可以处理大量文件描述符的 I/O 操作的机制,它可以在一个线程中监视多个文件描述符,当某个文件描述符就绪时,可以立即通知程序进行相应的操作。它采用了一种基于回调的事件驱动机制,使 I/O 处理效率更高。

二、使用 epoll 的优点

1、高效性:Epoll 采用了事件驱动机制,仅当有数据就绪时才会加以处理,避免了无效的等待。同时,它使用红黑树来存放文件描述符,所以当文件描述符数量较大时,查询的效率比 select 和 poll 更高。

2、可扩展性:在实际使用中,我们可以通过配置参数来调整 epoll 实例的大小,从而实现更精细的控制。

3、线程安全:在多线程程序中,使用 epoll 监听文件描述符的读写事件时,每个线程都会有自己的存储空间,避免了竞争条件。

三、使用 epoll 的实例

1、epoll 实现 TCP 服务器

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

#define LISTEN_BACKLOG 50
#define MAX_EPOLL_EVENTS 50

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

int main(int argc, char **argv) {
    struct sockaddr_in my_addr, client_addr;
    int listener, new_fd, epfd, nfds, n, i;
    char buf[BUFSIZ];
    socklen_t client_len = sizeof(client_addr);
    struct epoll_event ev, events[MAX_EPOLL_EVENTS];
    epfd = epoll_create(50);

    memset(&my_addr, 0, sizeof(struct sockaddr));
    my_addr.sin_family = AF_INET;
    my_addr.sin_port = htons(7788);
    my_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    if ((listener = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket create failed");
        exit(1);
    }

    int opt = 1;
    setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    if (bind(listener, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) {
        perror("bind failed!");
        exit(1);
    }
    set_nonblocking(listener);
    if (listen(listener, LISTEN_BACKLOG) == -1) {
        perror("listen failed!");
        exit(1);
    }
    ev.events = EPOLLIN | EPOLLET;
    ev.data.fd = listener;
    epoll_ctl(epfd, EPOLL_CTL_ADD, listener, &ev);

    while (1) {
        nfds = epoll_wait(epfd, events, MAX_EPOLL_EVENTS, -1);
        if (nfds == -1) {
            perror("epoll_wait error!");
            exit(1);
        }
        for (i = 0; i < nfds; i++) {
            if (events[i].data.fd == listener) {
                while ((new_fd = accept(listener, (struct sockaddr *)&client_addr, &client_len)) != -1) {
                    set_nonblocking(new_fd);
                    ev.events = EPOLLIN | EPOLLET;
                    ev.data.fd = new_fd;
                    epoll_ctl(epfd, EPOLL_CTL_ADD, new_fd, &ev);
                }
                if (errno != EAGAIN && errno != ECONNABORTED && errno != EPROTO && errno != EINTR) {
                    perror("accept error!");
                }
            } else if (events[i].events & EPOLLIN) {
                while ((n = read(events[i].data.fd, buf, BUFSIZ)) > 0) {
                    fprintf(stdout, "Data received: %s\n", buf);
                }
                if (n == 0) {
                    fprintf(stdout, "Client closed by peer, fd: %d\n", events[i].data.fd);
                    close(events[i].data.fd);
                    epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, NULL);
                } else if (n == -1 && errno != EAGAIN) {
                    perror("read error!");
                    close(events[i].data.fd);
                    epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, NULL);
                }
            }
        }
    }
    close(listener);
    return 0;
}

代码实现了基于 epoll 的 TCP 服务器,其中设置了底层监听端口,并通过 accept 接受连接,设置每个客户端连接的事件类型,处理客户端的读取事件。它使用 epoll_wait 函数监听客户端连接状态,当客户端连接时,创建新的文件描述符并加入 epoll 的监听队列,当客户端发来数据时,读取数据并在控制台上输出。

2、epoll 实现 UDP 服务器

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

#define LISTEN_BACKLOG 50
#define MAX_EPOLL_EVENTS 50

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

int main(int argc, char **argv) {
    struct sockaddr_in my_addr, client_addr;
    int listener, new_fd, epfd, nfds, n, i, len;
    char buf[BUFSIZ];
    socklen_t client_len = sizeof(client_addr);
    struct epoll_event ev, events[MAX_EPOLL_EVENTS];
    epfd = epoll_create(50);

    memset(&my_addr, 0, sizeof(struct sockaddr));
    my_addr.sin_family = AF_INET;
    my_addr.sin_port = htons(7788);
    my_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    if ((listener = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
        perror("socket create failed");
        exit(1);
    }

    int opt = 1;
    setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    if (bind(listener, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) {
        perror("bind failed!");
        exit(1);
    }
    set_nonblocking(listener);
    ev.events = EPOLLIN | EPOLLET;
    ev.data.fd = listener;
    epoll_ctl(epfd, EPOLL_CTL_ADD, listener, &ev);

    while (1) {
        nfds = epoll_wait(epfd, events, MAX_EPOLL_EVENTS, -1);
        if (nfds == -1) {
            perror("epoll_wait error!");
            exit(1);
        }
        for (i = 0; i < nfds; i++) {
            if (events[i].data.fd == listener) {
                len = sizeof(struct sockaddr);
                n = recvfrom(listener, buf, 256, 0, (struct sockaddr *)&client_addr, &len);
                if (n == -1) {
                    perror("recvfrom error!");
                    continue;
                }
                printf("Data received: %s\n", buf);
            }
        }
    }
    close(listener);
    return 0;
}

代码实现了基于 epoll 的 UDP 服务器,其中设置了底层监听端口,并通过 recvfrom 接收来自客户端的数据,使用 epoll_wait 函数监听客户端的读事件。当客户端发来数据时,读取数据并在控制台上输出。

四、总结

本文对使用 epoll 进行高效 I/O 处理进行了详细的介绍,包括了 epoll 的优点以及示例代码实现。使用 epoll 可以大大提高 I/O 处理的效率,特别适用于大量文件描述符的 I/O 处理。同时,epoll 还具有可扩展性和线程安全性等优点。在实际使用中,我们可以根据需要来调整 epoll 实例的大小,提高控制的精确度。

原创文章,作者:PUDFB,如若转载,请注明出处:https://www.506064.com/n/372549.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
PUDFBPUDFB
上一篇 2025-04-24 06:40
下一篇 2025-04-24 06:40

相关推荐

  • Java JsonPath 效率优化指南

    本篇文章将深入探讨Java JsonPath的效率问题,并提供一些优化方案。 一、JsonPath 简介 JsonPath是一个可用于从JSON数据中获取信息的库。它提供了一种DS…

    编程 2025-04-29
  • 运维Python和GO应用实践指南

    本文将从多个角度详细阐述运维Python和GO的实际应用,包括监控、管理、自动化、部署、持续集成等方面。 一、监控 运维中的监控是保证系统稳定性的重要手段。Python和GO都有强…

    编程 2025-04-29
  • Python应用程序的全面指南

    Python是一种功能强大而简单易学的编程语言,适用于多种应用场景。本篇文章将从多个方面介绍Python如何应用于开发应用程序。 一、Web应用程序 目前,基于Python的Web…

    编程 2025-04-29
  • Python wordcloud入门指南

    如何在Python中使用wordcloud库生成文字云? 一、安装和导入wordcloud库 在使用wordcloud前,需要保证库已经安装并导入: !pip install wo…

    编程 2025-04-29
  • Python字符转列表指南

    Python是一个极为流行的脚本语言,在数据处理、数据分析、人工智能等领域广泛应用。在很多场景下需要将字符串转换为列表,以便于操作和处理,本篇文章将从多个方面对Python字符转列…

    编程 2025-04-29
  • Python小波分解入门指南

    本文将介绍Python小波分解的概念、基本原理和实现方法,帮助初学者掌握相关技能。 一、小波变换概述 小波分解是一种广泛应用于数字信号处理和图像处理的方法,可以将信号分解成多个具有…

    编程 2025-04-29
  • Python初学者指南:第一个Python程序安装步骤

    在本篇指南中,我们将通过以下方式来详细讲解第一个Python程序安装步骤: Python的安装和环境配置 在命令行中编写和运行第一个Python程序 使用IDE编写和运行第一个Py…

    编程 2025-04-29
  • FusionMaps应用指南

    FusionMaps是一款基于JavaScript和Flash的交互式地图可视化工具。它提供了一种简单易用的方式,将复杂的数据可视化为地图。本文将从基础的配置开始讲解,到如何定制和…

    编程 2025-04-29
  • Python起笔落笔全能开发指南

    Python起笔落笔是指在编写Python代码时的编写习惯。一个好的起笔落笔习惯可以提高代码的可读性、可维护性和可扩展性,本文将从多个方面进行详细阐述。 一、变量命名 变量命名是起…

    编程 2025-04-29
  • Python中文版下载官网的完整指南

    Python是一种广泛使用的编程语言,具有简洁、易读易写等特点。Python中文版下载官网是Python学习和使用过程中的重要资源,本文将从多个方面对Python中文版下载官网进行…

    编程 2025-04-29

发表回复

登录后才能评论