首页 > 系统相关 >Linux网络编程(epoll函数的使用)

Linux网络编程(epoll函数的使用)

时间:2023-08-19 15:31:30浏览次数:42  
标签:epoll int 编程 描述符 事件 Linux 就绪 event

(文章目录)


前言

本篇文章我们讲解epoll函数的使用方法,epoll相比于poll来说性能方面有所提升和改进。

一、epoll概念特点讲解

epoll 是 Linux 上一种高性能的多路复用机制,用于监视大量文件描述符并在它们就绪时通知应用程序。它是在 select 和 poll 的基础上进一步优化和改进而来的。

epoll 的主要特点包括:

1.没有文件描述符数量限制:与 select 和 poll 不同,epoll 采用了基于事件的就绪通知机制,没有预定义的文件描述符数量限制,可以支持更大规模的并发连接。

2.高效的事件通知:epoll 使用了内核和用户空间共享的事件数据结构,将文件描述符的事件注册到内核空间,当事件就绪时,内核直接将就绪的事件通知给用户空间,避免了每次调用都需要遍历整个文件描述符数组的性能开销。

3.分离的就绪事件集合:epoll 将就绪的事件从内核空间复制到用户空间,形成一个分离的就绪事件集合,用户可以直接遍历这个集合来处理就绪的事件,而不需要遍历整个文件描述符数组。

4.支持边缘触发和水平触发:epoll 提供了两种模式来处理事件,一种是边缘触发模式(EPOLLET),只在状态发生变化时通知应用程序,另一种是水平触发模式(默认),在事件就绪期间一直通知应用程序。

5.更低的内存拷贝开销:epoll 使用内存映射技术,避免了每次调用都需要将事件数据从内核复制到用户空间的开销,从而减少了系统调用的次数和内存拷贝的开销。

6.支持较高精度的超时控制:与 poll 不同,epoll 的超时参数以毫秒和纳秒为单位,提供了较高精度的超时控制。

总体来说,epoll 在性能上相比于 select 和 poll 有较大的优势,特别适用于高并发场景下的网络编程。它的高效事件就绪通知、支持大规模并发连接、较低的内存拷贝开销以及较高的超时精度,使得它成为开发高性能服务器和网络应用的首选机制。

二、epoll实现机理

epoll是使用红黑树(Red-Black Tree)实现的。epoll是Linux操作系统提供的一种高效的事件通知机制,用于处理大量的并发连接。它能够监视多个文件描述符的状态变化,当文件描述符就绪时,通过回调函数进行相应的处理。

在Linux内核中,epoll使用红黑树作为其主要的数据结构,用于维护注册的文件描述符集合。红黑树是一种自平衡的二叉搜索树,具有较快的插入、删除和搜索操作的时间复杂度。通过使用红黑树,epoll能够高效地检索和管理大量的文件描述符。

当文件描述符发生事件时,epoll通过红黑树的查找操作快速定位到相应的结点,并触发注册的回调函数进行事件处理。使用红黑树的原因是它能够保持良好的平衡性,保证搜索、插入和删除操作的最坏情况时间复杂度为O(log n),从而保证了epoll的高性能和可伸缩性。

总结来说,epoll是利用红黑树作为其底层数据结构实现的,这使得它在处理大量并发连接时能够提供高效的事件通知机制。

三、epoll相关函数讲解

1.epoll_create函数

函数原型:

int epoll_create(int size);

epoll_create 函数创建一个 epoll 实例,并返回一个文件描述符,用于标识该 epoll 实例。参数 size 是一个提示,表示 epoll 实例可以监视的文件描述符的数量上限。但在大多数情况下,该参数会被忽略,可以传递任意的值。

返回的文件描述符可以用于之后对 epoll 实例进行操作,比如注册、修改和删除文件描述符的事件。

需要注意的是,epoll_create 函数在成功时返回一个非负整数,表示 epoll 实例的文件描述符,如果出现错误,返回值为 -1,并设置 errno 错误码来指示具体的错误类型。

2.epoll_ctl函数

函数原型:

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

epoll_ctl 函数通过指定的 epfd 参数来操作特定的 epoll 实例,op 参数表示操作类型,可以是以下三种之一:

EPOLL_CTL_ADD:将指定的文件描述符 fd 添加到 epoll 实例中,并注册相应的事件。这样,当该文件描述符上的事件就绪时,就会通知应用程序。

EPOLL_CTL_MOD:修改已经注册在 epoll 实例中的文件描述符 fd 对应的事件。可以修改事件的类型、关注的事件、关联的用户数据等。

EPOLL_CTL_DEL:删除已经注册在 epoll 实例中的文件描述符 fd。

fd 参数是目标文件描述符,用于指定需要进行操作的文件描述符。

event 参数是一个指向 struct epoll_event 结构体的指针,用于指定事件相关的配置。该结构体包含两个成员:

