高级IO【select、poll、epoll】

在这里插入图片描述

高山仰止,景行行止

文章目录

  • 五种IO模型
    • 阻塞I/O
    • 非阻塞I/O
    • I/O复用
    • 信号驱动I/O
    • 异步I/O
  • 同步通信与异步通信
    • 同步通信
    • 异步通信
  • 非阻塞IO
    • 基于fcntl实现setNonblock函数
    • 注意事项
  • IO多路转接—select
    • 文件描述符集合
    • timeval结构
    • 调用过程
    • 返回值
    • 缺点和局限性
  • IO多路转接—poll
    • struct pollfd 结构体
    • 返回值
    • 优缺点
  • IO多路转接—epoll
    • 原理和特点
    • 函数原型
      • epoll_create
      • epoll_ctl
      • epoll_wait
      • struct epoll_event 结构体
    • 工作模式
      • 水平触发
      • 边缘触发
    • epoll惊群问题
    • 优缺点
  • 总结

五种IO模型

在计算机网络中,I/O模型描述了数据在网络套接字和应用程序之间传输的方式。常见的五种I/O模型包括:阻塞I/O、非阻塞I/O、I/O复用、信号驱动I/O和异步I/O。

任何IO过程中,都包含两个步骤:等待和拷贝.。而且在实际的应用场景中,等待消耗的时间往往都远远高于拷贝的时间。为了让IO更高效,最核心的办法就是让等待的时间尽量少

阻塞I/O

  • 原理:应用程序执行一个I/O操作时,如果数据未准备好,调用将会被阻塞,直到数据准备好为止。在数据被复制到应用程序缓冲区之前,应用程序一直等待,不会返回到主程序。
  • 应用场景:适用于单用户和单任务对性能要求不高的应用程序。

非阻塞I/O

  • 原理:应用程序执行一个I/O操作时,如果数据未准备好,调用立即返回一个错误。应用程序可以继续做其他事情,它必须不断地询问I/O操作是否完成。
  • 应用场景:适用于需要同时处理多个操作或任务的应用程序,但要求应用程序显式管理检查数据状态和重试操作。

I/O复用

  • 原理:使用select或poll等系统调用,允许应用程序监视多个文件描述符,一旦某个文件描述符就绪(读就绪或写就绪),能够通知程序进行相应的读写操作。这样,单个进程可以处理多个并发数据流。
  • 应用场景:适用于需要处理多个连接或多种网络条件的服务器应用,如Web服务器、数据库服务器。

信号驱动I/O

  • 原理:应用程序通过sigaction系统调用预先注册一个信号处理函数,当数据准备就绪时,系统产生一个SIGIO信号,应用程序随后开始I/O操作。
  • 应用场景:适用于希望避免轮询但又不希望I/O操作阻塞进程的场景。

异步I/O

  • 原理:应用程序发起一个I/O操作后立即返回,不需要等待I/O操作完成。当I/O操作完成后,应用程序会收到一个通知。
  • 应用场景:适用于需要高性能I/O处理的应用程序,特别是那些需要处理大量并发I/O操作或者对响应时间有严格要求的应用。

同步通信与异步通信

在计算机网络和系统设计中,同步通信和异步通信是两种基本的数据交换方式,它们在处理数据传输、通信过程控制、资源占用等方面有明显的不同。

同步通信

同步通信要求发送方和接收方在交换信息时必须同时处于就绪状态,即发送方在发送数据后,必须等待接收方的响应,才能继续进行下一步的操作。在同步通信中,双方的交互是实时进行的,这意味着任一方都必须在通信过程中等待对方,直到交换的信息被接收和确认。

  • 优点:数据的一致性和可靠性高,易于理解和实现。
  • 缺点:可能会导致资源的浪费,因为在等待响应期间,某些资源(如CPU、网络等)可能处于闲置状态。这种方式在处理速度较慢或负载较高的系统中效率不高。

异步通信

