【TCP/IP】利用I/O复用技术实现并发服务器 - epoll

news2024/12/28 21:52:50

目录

select的缺陷

epoll函数

epoll_create

epoll_ctl

epoll_wait

基于epoll的回声服务器实现


select的缺陷

        在之前,我们使用了select函数完成了对回声服务器端I/O的复用,但是从代码上依然存有缺陷,主要集中在:

  • 每次调用select函数需向函数传递监视对象信息
  • 调用select后,要在fd_set中设计循环语句以监视所有对象变量

       这两项操作对于性能损耗较大。并且需要注意的是,套接字是由操作系统管理,在需要频繁调用select函数的场景下,select函数要求必须向其传递监视对象(套接字),那么这样时间、空间都会产生巨大耗费。

        不过,这个问题可以通过合适的策略来进行优化——仅向操作系统传递1次监视对象文件描述符,当监视对象有变化时只通知关注的事件。

        在Linux中,epoll可以支持这项操作。

epoll函数

* 2.5.44版本之前的Linux内核无法使用epoll函数

        在实现epoll时,需要用到其他一些关联函数和结构体:

  • epoll_create: 创建保存epoll文件描述符的空间。
  • epoll_ctl: 向空间注册并注销文件描述符。
  • epoll_wait: 与select函数类似,等待文件描述符发生变化。

        相较于select方式中使用fd_set变量来存储监视对象文件描述符,epoll使用epoll_create函数来向操作系统请求保存对象文件描述符。

        select方法中需要使用到 FD_SET 和 FD_CLR 宏函数,而在epoll方法中只需要通过 epoll_ctl 函数请求操作系统完成即可。 

        在监视文件描述符的变化方面,epoll调用epoll_wait函数来实现对文件描述符变化的监视,同时用结构体类型epoll_event将发生变化的文件描述符集中于一起。

        epoll_event结构体类型定义如下:

typedef union epoll_data
{
  void *ptr;
  int fd;
  uint32_t u32;
  uint64_t u64;
} epoll_data_t;

struct epoll_event
{
  uint32_t events;	/* 欲监视的 Epoll 事件 */
  epoll_data_t data;	/* 用户数据变量 */
}

        声明epoll_event结构体后,将其传递给epoll_wait函数,发生变化的文件描述符将被填入该结构体中,而无需再采用循环的方式对范围内所有文件描述符进行扫描监视。

epoll_create

        epoll_create函数用以创建epoll实例,其引用头文件和函数结构如下:

#include <sys/epoll.h>

int epoll_create(int size);

/* 成功时返回 epoll 文件描述符,失败时返回-1。
   
   参数size 用来传递epoll实例的大小 */

        需要注意的是,函数中参数size的值并不能控制epoll实例的实际大小,该值只负责告诉操作系统实例大小的建议值,供操作系统参考。

        epoll_create函数创建的实例本质上是套接字,因此也会返回文件描述符,同时在结束对套接字的操作时,要调用close函数来进行关闭。

拓展:

        Linux2.6.8之后的内核版本会忽略epoll_create中的size参数意义,即size参数不再具有实际意义,操作系统将会自行根据情况调整epoll实例的大小。

epoll_ctl

        创建epoll实例后,需要通过epoll_ctl函数来对监视对象文件描述符进行注册

        epoll_ctl函数的引用头文件和函数结构如下:

#include <sys/epoll.h>

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

//成功时返回 0 ,失败时返回 -1

/* 参数含义 */
/* 
   epfd: 用于注册监视对象epoll实例的文件描述符
   op: 用于指定监视对象的添加、删除或修改等操作
   fd: 需要注册的监视对象文件描述符
   event: 监视对象的时间类型
*/

        其中参数op有设计好的宏,用来表示增加、删除或修改操作:

  • EPOLL_CTL_ADD: 将文件描述符注册到epoll实例 
  • EPOLL_CTL_DEL:  删除epoll实例中的文件描述符 
  • EPOLL_CTL_MOD: 更改已注册的文件描述符中所关注的事件发生情况

        使用EPOLL_CTL_DEL时,应向第四个参数(event)中传递NULL。

示例:

//从实例 A 中删除文件描述符SOCK_1
epoll_ctl(A , EPOLL_CTL_DEL , SOCK_1, NULL);

//向实例 A 中注册文件描述符SOCK_1,并监视 EVENT中 EPOLLIN 事件的发生与否
struct epoll_event EVENT;
EVENT.events=EPOLLIN;
EVENT.data.fd=sockfd;
epoll_ctl(A , EPOLL_CTL_ADD , SOCK_1, &EVENT);

        示例中EPOLLIN是一种事件类型,用以表示“需要读取数据的情况”,还有其他事件类型,具体如下:

  • EPOLLIN: 需要读取数据的情况 。
  • EPOLLOUT: 输出缓冲为空,可立即发送数据的情况 。
  • EPOLLPRI: 收到OOB数据的情况 。
  • EPOLLRDHUP: 断开连接或半关闭的情况(常用在边缘触发方式下) 。
  • EPOLLERR: 发生错误的情况 。
  • EPOLLET: 以边缘触发的方式得到事件通知 。
  • EPOLLONESHOT: 发生一次事件后,相应文件描述符不再收到事件通知的情况。(需要向epoll_ctL函数的第二个参数传递EPOLL_CTL_MOD,再次设置事件)

epoll_wait

        epoll_wait用于完成最后对对象文件描述符的监视

        epoll_wait函数的引用头文件和函数结构如下:

#include <sys/epoll.h>

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

// 成功时返回发生事件的文件描述符数,失败时返回 -1。

/* 参数含义 */
/* 
   epfd: 表示事件发生监视范围的 epoll 实例 的文件描述符
   events: 保存发生事件的文件描述符集合的结构体地址值
   maxevents: 可以保存的最大事件数 (对应第二个参数events)
   timeout: 以 1/ 1000秒为单位的等待时间,传递 -1 时代表一直等待到事件的发生。
*/

        需要注意的是,epoll_wait函数中的events变量(第二个参数)需要用malloc来开辟空间。

比如:

struct epoll_event * EVENTS;

//EPOLL_SIZE是宏常量,其值代表欲开辟的实例数。注意最后要对类型进行强转,因为malloc返回的是void*
EVENTS = (struct epoll_event *) malloc(sizeof(struct epoll_event) * EPOLL_SIZE); 

epoll_wait(SOCK, EVENTS, EPOLL_SIZE, -1)

        接下来让我们尝试用epoll来实现之前的回声服务器

基于epoll的回声服务器实现

代码示例:

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

#define BUF_SIZE 1024
#define EPOLL_SIZE 5

void Sender_message(char *message)
{
    puts(message);
    exit(1);
}

int main(int argc, char *argv[])
{
    int serv_sock, clnt_sock;
    struct sockaddr_in serv_adr, clnt_adr;
    socklen_t adr_sz;
    int str_len, i;
    char buf[BUF_SIZE];

    struct epoll_event *ep_events;
    struct epoll_event event;
    int epfd, event_cnt;

    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    if (serv_sock == -1)
        Sender_message((char *)"sock creation error");

    memset(&serv_adr, 0, sizeof(serv_adr));

    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(atoi(argv[1]));

    if (bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1)
        Sender_message((char *)"bind error");

    if (listen(serv_sock, 5) == -1)
        Sender_message((char *)"listen error");

    epfd = epoll_create(EPOLL_SIZE);
    ep_events = (struct epoll_event *)malloc(sizeof(struct epoll_event) * EPOLL_SIZE);

    event.events = EPOLLIN; //声明欲监视的事件为需要读取数据的情况
    event.data.fd = serv_sock; //将服务器端套接字保存至epoll_event结构体中
    epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event);

    while (1)
    {
        event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
        if (event_cnt == -1)
        {
            puts((char *)"supervise error");
            break;
        }

        for (i = 0; i < event_cnt; i++)
        {
            if (ep_events[i].data.fd == serv_sock) //若找到的文件描述符是服务器端的
            {
                adr_sz = sizeof(clnt_adr);
                clnt_sock =
                    accept(serv_sock, (struct sockaddr *)&clnt_adr, &adr_sz);
                event.events = EPOLLIN;
                event.data.fd = clnt_sock;
                epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);
                printf((char *)"connected client: %d \n", clnt_sock);
            }
            else
            {
                str_len = read(ep_events[i].data.fd, buf, BUF_SIZE);
                if (str_len == 0) // 关闭请求
                {
                    epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL);
                    close(ep_events[i].data.fd);
                    printf((char *)"closed client: %d \n", ep_events[i].data.fd);
                }
                else
                {
                    write(ep_events[i].data.fd, buf, str_len); //写数据
                }
            }
        }
    }
    close(serv_sock);
    close(epfd);
    return 0;
}