uint32_t events:表示注册的事件类型,可以是 EPOLLIN(可读事件)、EPOLLOUT(可写事件)、EPOLLRDHUP(对端关闭连接)、EPOLLPRI(有紧急数据可读)、EPOLLERR(错误事件)等。可以使用位掩码进行组合。

epoll_data_t data:用于存储用户数据信息,可以是文件描述符本身的值,也可以是用户自定义的数据结构指针。

函数的返回值为 0 表示操作成功,-1 表示出现错误,具体的错误信息可以通过检查 errno 变量获得。

3.epoll_wait函数

函数原型:

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

epfd 参数是 epoll 实例的描述符,通过它指定要进行事件等待的 epoll 实例。

events 参数是一个用于存放事件信息的数组,每个数组元素都是 struct epoll_event 类型的结构体,用于存储就绪的文件描述符以及对应的事件信息。

maxevents 参数表示 events 数组的大小,即最多可以等待多少个事件。

timeout 参数是超时时间,单位为毫秒。它决定了 epoll_wait 函数的阻塞行为:

如果 timeout 设置为 -1,表示无限期阻塞,直到有事件发生为止。

如果 timeout 设置为 0,表示非阻塞,立即返回当前就绪的事件,如果没有事件就绪,则返回 0。

如果 timeout 设置为一个正整数,表示阻塞等待指定的时间后返回,如果在超时时间内没有等到事件就绪,则返回 0。

epoll_wait 函数在成功时返回就绪事件的文件描述符数量,如果出现错误则返回 -1,并设置 errno 错误码来指示具体的错误类型。

四、epoll实现并发服务器

当使用epoll实现并发服务器时,通常的步骤包括以下几个主要环节:

1.创建socket:使用socket函数创建一个监听套接字,用于接受客户端的连接请求。

2.绑定socket:使用bind函数将监听套接字绑定到一个特定的IP地址和端口。

3.监听连接:使用listen函数开始监听连接请求,指定服务器可接受的最大连接数。

4.创建epoll实例:使用epoll_create函数创建一个epoll实例,返回一个文件描述符。

5.将监听套接字添加到epoll实例:使用epoll_ctl函数将监听套接字添加到epoll实例中,并注册对读事件的关注。

6.进入事件循环:循环调用epoll_wait函数来等待事件的发生,该函数会阻塞当前线程直至有事件发生。一旦有事件发生,它将返回一个就绪事件的列表。

7.处理就绪事件:遍历就绪事件列表,对每个事件进行处理。根据事件类型,可以进行接受连接、读取数据、发送数据或关闭连接等操作。

8.根据需要添加或删除文件描述符:在处理完一个事件后,可以根据需要使用epoll_ctl函数动态地添加或删除文件描述符,以便继续监听其他事件。

9.重复步骤6-8:继续循环执行步骤6-8,处理新的就绪事件,直到服务器主动关闭或出现错误条件为止。

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

#define MAX_EVENTS  1024 //最多可以等待多少个事件

int main()
{
    int server = 0;
    struct sockaddr_in saddr = {0};
    int client = 0;
    struct sockaddr_in caddr = {0};
    socklen_t asize = 0;
    int len = 0;
    char buf[32] = {0};
    int maxfd;
    int ret = 0;
    int i = 0;

    server = socket(PF_INET, SOCK_STREAM, 0);

    if( server == -1 )
    {
        printf("server socket error\n");
        return -1;
    }

    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = htonl(INADDR_ANY);
    saddr.sin_port = htons(8888);

    if( bind(server, (struct sockaddr*)&saddr, sizeof(saddr)) == -1 )
    {
        printf("server bind error\n");
        return -1;
    }

    if( listen(server, 128) == -1 )
    {
        printf("server listen error\n");
        return -1;
    }

    printf("server start success\n");


    struct epoll_event event, events[MAX_EVENTS];
    /*创建epoll*/
    int epollInstance = epoll_create1(0);
    if (epollInstance == -1) 
    {
        printf("Failed to create epoll instance\n");
    }
    /*将服务器添加进入event中*/
    event.events = EPOLLIN;
    event.data.fd = server;

    if (epoll_ctl(epollInstance, EPOLL_CTL_ADD, server, &event) == -1) 
    {
        printf("Failed to add server socket to epoll instance");
    }        

    while( 1 )
    {        
        int numEventsReady = epoll_wait(epollInstance, events, MAX_EVENTS, -1);
        if (numEventsReady == -1) 
        {
            printf("Failed to wait for events");
            return -1;
        }

        for(i = 0; i < numEventsReady; i++)
        {
            if(events[i].data.fd == server)
            {
                /*有客户端连接上来了*/
                asize = sizeof(caddr);  
                client = accept(server, (struct sockaddr*)&caddr, &asize);
                printf("client is connect\n");

                event.events = EPOLLIN | EPOLLET;
                event.data.fd = client;

                if (epoll_ctl(epollInstance, EPOLL_CTL_ADD, client, &event) == -1) 
                {
                    printf("Failed to add client socket to epoll instance");
                    return -1;
                }                
            }
            else
            {
                /*处理客户端的请求*/
                len = read(events[i].data.fd, buf, 1024);
                if(len == 0)
                {
                    printf("client is disconnect\n");
                    close(events[i].data.fd);
                }
                else
                {
                    /*对接收到的数据进行处理*/
                    printf("read buf : %s\n", buf);
                    write(events[i].data.fd, buf, len);
                }
            }
        }        


    }
    
    close(server);

    return 0;
}