异步通信允许发送方在发送数据后不必立即等待响应,而是可以继续执行其他任务。接收方在接收和处理数据后,可以通过回调、事件、消息队列等方式通知发送方结果。在异步通信中,发送方和接收方不需要同时处理交换的信息,减少了等待时间。

  • 优点:提高了系统的整体效率和吞吐量,因为发送方在等待响应期间可以处理其他任务。非常适合于处理速度不均或多任务环境。
  • 缺点:编程模型更复杂,需要更多的错误处理机制。数据的一致性和顺序控制也更加困难。

注意,这里的同步通信与异步通信和线程的同步与互斥是完全不相干的概念,线程的同步是线程间的一种相互制约,相互协调的关系。

非阻塞IO

一般来说,文件描述符默认都是非阻塞的,如果想将文件描述符设置为非阻塞,可以使用函数fcntlfcntl 是 Unix 和类 Unix 系统中的一个重要的系统调用,全称是 “file control”。它提供了对文件描述符的各种控制操作,包括复制文件描述符、获取/设置文件描述符属性、文件锁定等。fcntl 是一个非常灵活的命令,其功能由命令参数指定。

语法

#include <fcntl.h>int fcntl(int fd, int cmd, ... /* arg */ );
  • fd: 文件描述符,是一个指向打开文件的引用。
  • cmd: 指定要执行的操作类型。
  • : 额外参数,其类型和数量取决于 cmd 参数的值。

常见用途和命令

  1. 复制文件描述符 (F_DUPFDF_DUPFD_CLOEXEC)

    • 创建一个新的文件描述符,作为旧文件描述符的副本。这通常用于重定向标准输入/输出或处理多线程中的文件描述符。
  2. 获取/设置文件描述符标志 (F_GETFDF_SETFD)

    • 获取或设置文件描述符的标志,例如设置 FD_CLOEXEC 标志,使得在执行 exec 调用新程序时关闭文件描述符。
  3. 获取/设置文件状态标志 (F_GETFLF_SETFL)

    • 获取或设置文件状态标志,如非阻塞 (O_NONBLOCK)、同步 (O_SYNC) 等。
  4. 文件锁定 (F_GETLK, F_SETLK, F_SETLKW)

    • 对文件或文件的一部分进行锁定,避免多个进程同时写入同一文件区域。F_SETLK 设置锁但如果无法立即获得锁则失败,F_SETLKW 则会等待直到锁被获取,而 F_GETLK 可以用来测试锁的状态。

示例

#include <iostream>
#include <unistd.h>
#include <fcntl.h>int main()
{std::cout << ">>>>>> ";fflush(stdout);char buffer[1024];fcntl(0, F_SETFL, O_NONBLOCK);int ret = read(0, buffer, sizeof buffer);if(ret < 0){std::cout << "read fail\n";exit(-1);}return 0;
}

在这里插入图片描述

基于fcntl实现setNonblock函数

void setNonblock(int fd)
{int fl = fcntl(fd, F_GETFL);if(fl < 0){std::cout << "fcntl getfl fail\n";exit(-1);}fl |= O_NONBLOCK;fcntl(fd, F_SETFL, fl);
}

注意事项

  • fcntl 的行为可能会根据不同的 UNIX 系统变化,特别是在文件锁定和某些命令的支持方面。
  • 在多线程环境中使用 fcntl 进行文件锁定时要小心,确保不会导致死锁。
  • 一些 fcntl 操作可能会因为文件类型而不被支持,比如对某些类型的特殊文件或网络套接字可能不支持文件锁定。

IO多路转接—select

select 函数是一种 I/O 多路复用机制,用于监视多个文件描述符的状态变化,以便在其中至少一个文件描述符就绪时进行读取、写入或异常处理。

select 函数的调用方式

#include <sys/select.h>int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
  • nfds:要监视的文件描述符的数量(通常设置为要监视的最大文件描述符加一)。
  • readfdswritefdsexceptfds:分别是用于监视读取、写入和异常事件的文件描述符集合。
  • timeout:表示 select 函数的超时时间,如果为 NULL 则表示阻塞直到至少有一个文件描述符就绪。

文件描述符集合

fd_set 是一个位图(bitmask),用于表示一组文件描述符。通过宏函数 FD_ZEROFD_SETFD_CLRFD_ISSET,可以对 fd_set 进行操作,例如清零、设置、清除和测试文件描述符的状态。