运行结果:

 得到验证

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/707656.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ModaHub魔搭社区:向量数据库Milvus性能优化问题(三)

目录 Milvus 的导入性能如何&#xff1f; 边插入边搜索会影响搜索速度吗&#xff1f; 批量搜索时&#xff0c;用多线程的收益大吗&#xff1f; 为什么同样的数据量&#xff0c;用 GPU 查询比 CPU 查询慢&#xff1f; Milvus 的导入性能如何&#xff1f; 客户端和服务端在同…

__attribute__机制

__attribute__((constructor))和 __attribute__((destructor)) __attribute__((constructor))&#xff1a;放在main函数之前执行的函数的前面。 __attribute__((destructor))&#xff1a;放在main函数之后执行的函数的前面。 测试代码 #include <stdio.h> #include &l…

RocketMQ 详解

&#x1f3c6;今日学习目标&#xff1a; &#x1f340;RocketMQ 详解 ✅创作者&#xff1a;林在闪闪发光 ⏰预计时间&#xff1a;30分钟 &#x1f389;个人主页&#xff1a;林在闪闪发光的个人主页 &#x1f341;林在闪闪发光的个人社区&#xff0c;欢迎你的加入: 林在闪闪发光…

【Shell】复制用户传参的文件夹

授权 cd /Users/lion/Downloads/shell-test-demos chmod ux *.sh#!/bin/bashprintHelp() {echo "-p pic (required) path for pic"exit 1 }while getopts p:h OPT; docase $OPT inp) path"$OPTARG" ;;esac done# check api_key exists if [ -z "$pat…

IDEA字体配置

IDEA默认字体&#xff1a;JetBrains Mono 1、下载Monaco字体&#xff08;windows版&#xff09;&#xff1a;下载地址&#x1f448; 2、双击安装 3、在IDEA中切换Monaco字体

帆软Json数据集插件,数据查询及参数控件传参

先看Demo 文本查询&#xff0c;下拉复选框查询&#xff0c;无参数时查全部 有参数传参时 1.定义数据连接 测试地址&#xff1a; http://fine-doc.oss-cn-shanghai.aliyuncs.com/book.json 2.新建json数据集&#xff0c;查询全表 2.1.查询全表 2.2.查询单个字段 3. 上述是简单…

CLion开发STM32

CLion开发STM32 opencd https://gnutoolchains.com/arm-eabi/openocd/ gcc-arm-none-eabi https://launchpad.net/gcc-arm-embedded/download arm-none-eabi-gcc -v“gcc-arm-none-eabi是GNU项目下的软件,是一个面向裸机arm的编译器。 mingw 需要把opencd和的工具链添加…

移远通信全新3GPP NTN R17模组正式上线,助力实现空天地海网络全覆盖

6月29日&#xff0c;在2023上海世界移动通信大会期间&#xff0c;物联网整体解决方案供应商移远通信正式宣布&#xff0c;推出符合3GPP NTN R17标准的全新5G卫星通信模组——CC950U-LS。该产品面向国内物联网市场&#xff0c;将为蜂窝网络无法覆盖的森林、海洋、沙漠等偏远地区…

抖音SEO矩阵源码开发(一)

前言&#xff1a; 1.抖音SEO矩阵系统源码开发 是一项技术密集型工作&#xff0c;需要对大数据处理、人工智能等领域有深入了解。该系统开发过程中需要用到多种编程语言在服务器上安装LNMP环境&#xff0c;包括Linux操作系统、Nginx、MySQL、PHP等&#xff0c;如Java、Python等…