总结

本篇文章就讲解到这里,下篇文章继续讲解Linux网络编程的知识。

标签:epoll,int,编程,描述符,事件,Linux,就绪,event
From: https://blog.51cto.com/u_16153875/7150279

相关文章

  • linux之shell脚本quickStart
    这篇文章主要参考于《跟老男孩学linux运维:Shell编程实战》,方便写shell脚本时参考,只列一些shell脚本中的容易混淆的知识点。目录1变量1.1普通变量1.2shell特殊变量1.3shell特殊扩展变量2运算符2.1空格2.2(())与[]2.3||与&&3常用命令3.1read3.2echo3.3eval3.4双......
  • linux云服务器状态上报
    统计某文件夹下文件的个数ls-l|grep “^-”|wc-l统计某文件夹下目录的个数ls-l|grep“^d”|wc-l统计文件夹下文件的个数,包括子文件夹里的。ls-lR|grep“^-”|wc-l统计文件夹下目录的个数,包括子文件夹里的。ls-lR|grep“^d”|wc-l说明:ls-l长列表输出该目录下文件信息(......
  • 部署Kafka+ZK及其日志采集实战(系统版本:linux_CentOs_7.8)
    部署ZKdockerrun-d--namezookeeper-p2181:2181-twurstmeister/zookeeper部署Kafka-p9092:9092\-eKAFKA_BROKER_ID=0\--envKAFKA_HEAP_OPTS=-Xmx256M\--envKAFKA_HEAP_OPTS=-Xms128M\-eKAFKA_ZOOKEEPER_CONNECT=[内网ip]:2181\-eKAFKA_ADVERTISED......
  • Linux 系统替换字符串常用命令
    概述在Linux系统中有时候我们需要替换某个很长的字符串或者修改某个配置参数,有些文件又隐藏目录比较深,有些场景也需要在一个目录下批量去修改文件,那应该怎么高效,快速的去完成修改呢?下面记录一下本人实施过程中的一些方法,做个备忘手稿分享以备随时查看。系统平台CentOSLinux7第......
  • Linux常用网络配置练习(2)
    打开第二台虚拟机(带图形界面的虚拟机)使用浏览器访问一些网站,然后统计这些连接处于time-wait的数量[[email protected]]#netstat-an|grepTIME_WAIT|wc-l14打开两台Linux虚拟机,然后测试它们之间的TCP性能和UDP性能,并将结果记录下来##虚拟机01[root@test-server......
  • Java8编程 轻松驾驭数据流
    StreamAPI是 Java 8中最重要的新特性之一,它是处理集合和数组的一种新方式。它提供了一种简单、灵活和可读的方式来处理集合和数组中的元素,从而使代码更加简洁、高效和易于维护。1.原理介绍StreamAPI的核心是Stream接口,它表示一组元素的序列,可以按需进行计算。......
  • linux下进程间通信
    进程间通信一、进程间通信的介绍1、进程间通信的概念进程通信(Interprocesscommunication),简称:IPC;本来进程之间是相互独立的。但是由于不同的进程之间可能要共享某些信息,所以就必须要有通讯来实现进程间的互斥和同步。比如说共享同一块内存、管道、消息队列、信号量等等就是实......
  • 程序代做服务:解放您的编程烦恼
    导言:在现代技术驱动的社会中,编程已经成为了解决问题和创新的重要手段。然而,不是每个人都拥有编程的技能和时间来完成复杂的编程任务。在这样的情况下,程序代做服务应运而生,为那些需要技术支持的个人和企业提供了便利。什么是程序代做服务?程序代做服务是一种服务模式,通过该模式,您......
  • 【LeetCode173. 最多连胜的次数】MySQL用户变量编程解法
    目录题目地址题目描述代码题目地址https://leetcode.cn/problems/longest-winning-streak/description/题目描述选手的 连胜数是指连续获胜的次数,且没有被平局或输球中断。编写解决方案来计算每个参赛选手最多的连胜数。结果可以以任何顺序返回。代码WITHt1AS(......
  • Linux命令
    常用命令命令ls-a这个选项能显示.开头的隐藏文件-i显示每个文件的inode号-m所有项目以逗号分隔,并填满整行行宽-R同时列出所有子目录层-h将列出文件的大小以人性化格式输出ls-lc[文件名]查看文件的访问时间ls-lu[文件名]查看文件的最后修改时间ls-l显示文件的......