在这里插入图片描述

在这里插入图片描述

timeval结构

timeval 结构是用于表示时间间隔的数据结构,通常用于设置超时时间或者记录时间间隔的信息。在 POSIX 系统中经常被使用,比如在网络编程中的超时设置,或者在计时功能中的时间间隔记录等。

timeval 结构的定义通常如下所示:

在这里插入图片描述

  • tv_sec:表示秒数部分,是一个 long 类型的整数,用于表示秒数。
  • tv_usec:表示微秒部分,是一个 long 类型的整数,用于表示微秒数(以毫秒为单位)。

timeval 结构主要用于设置超时时间,比如在调用 select 函数时设置超时时间,或者在 setsockopt 函数中设置超时选项。此外,它还可以用于记录时间间隔,比如在计时功能中记录程序执行的时间间隔。

调用过程

  1. 初始化fd_set:通过执行FD_ZERO(&set);初始化fd_set变量set,确保所有位都清零。这个步骤是必要的,因为它保证了set的起始状态是确定的,没有任何文件描述符被监视。

  2. 添加文件描述符到fd_set

    • 当执行FD_SET(fd, &set);时,如果fd=5,则set的第5位(从0开始计数)被设置为1,set的位表示变为0001,0000。
    • 如果继续添加fd=2fd=1,则相应的第2位和第1位也被设置为1,set现在变为0001,0011,表示fd=1fd=2fd=5都在监视之列。
  3. 调用select等待事件select(6, &set, NULL, NULL, NULL);调用会阻塞等待,直到监视的文件描述符之一(在这个例子中,是fd=1fd=2fd=5)上发生可读事件,或者发生了错误或异常条件。参数6表示监视的文件描述符范围从0到5,这是因为select的第一个参数应该是监视的文件描述符集中最大文件描述符加1。

  4. 检查select的结果:当select返回后,set会被修改以反映哪些文件描述符上发生了事件。如果fd=1fd=2上发生了可读事件,则它们在set中对应的位保持为1,表示它们准备好被读取。没有事件发生的fd=5对应的位会被清零,因为select会修改fd_set来仅包含那些发生了指定事件的文件描述符。

返回值

  • 如果至少有一个文件描述符就绪,select 函数返回就绪文件描述符的数量。
  • 如果超时时间到达,没有文件描述符就绪,select 返回 0。
  • 如果出现错误,select 返回 -1,并设置 errno 变量来指示错误类型。

缺点和局限性

  • 文件描述符数量限制:某些系统对单个进程能够监视的文件描述符数量有限制,这会限制 select 函数的使用。
  • 效率问题:select 函数需要遍历所有待监视的文件描述符,效率在文件描述符数量较大时可能不高。
  • 没有提供文件描述符状态改变的通知机制,需要不断轮询检查文件描述符的状态。

IO多路转接—poll

poll函数是一个I/O多路复用的系统调用,允许一个进程监视多个文件描述符,以确定是否可以执行读取、写入或是出错等操作,而无需阻塞等待。

函数原型:

#include <poll.h>int poll(struct pollfd *fds, nfds_t nfds, int timeout);
  • fds:指向一个struct pollfd数组的指针,该结构体用于描述要监视的文件描述符及其事件。
  • nfds:表示要监视的文件描述符的数量。
  • timeout:表示超时时间,以毫秒为单位。如果设置为负数,则poll会阻塞直到有文件描述符准备就绪;如果设置为0,则poll立即返回而不阻塞;如果大于0,则表示等待的最大时间。

struct pollfd 结构体

在这里插入图片描述

  • fd:要监视的文件描述符。
  • events:要监视的事件,包括 POLLIN(可读)、POLLOUT(可写)等。
  • revents:实际发生了的事件,由poll函数填充。

返回值

  • -1:发生错误,可以通过 errno 变量查看具体错误原因。
  • 0:超时。
  • > 0:返回就绪的文件描述符数量。

优缺点