CTF安全竞赛介绍

目录 一、赛事简介 二、CTF方向简介 1.Web&#xff08;Web安全&#xff09; &#xff08;1&#xff09;简介 &#xff08;2&#xff09;涉及主要知识 2.MISC&#xff08;安全杂项&#xff09; &#xff08;1&#xff09;介绍 &#xff08;2&#xff09;涉及主要知识 3…

Vue3 刨析响应式原理

ref 目标 了解 Vue &#xff0c;手写一个方法&#xff0c;实现响应式&#xff0c;并读懂响应式 源码 class MyRef {constructor(value) {this._value value}// 访问器get value() {console.log(触发 getter 函数 访问);return this._value}// 读取器set value(newVal) {cons…

基于Android新生预报到系统APP的设计与实现

1.引言 随着国家的教育方针政策的不断改变&#xff0c;自20世纪初以来&#xff0c;政府开始实施扩招计划&#xff0c;截至2020年我国大学生总规模己经攀升至近5000万&#xff0c;大学教育的普及化正在逐渐实现。随着大学新生数量的不断增多&#xff0c;到了每年的9月份&#x…

实现Linux(Ubuntu22.04)与Windows文件互通共享(双方永久往来~)

Samba服务器了解&#xff08;防守&#xff09; Samba是一个开源软件套件&#xff0c;它允许在不同操作系统之间共享文件、打印机和其他资源。它是基于SMB/CIFS协议&#xff0c;这是一种用于在Windows系统中共享文件和打印机的网络协议。Samba服务器可以在Linux、Unix、macOS和…

玩转C++调试之Python的GDB库增强

玩转C调试之Python的GDB库增强 0.导语 调试是软件开发过程中不可或缺的一环&#xff0c;而GDB&#xff08;GNU调试器&#xff09;作为一款功能强大的调试工具&#xff0c;在开发者中得到广泛应用。除了传统的命令行调试功能外&#xff0c;GDB还提供了Python的GDB库&#xff0c;…

2024考研408-计算机组成原理第五章-中央处理器学习笔记

文章目录 前言一、CPU的功能与基本结构1.1、CPU的功能1.2、运算器与控制器需要实现功能1.3、运算器的基本结构1.3.1、基本结构构成&#xff08;七个部分&#xff09;1.3.2、各个部件详细介绍①算数逻辑运算单元②通用寄存器组&#xff08;介绍数据通路的基本结构2个&#xff09…

C语言进阶---字符串+内存函数

本章重点 重点介绍处理字符和字符串的库函数的使用和注意事项。 求字符串长度 strlen() 长度不受限制的的字符串函数 strcpy()strcat()strcmp() 长度受限制的的字符串函数 strncpy()strncat()strncmp() 字符串查找 strstr()strtok() 错误信息报告 strerror() 字符操作内存操作函…

《企业性能测试:体系构建、落地指导与案例解读》——小解送书第四期

目录 介绍 抽奖 介绍 软件系统性能的重要性无须多言&#xff0c;没有哪个用户可以忍受一个响应缓慢的网站或者反应迟钝的软件。软件性能是用户体验的核心。大部分用户可能对软件性能并不了解&#xff0c;但他们永远想使用响应更迅速的软件。所以&#xff0c;性能是评估一个软…

小程序data-*的误区

场景&#xff1a;点击按钮获取data-*的值跳转页面&#xff0c;跳转页获取传过来的参数 binnie: 华哥&#xff0c;为什么有的部分参数传不过去然后显示undefined&#xff1f; 华哥&#xff1a; binnie, 我看了一下你的代码&#xff0c;你错在属性名有大写字母了。我给你写了个…

postman自动生成接口文档

点击&#xff1a; 会自动生成一个文件夹 点击图表&#xff0c;修改名字 新建一个请求&#xff0c;到时候会自动保存到文件夹里面&#xff0c;但是保存前看清楚保存的名字 点击三个点-》点击export即可

Python提取斗鱼美女图片--selenium requests两种方式

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 前言 使用selenium,requests提取斗鱼美女数据 数据来源 斗鱼美女链接 一、selenium是干嘛…