RT-DETR:可以满足实时性要求的DETR模型
本文分享自华为云社区《高性能网络设计秘笈:深入剖析Linux网络IO与epoll》,作者: Lion Long 。
一、epoll简介
epoll是Linux内核中一种可扩展的IO事件处理机制,可替代select和poll的系统调用。处理百万级并发访问性能更佳。
二、select的局限性
(1) 文件描述符越多,性能越差。 单个进程中能够监视的文件描述符存在最大的数量,默认是1024(在linux内核头文件中定义有 #define _FD_SETSIZE 1024),当然也可以修改,但是文件描述符数量越多,性能越差。
(2)开销巨大 ,select需要复制大量的句柄数据结构,产生了巨大的开销(内核/用户空间内存拷贝问题)。
(3)select需要遍历整个句柄数组才能知道哪些句柄有事件。
(4)如果没有完成对一个已经就绪的文件描述符的IO操作,那么每次调用select还是会将这些文件描述符通知进程,即水平触发。
(5)poll使用链表保存监视的文件描述符,虽然没有了监视文件数量的限制,但是其他缺点依旧存在。
由于以上缺点,基于select模型的服务器程序,要达到十万以上的并发访问,是很难完成的。因此,epoll出场了。
三、epoll的优点
(1)不需要轮询所有的文件描述符
(2)每次取就绪集合,都在固定位置
(3)事件的就绪和IO触发可以异步解耦
四、epoll函数原型
4.1、epoll_create(int size)
#include <sys/epoll.h> int epoll_create(int size);
功能:创建epoll的文件描述符。
参数说明:size表示内核需要监控的最大数量,但是这个参数内核已经不会用到,只要传入一个大于0的值即可。 当size<=0时,会直接返回不可用,这是历史原因保留下来的,最早的epoll_create是需要定义一次性就绪的最大数量;后来使用了链表以便便维护和扩展,就不再需要使用传入的参数。
返回:返回该对象的描述符,注意要使用 close 关闭该描述符。
4.2、epoll_ctl
#include <sys/epoll.h> int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); // epoll_ctl对应系统调用sys_epoll_ctl
功能:操作epoll的文件描述符,主要是对epoll的红黑树节点进行操作,比如节点的增删改查。
参数说明:

4.2.1、event参数说明
struct epoll_event结构体原型
typedef union epoll_data{
void* ptr;
int fd;
uint32_t u32;
uint64_t u64
};
struct epoll_event{
uint32_t events;
epoll_data_t data;
}
events成员代表要监听的epoll事件类型
events成员:

data成员:
data 成员时一个联合体类型,可以在调用 epoll_ctl 给 fd 添加/修改描述符监听的事件时携带一些数据,方便后面的epoll_wait可以取出信息使用。
4.2.2、扩展说明:SYSCALL_DEFINE数字 的宏定义
跟着的数字代表函数需要的参数数量,比如SYSCALL_DEFINE1代表函数需要一个参数、SYSCALL_DEFINE4代表函数需要4个参数。
4.2.3、注意
epoll_ctl是非阻塞的,不会被挂起。
4.3、epoll_wait
函数原型
#include <sys/epoll.h> int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
功能:阻塞一段时间,等待事件发生
返回:返回事件数量,事件集添加到events数组中。也就是遍历红黑树中的双向链表,把双向链表中的节点数据拷贝出来,拷贝完毕后把节点从双向链表中移除。

五、epoll使用步骤
step 1:创建epoll文件描述符
int epfd = epoll_create(1);
step 2:创建struct epoll_event结构体
struct epoll_event ev; ev.data.fd=listenfd;//保存监听的fd,以便epoll_wait的后续操作 ev.events=EPOLLIN;//设置监听fd的可读事件
step 3:添加事件监听
epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
step 4:等待事件
struct epoll_event events[EVENTS_LENGTH];
char rbuffer[MAX_BUFF]={ 0 };
char wbuffer[MAX_BUFF]={ 0 };
while(1)
{
int nready = epoll_wait(epfd,events,EVENTS_LENGTH,-1);//-1表示阻塞等待
int i=0;
for(i=0;i<nready;i++)
{
int clientfd=events[i].data.fd;
if(clientfd==listenfd)
{
struct sockaddr_in client;
int len=sizeof(client);
int confd=accept(listenfd,(struct sockaddr*)&client,&len);
//step 2:创建struct epoll_event结构体
struct epoll_event evt;
evt.data.fd=confd;//保存监听的fd,以便epoll_wait的后续操作
evt.events=EPOLLIN;//设置监听fd的可读事件
// step 3:添加事件监听
epoll_ctl(epfd,EPOLL_CTL_ADD,confd,&evt);
}
else if(events[i].events &EPOLLIN)
{
int ret = recv(clientfd,rbuffer,MAX_BUFF,0);
if(ret>0)
{
rbuffer[ret]='\0';//剔除干扰数据
printf("recv: %s\n",rbuffer);
memcpy(wbuffer,rbuffer,MAX_BUFF);//拷贝数据,做回传示例
//step 2:创建struct epoll_event结构体
struct epoll_event evt;
evt.data.fd=clientfd;//保存监听的fd,以便epoll_wait的后续操作
evt.events=EPOLLOUT;//设置监听fd的可写事件
// step 3:修改事件监听
epoll_ctl(epfd,EPOLL_CTL_MOD,clientfd,&evt);
}
}
else if(events[i].events &EPOLLOUT)
{
int ret = send(clientfd,wbuffer,MAX_BUFF,0);
printf("send: %s\n",wbuffer);
//step 2:创建struct epoll_event结构体
struct epoll_event evt;
evt.data.fd=clientfd;//保存监听的fd,以便epoll_wait的后续操作
evt.events=EPOLLIN;//设置监听fd的可读事件
// step 3:修改事件监听
epoll_ctl(epfd,EPOLL_CTL_MOD,clientfd,&evt);
}
}
}
六、完整示例代码
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/epoll.h>
#include <string.h>
#define BUFFER_LENGTH 128
#define EVENTS_LENGTH 128
char rbuff[BUFFER_LENGTH] = { 0 };
char wbuff[BUFFER_LENGTH] = { 0 };
int main() {
// block
int listenfd = socket(AF_INET, SOCK_STREAM, 0); //
if (listenfd == -1) return -1;
// listenfd
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(9999);
if (-1 == bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr))) {
return -2;
}
#if 0 // nonblock
int flag = fcntl(listenfd, F_GETFL, 0);
flag |= O_NONBLOCK;
fcntl(listenfd, F_SETFL, flag);
#endif
listen(listenfd, 10);
int epfd = epoll_create(1);
struct epoll_event ev, events[EVENTS_LENGTH];
ev.events = EPOLLIN;
ev.data.fd = listenfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);
printf("epfd : %d\n", epfd);
while (1)
{
int nready = epoll_wait(epfd, events, EVENTS_LENGTH, -1);
printf("nready --> %d\n",nready);
int i;
for (i = 0; i < nready;i++)
{
int clientfd = events[i].data.fd;
if (listenfd == clientfd)
{
// accept
struct sockaddr_in client;
int len = sizeof(client);
int conffd = accept(clientfd, (struct sockaddr*)&client,&len);
printf("conffd --> %d\n",conffd);
ev.events = EPOLLIN;
ev.data.fd = conffd;
epoll_ctl(epfd, EPOLL_CTL_ADD, conffd, &ev);
}
else if(events[i].events & EPOLLIN)//client
{
int ret=recv(clientfd, rbuff, BUFFER_LENGTH, 0);
if (ret > 0)
{
rbuff[ret] = '\0';
printf("recv buffer: %s\n", rbuff);
/*
int j;
for (j = 0; j < BUFFER_LENGTH;j++)
{
buff[j] = 'a' + (j % 26);
}
send(clientfd, buff, BUFFER_LENGTH, 0);
*/
memcpy(wbuff, rbuff, BUFFER_LENGTH);
ev.events = EPOLLOUT;
ev.data.fd = clientfd;
epoll_ctl(epfd, EPOLL_CTL_MOD, clientfd, &ev);
}
}
else if (events[i].events & EPOLLOUT)
{
send(clientfd, wbuff, BUFFER_LENGTH, 0);
printf("send --> %s\n",wbuff);
ev.events = EPOLLIN;
ev.data.fd = clientfd;
epoll_ctl(epfd, EPOLL_CTL_MOD, clientfd, &ev);
}
}
}
return 0;
}
七、epoll的缺点
读写使用相同的缓冲区。比如上述的示例中,wbuffer和rbuffer是使用同一个缓冲区的,所以需要rbuff[ret] = ‘\0’;去除杂数据。
八、水平触发(LT)与边沿触发(ET)
8.1、两者差异
1、水平触发可以一次recv,边沿触发需要用循环来recv;
2、水平触发可以使用阻塞模式,边沿模式不能
3、两者性能差异非常小,一般小数据使用水平触发LT,大数据使用边沿触发ET
4、listen fd最好使用水平触发,尽量不要边沿触发
5、当当recv的buffer小于接受的数据时:
(1)水平触发是只要有数据就一直触发,直到数据读完;
(2)边沿触发是来一次连接触发一次,如果接受数据的buffer不够大,则数据会保留在缓冲区,下次触发继续从缓冲区读出来;
6、一般,水平触发只需要一个recv,边沿触发需要搭配while从缓冲区读完数据
8.2、设置触发模式
默认是水平触发模式,在事件中设置中 | EPOLLET 就可以设置边沿触发,不设置则默认是水平触发。
例如:
ev.events=EPOLL_IN | EPOLLET
九、常见疑惑问题
9.1、为什么提前先定义一个事件?
我们需要注册,内核才会有事件来的时候通知进程。比如生活中要退一个快递,那么我们需要注册一个快递公司的账户,然后发送一个退快递请求时快递公司才能找到你并取快递。
9.2、epoll events超出EVENTS_LENGTH?
epoll会循环拷贝红黑树结构体中的双向链表节点,读取节点数据,直到没有事件。
9.3、缓冲区有多大空间时才返回可读/可写?
只要缓冲区有空间就返回可读、可写,不管空间多少。比如缓冲区是1024,但是有1023有数据了,这种极端条件也会返回可读、可写。
9.4、recv和send放在一起时,有什么问题?
发送给客户端数据很大的时候(大于内核缓冲区),就可能出现send不全,客户端recv不全,最好用EPOLLOUT单独处理发送数据事件。
总结
本文介绍了网络IO模型,引入了epoll作为Linux系统中高性能网络编程的核心工具。通过分析epoll的特点与优势,并给出使用epoll的注意事项和实践技巧,该文章为读者提供了宝贵的指导。通过掌握这些知识,读者能够构建高效、可扩展和稳定的网络应用,提供出色的用户体验。
RT-DETR:可以满足实时性要求的DETR模型的更多相关文章
- Linux 2.6 内核实时性分析 (完善中...)
经过一个月的学习,目前对linux 下驱动程序的编写有了入门的认识,现在需要着手实践,编写相关的驱动程序. 因为飞控系统对实时性有一定的要求,所以先打算学习linux 2.6 内核的实时性与任务调 ...
- Linux操作系统实时性分析
1. 概述 选择一个合适的嵌入式操作系统,可以考虑以下几个因素: 第一是应用.如果你想开发的嵌入式设备是一个和网络应用密切相关或者就是一个网络设备,那么你应该选择用嵌入式Linux或者uCLinux ...
- 【原创】xenomai与VxWorks实时性对比(资源抢占上下文切换对比)
版权声明:本文为本文为博主原创文章,转载请注明出处.如有问题,欢迎指正.博客地址:https://www.cnblogs.com/wsg1100/ (下面数据,仅供个人参考) 可能大部分人一直好奇Vx ...
- 【原创】有利于提高xenomai 实时性的一些配置建议
版权声明:本文为本文为博主原创文章,转载请注明出处.如有错误,欢迎指正. @ 目录 一.影响因素 1.硬件 2.BISO(X86平台) 3.软件 4. 缓存使用策略与GPU 二.优化措施 1. BIO ...
- CNC系统实时性分析
该系统有哪些强实时功能需求?需要对哪些实时事件进行实时响应,对允许的实时延迟的数量级进行估计. 答:数控系统中控制装置与现场设备通信的实时性要求,数控系统要快速而有效的对复杂而庞大的操作任务进行处理. ...
- PROFINET如何实现实时性
平时我们都听过文艺作品要“源于生活而高于生活”.PROFINET是基于工业以太网的,用文艺范儿的词汇说就是“源于以太网而高于以太网”.那么,PROFINET是怎么做到“高于以太网”的呢? 要做到比普通 ...
- 什么是PROFINET IO系统的实时性
实时系统是指系统能及时响应外部事件的请求,在规定的时间内完成对该事件的处理,并控制所有实时任务协调一致的运行. PROFINET IO系统的实时性就是指当有一个外部事件发生时,从输入信号到传输.到控制 ...
- 用Vue开发一个实时性时间转换功能,看这篇文章就够了
前言 最近有一个说法,如果你看见某个网站的某个功能,你就大概能猜出背后的业务逻辑是怎么样的,以及你能动手开发一个一毛一样的功能,那么你的前端技能算是进阶中高级水平了.比如咱们今天要聊的这个话题:如何用 ...
- 针对Properties中实时性要求不高的配置参数,用Java缓存起来
Properties常用于项目中参数的配置,当项目中某段程序需要获取动态参数时,就从Properties中读取该参数,使程序是可配置的.灵活的. 有些配置参数要求立即生效,有些则未必: 一.实时性要求 ...
- 为树莓派添加一个强实时性前端[原创cnblogs.com/helesheng]
树莓派是最近流行嵌入式平台,其自由的开源特性以及低廉的价格,吸引了来 自全球的大量极客和计算机大咖的关注.来自各大树莓派社区的幕后英雄,无私地在这个开源硬件平台上做了大量的工作,将其打造成了世界上通用 ...
随机推荐
- CF433B
题目简化和分析: 为了更加快速的求出答案,好像没前缀和快速. 为了大家更好的理解线段树,我们使用了线段树. 如果您并不了解线段树,可以转战模板. 因为我们知道线段树可以快速求区间和,于是我们建两棵树. ...
- js滚动条滚动到底部和顶部
<!DOCTYPE html> <html> <head> <title></title> <style type="tex ...
- keepalived部署+nginx高可用
nginx+keepalived搞性能web网络架构实战配置: 环境准备: keepalived+nginx-1: 192.168.1.23 keepalived+nginx-2: 192.168.1 ...
- SQL Server数据库创建远程服务器备份计划(小白详细图文教程)
一.前言 最近项目系统做安全加固,以前是本地备份,现在需要做远程内网服务器数据库备份,后期也有可能做异地备份.下面以SQL Server2016 内网服务器数据库备份为例, 数据库服务器地址:192. ...
- 字符串小记 II:字符串自动机
OI 中的自动机指的是"有限状态自动机",它是对一串信号进行处理的数学模型,一般由以下三部分构成: 字符集(\(\Sigma\)),能够输入进自动机的字符集合. 状态集合(\(Q\ ...
- 一元多项式求和(c++源码)
LinkList.h #ifndef LINKLIST_H_ #define LINKLIST_H_ #include<stdio.h> template<class T> s ...
- 提升运维效率:轻松掌握JumpServer安装和使用技巧
前言 JumpServer 是一个开源的跳板机的解决方案,提供了对远程服务器的安全访问.会话录制和审计.用户身份管理等功能,适用于需要管理机器资源&大量服务器资源的情况. 本文将在分享 doc ...
- 编译wasm Web应用
刚学完WebAssembly的入门课,卖弄一点入门知识. 首先我们知道wasm是目标语言,是一种新的V-ISA标准,所以编写wasm应用,正常来说不会直接使用WAT可读文本格式,更不会用wasm字节码 ...
- JavaSE面试题01:自增变量
JavaSE面试题:自增变量 来源:https://runwsh.com/ 代码 public static void main(String[] args) { int i=1; i=i++; in ...
- 【scipy 基础】--信号处理
scipy.signal模块主要用于处理和分析信号.它提供了大量的函数和方法,用于滤波.卷积.傅里叶变换.噪声生成.周期检测.谱分析等信号处理任务. 此模块的主要作用是提供一套完整的信号处理工具,从而 ...