优点:

  1. 支持大量文件描述符: poll能够有效地处理大量的文件描述符,通常没有文件描述符数量的限制。

  2. 可移植性: poll是POSIX标准的一部分,在大多数主流操作系统上都有支持,包括Linux、Windows和Unix等。

  3. 简单易用: poll的接口相对简单,并且易于使用和理解,只需要准备一个struct pollfd数组来描述要监视的文件描述符和事件。

缺点:

  1. 效率问题: 尽管poll在处理大量文件描述符时比select效率更高,但在某些情况下仍可能存在效率问题,特别是当文件描述符数量非常大时。

  2. 性能下降: poll是一种轮询机制,需要遍历所有的文件描述符来检查状态变化,因此随着文件描述符数量的增加,性能可能会下降。

  3. 没有就绪文件描述符数量的返回值: poll函数只返回就绪的文件描述符的数量,而不提供就绪的文件描述符的具体信息,需要通过遍历struct pollfd数组来获取。

IO多路转接—epoll

epoll是Linux提供的一种I/O多路复用机制,用于有效地处理大量的文件描述符,并且能够高效地监视多个文件描述符的状态变化。

原理和特点

数据结构:

  • 红黑树(Red-Black Tree): epoll使用红黑树来存储需要监视的文件描述符(FD)。
  • 就绪链表(Ready List): 用于存储发生事件的文件描述符。

主要流程:

  1. 创建epoll实例: 用户调用epoll_create函数创建一个epoll实例,并返回一个文件描述符,用于操作该实例。

  2. 添加文件描述符: 用户通过epoll_ctl函数将需要监视的文件描述符添加到epoll实例中。

  3. 等待事件发生: 用户调用epoll_wait函数等待事件发生,内核会阻塞进程直到有文件描述符发生事件。

  4. 事件通知: 当文件描述符上有事件发生时,内核会将发生事件的文件描述符加入到就绪链表中,并唤醒等待的进程。

  5. 处理事件: 用户从就绪链表中获取发生事件的文件描述符,并处理相应的事件。

工作原理:

  • 当调用epoll_wait函数时,内核会遍历红黑树,找到有事件发生的文件描述符,并将其添加到就绪链表中。
  • 用户可以通过就绪链表获取发生事件的文件描述符,然后进行相应的处理。

函数原型

epoll_createepoll_ctlepoll_wait,它们是使用epoll进行事件驱动的核心。

epoll_create

int epoll_create(int size);
  • 功能: 创建一个epoll实例,并返回一个文件描述符。
  • 参数:
    • size:指定epoll实例的大小,通常设置为一个大于0的数即可,但实际上内核会忽略这个参数。
  • 返回值:
    • 如果成功,返回一个新的文件描述符,代表创建的epoll实例;
    • 如果失败,返回-1,并设置errno。

epoll_ctl

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
  • 功能: 控制epoll实例,用于添加、修改或删除要监视的文件描述符。
  • 参数:
    • epfdepoll实例的文件描述符,由epoll_create返回。
    • op:操作类型,可以是EPOLL_CTL_ADDEPOLL_CTL_MODEPOLL_CTL_DEL,分别表示添加、修改或删除文件描述符。
    • fd:要进行操作的文件描述符。
    • event:要监视的事件,是一个struct epoll_event结构体。
  • 返回值:
    • 如果成功,返回0;
    • 如果失败,返回-1,并设置errno。

epoll_wait

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
  • 功能: 等待事件发生,返回发生了事件的文件描述符。
  • 参数:
    • epfdepoll实例的文件描述符,由epoll_create返回。
    • events:用于存储发生事件的文件描述符和事件的数组。
    • maxeventsevents数组的最大容量。
    • timeout:等待超时时间,单位为毫秒,传入-1表示永久等待。
  • 返回值:
    • 如果成功,返回发生了事件的文件描述符数量;
    • 如果超时,返回0;
    • 如果出错,返回-1,并设置errno。

这些函数共同构成了epoll的核心API,允许用户控制监视的文件描述符和等待事件的情况,从而实现高效的事件驱动机制。

struct epoll_event 结构体

在这里插入图片描述

  • events:要监视的事件,包括 EPOLLIN(可读)、EPOLLOUT(可写)等。
  • data:用户数据,通常是文件描述符。

在这里插入图片描述

工作模式

epoll具有两种工作模式:水平触发(Level-Triggered)和边缘触发(Edge-Triggered)

水平触发

  • 特点:
    • 当文件描述符准备好时,epoll_wait返回,并且在下次仍然会触发。
    • 如果文件描述符处于就绪状态,但未处理完全部数据,下次epoll_wait仍然会返回该文件描述符。
  • 适用情况:
    • 适用于普通的I/O事件处理,适用性广泛,使用比较灵活。
  • 优点:
    • 兼容性好,适用于大多数场景。
  • 缺点:
    • 容易出现事件重复通知,可能导致性能损耗。

边缘触发

  • 特点:
    • 当文件描述符状态发生变化时,epoll_wait返回,但只返回一次,需要重新注册。
    • 仅在状态变化时通知用户,不会重复通知相同的事件。
  • 适用情况:
    • 适用于需要精确控制事件处理的高性能场景。
  • 优点:
    • 减少了事件重复通知,提高了效率。
  • 缺点:
    • 需要用户自行管理文件描述符的状态变化,可能较为复杂。

选择建议:

  • 如果对于同一个文件描述符的事件可能会频繁发生而且需要一直关注,可以选择水平触发模式。
  • 如果希望系统更高效地处理事件,并且能够减少事件通知次数,可以选择边缘触发模式。

epoll惊群问题

"惊群问题"是在使用多线程或多进程并发处理网络连接时可能遇到的一种性能问题。它的主要原因是内核中的事件通知机制会通知所有等待相同事件的线程或进程,导致多个线程或进程同时被唤醒,竞争处理同一个事件,造成资源浪费和性能下降。

epoll 中,惊群问题通常指当一个文件描述符上的事件就绪时,epoll 调用 epoll_wait 返回,然后多个线程或进程被唤醒来处理这个事件,但实际上只有一个线程或进程能够处理该事件,其他的则白白被唤醒了。

解决 epoll 中的惊群问题,可以采取以下措施:

  1. 多线程下,确保只有一个线程在epoll_wait,然后有该线程对事件进行分配给其他线程处理。
  2. 多进程下,使用一把全局互斥锁,在子进程进行epoll_wait前,则先获取锁。

优缺点

优点:

  1. 高性能: epoll使用红黑树和就绪链表管理文件描述符,能够高效地处理大量的并发连接,适用于高性能的网络服务器。
  2. 支持大规模并发: epoll不受文件描述符数量的限制,适用于处理大规模的并发连接。
  3. 事件驱动: epoll_wait函数阻塞进程直到有事件发生,避免了轮询的开销,提高了系统的效率。
  4. 边缘触发模式: epoll支持边缘触发模式,可以减少事件通知次数,提高了系统的性能。

缺点:(基本没什么缺点,挑刺)

  1. Linux专有: epoll是Linux特有的系统调用,在其他操作系统上无法直接使用。
  2. 复杂性: 使用epoll相对于selectpoll来说,需要更复杂的编程模型和更深入的理解,使用不当容易导致性能问题或者编程错误。
  3. API变化: 随着Linux内核版本的更新,epoll的API可能会发生变化,需要及时了解和适应新的API。

总结

特性selectpollepoll
跨平台性良好良好仅适用于Linux系统
效率低效,性能随文件描述符数量增加而下降低效,性能随文件描述符数量增加而下降高效,性能不随文件描述符数量增加而下降
最大文件描述符数有限制无限制无限制
编程复杂度简单简单相对较高
边缘触发
适用场景小规模并发连接小规模并发连接大规模并发连接

从上表可以看出,epoll在性能、文件描述符数量无限制和支持边缘触发等方面具有优势,适用于大规模并发连接的场景。select和poll在跨平台性和简单易用性方面具有优势,但在处理大规模并发连接时性能较差。选择合适的I/O多路复用方式应根据具体的应用需求和环境来决定。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【HarmonyOS】ArkUI - 自定义卡片样式

ArkUI - 自定义卡片样式 HarmonyOS API 9 没有提供原生的卡片样式&#xff0c;我定义了一个卡片样式&#xff0c;可以方便大家在日常开发中使用。 效果图&#xff1a; 卡片样式代码如下&#xff1a; Styles function card() {.width(95%).padding(20).backgroundColor(Col…

【LGR-176-Div.2】[yLCPC2024] 洛谷 3 月月赛 I(A~C and G<oeis>)

[yLCPC2024] A. dx 分计算 前缀和提前处理一下区间和&#xff0c;做到O&#xff08;1&#xff09;访问就可以过。 #include <bits/stdc.h> //#define int long long #define per(i,j,k) for(int (i)(j);(i)<(k);(i)) #define rep(i,j,k) for(int (i)(j);(i)>(k);…

【PowerMockito:编写单元测试过程中原方法使用@Value注解注入的属性出现空指针】

错误场景 执行到Value的属性时会出现空指针&#xff0c;因为Value的属性为null 解决方法 在测试类调用被测试方法前&#xff0c;提前设置属性值&#xff0c;属性可以先自己定义好 ReflectionTestUtils.setField(endpointConnectionService, "exportUdpList", lis…

基于Token的身份验证:安全与效率的结合

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

CSS居中对齐 (垂直居中)

内部块级元素的高度要小于容器(父元素) 方案一&#xff1a;行高 容器高度&#xff08;单行内联元素&#xff09; 限制条件&#xff1a;仅用于单行内联元素 display:inline 和 display: inline-block; 给容器添加样式 height: 100px;line-height: 100px;<!DOCTYPE html>…

2024年【P气瓶充装】考试报名及P气瓶充装复审考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 P气瓶充装考试报名是安全生产模拟考试一点通总题库中生成的一套P气瓶充装复审考试&#xff0c;安全生产模拟考试一点通上P气瓶充装作业手机同步练习。2024年【P气瓶充装】考试报名及P气瓶充装复审考试 1、【多选题】《…

python 蓝桥杯之并查集

文章目录 总述合并过程查找过程算法实战实战1 总述 并查集&#xff08;Disjoint-set Union&#xff0c;简称并查集&#xff09;是一种用来管理元素分组情况的数据结构。它主要用于解决集合的合并与查询问题&#xff0c;通常涉及到以下两种操作&#xff1a; 合并&#xff08;Uni…

SpringCloud基础

SpringCloud基础环境 1、基本环境版本选择 Java&#xff1a; Java17&#xff1b;spring cloud&#xff1a;2023.0.0&#xff1b;spring boot&#xff1a;3.2.0&#xff1b;cloud alibaba&#xff1a;2022.0.0.0-RC2&#xff1b;Maven&#xff1a;3.9&#xff1b;Mysql&#x…

Spring Boot 中使用 Redis + Aop 进行限流

Spring Boot 中使用 Redis 进行限流&#xff0c;通常你可以采用如下几种方式&#xff1a; 令牌桶算法&#xff08;Token Bucket&#xff09;漏桶算法&#xff08;Leaky Bucket&#xff09;固定窗口计数器&#xff08;Fixed Window Counter&#xff09;滑动日志窗口&#xff08…

“ReferenceError: AMap is not defined“

问题 笔者进行web开发&#xff0c;引入高德地图&#xff0c;控制台报错 "ReferenceError: AMap is not defined"详细问题 vue.runtime.esm.js:4662 [Vue warn]: Error in mounted hook: "ReferenceError: AMap is not defined"found in---> <Map&…

使用Windows API实现一个简单的串口助手

使用Windows API实现一个简单的串口助手 目录 使用window API开发一个具有字符串收发功能的串口助手 开发环境串口设备相关的API步骤实现代码收发测试图 使用window API开发一个具有字符串收发功能的串口助手 开发环境 Visual Studio 2015 串口设备相关的API CreateFile 参…

【LGR-177-Div.3】洛谷基础赛 #8 Westlake OI #1(仅A)

String Minimization a尽可能小的话&#xff0c;如果能和c交换更小就交换。 如果a和c相等&#xff0c;b交换能更小&#xff0c;就交换b。 #include <bits/stdc.h> //#define int long long #define per(i,j,k) for(int (i)(j);(i)<(k);(i)) #define rep(i,j,k) for(…