07 定时器处理非活动连接(上)
07 定时器处理非活动连接(上)
基础知识
非活跃,是指客户端(这里是浏览器)与服务器端建立连接后,长时间不交换数据,一直占用服务器端的文件描述符,导致连接资源的浪费。
定时事件,是指固定一段时间之后触发某段代码,由该段代码处理一个事件,如从内核事件表删除事件,并关闭文件描述符,释放连接资源。
定时器,是指利用结构体或其他形式,将多种定时事件进行封装起来。具体的,这里只涉及一种定时事件,即定期检测非活跃连接,这里将该定时事件与连接资源封装为一个结构体定时器。
定时器容量,是指使用某种容器类数据结构,将上述多个定时器组合起来,便于对定时事件统一管理。具体的,项目中使用升序链表将所有定时器串联组织起来。
整体概述
本项目中,服务器主循环为每一个连接创建一个定时器,并对每个连接进行定时。另外,利用升序时间链表容器将所有容器串联起来,若主循环接收到定时通知,则在链表中依次执行定时任务。
Linux下提供了三种定时的方法:
- sokcet选项SO_RECVTIMEO和SO_SNDTIMEO
- SIGALRM信号
- I/O复用系统调用的超时参数
三种方法没有一劳永逸的应用场景,也没有绝对的优劣。项目中使用的是SIGALRM信号。
利用alarm函数周期性地触发SIGALRM信号,信号处理函数利用管道通知主循环,主循环接收到该信号后对升序链表上所有定时器进行处理,若该段时间内没有交换数据,则将该连接关闭,释放所占用地资源。
定时器处理非活动连接模块,主要分为定时方法与信号通知流程,定时器及其容器设计与定时任务地处理。
本文内容
定时方法与信号流程,具体设计到基础API、信号通知流程和代码实现。
基础API,描述sigaction结构体,sigaction函数,sigfillset函数,SIGALRM信号、SIGTERM信号、alarm函数,socketpair函数、send函数。
信号通知流程,介绍统一事件源和信号处理机制。
代码实现,结合代码对信号处理函数地设计与使用进行详解。
基础API
sigaction结构体
struct sigaction{void (*sa_handler)(int);void (*sa_sigaction)(int,siginfo_t *,void *);sigset_t sa_mask;int sa_flags;void (*sa_restorer)(void);
}
- sa_handler是一个函数指针,指向信号处理函数
- sa_sigaction同样是信号处理函数,有三个参数,可以获得关于信号更详细的信息
- sa_mask用来指定在信号处理函数执行期间需要被屏蔽的信号
- sa_flags用于指定信号处理的行为
SA_RESTART,使被信号打断的系统调用自动重新发起
SA_NOCLDSTOP,使父进程在它的子进程暂停或继续运行时不会收到SIGCHLD信号
SA_NOCLDWAIT,使父进程在它的子进程退出时不会收到SIGCHLD信号,这时子进程如果退出也不会成为僵尸进程
SA_NODEFER,使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号
SA_RESETHAND,信号处理之后重新设置为默认的处理方式
SA_SIGINFO,使用sa_sigaction成员而不是sa_handler作为信号处理函数 - sa_restorer一般不使用
sigaction函数
#include <signal.h>int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact);
- signum表示操作的信号
- act表示对信号设置新的处理方式
- oldact表示信号原来的处理方式
- 返回值,0表示成功,-1表示有错误发生
sigfillset函数
#include <signal.h>int sigfillset(sigset_t *set);
用来将参数set信号集初始化,然后将所有的信号加入到此信号集里。
SIGALRM、SIGTREM信号
#define SIGALRM 14//由alarm系统调用产生timer时钟信号
#define SIGTERM 15//终端发送的终止信号
alarm函数
#include <unistd.h>unsigned int alarm(unsigned int seconds);
设置信号传送闹钟,即用来设置信号SIGALRM在经过参数seconds秒数后发送给目前的进程。如果未设置信号SIGALRM的处理函数,那么alarm()默认终止进程。
socketpair函数
在linux下,使用socketpair函数能够创建一对套接字进行通信,项目中使用管道通信。
#include <sys/types.h>
#include <sys/socket.h>int socketpair(int domain,int type,int protocol,int sv[2]);
- domain表示协议族,PF_UNIX或者AF_UNIX
- type表示协议,可以是SOCKSTREAM或者SOCK_DGRAM,SOCK_STREAM基于TCP,SOCK_DGRAM基于UDP
- protocol表示类型,只能未0
- sc[2]表示套接字柄对,该两个句柄作用相同,均能进行读写双向操作
- 返回结果,0创建成功,-1创建失败
send函数
#include <sys/types.h>
#include <sys/socket.h>ssize_t send(int sockfd,const void *buf,size_t len,int flags);
当套接字发送缓冲区变满后,send通常会阻塞,除非套接字设置为非阻塞模式,当缓冲区变满时,返回EAGAIN或者EWOULDBLOCK错误,此时可以调用select函数来监视何时可以发送数据。
信号通知流程
Linux下的信号采用的异步处理机制,信号处理函数和当前进程是两条不同的执行路线。具体的,当进程收到信号时,操作系统会中断当前的正常流程,转而进入信号处理函数执行操作,完成后再返回中断的地方继续执行。
为避免信号竞态现象发生,信号处理期间系统不会再次触发它。所以,为确保该信号不被屏蔽太久,信号处理函数需要尽可能快地执行完毕。
一般的信号处理函数需要处理该信号对应的逻辑,当逻辑比较复杂时,信号处理函数执行时间过长,会导致信号屏蔽太久。
这里的解决方案是,信号处理函数仅仅发送信号通知程序主循环,将信号对应的处理逻辑放在程序主循环中,由主循环执行信号对应的逻辑代码。
统一事件源
统一事件源,是指将信号事件与其他事件一样处理。
信号处理函数使用管道将信号传递给主循环,信号处理函数往管道的写端写入信号值,主循环则从管道的读端读出信号值,使用I/O复用系统调用来监听管道读端的可读事件,这样信号事件与其他文件描述符都可以通过epoll来检测,从而实现统一处理。
信号处理机制
每个进程之中,都有存着一个表,里面存着每种信号所代表的含义,内核通过设置表项中每一个位来标识对应的信号类型。

信号的接收
- 接收信号的任务是由内核代理的,当内核接收到信号后,会将其放到对应进程的信号队列中,同时向进程发送一个中断,使其陷入内核态。此时信号还只是在队列中,对进程来说暂时是不知道由信号到来的。
信号的检测
- 进程从内核态返回到用户态前进行信号检测
- 进程在内核态中,从睡眠状态被唤醒的时候进行信号检测
- 进程陷入内核态后,由两种场景会对信号进行检测
- 当发现有新信号时,便会进入下一步,信号的处理
信号的处理
- (内核)信号处理函数是运行在用户态的,调用处理函数前,内核会将当前内核栈的内容备份拷贝到用户栈上,并且修改指令寄存器(eip)将其指向信号处理函数。
- (用户)接下来进程返回到用户态中,执行相应的信号处理函数
- (内核)信号处理函数执行完成后,还需要返回内核态,检查是否还有其他信号未处理
- (用户)如果所有信号都处理完成,就会将内核栈恢复(从用户栈的备份拷贝回来),同时恢复指令寄存器(eip)将其指向中断前的运行位置,最后回到用户态继续执行进程。
至此,一个完整的信号处理流程便结束了,如果同时有多个信号到达,上面的处理流程会在第2步和第3步骤间重复进行。
代码分析
信号处理函数
自定义信号处理函数,创建sigaction结构体变量,设置信号函数。
//信号处理函数
void sig_handler(int sig)
{//为保证函数的可重入性,保证原来的errno//可重入性表示中断后再次进入该函数,环境变量与之前相同,不会丢失数据int save_errno=errno;int msg=sig;//将信号值从管道写端写入,传输字符类型,而非整型send(pipefd[1],(char *)&msg,1,0);//将原来的errno赋值为当前的errnoerrno=save_errno;
}
信号处理函数中仅仅通过管道发送信号值,不处理信号对应的逻辑,缩短异步执行时间,减少对主程序的影响。
//设置信号函数
void addsig(int sig,void(handler)(int),bool restart=true)
{//创建sigaction结构体变量struct sigaction sa;memset(&sa,'\0',sizeof(sa));//信号处理函数中仅仅发送信号值,不做对应逻辑处理sa.handler=handler;if(restart)sa.sa_flags|=SA_RESTART;//将所有信号添加到信号集中sigfillset(&sa.sa_mask);//执行sigaction函数assert(sigaction(sig,&sa,NULL)!=-1);
}
项目中设置信号函数,仅关注SIGALRM和SIGTERM两个信号。
信号通知逻辑
- 创建管道,其中管道写端写入信号值,管道读端通知I/O复用系统监测读事件
- 设置信号处理函数SIGALRM(时间到了触发)和SIGTERM(kill会触发,Ctrl+C)
通过struct sigaction结构体和sigaction函数注册信号捕捉函数
在结构体的handler参数设置信号处理函数,具体的,从管道写端写入信号的名字 - 利用I/O复用系统监听管道读端文件描述符的可读事件
- 信号值传递给主循环,主循环再根据收到的信号值执行目标信号对应的逻辑代码
代码分析
//创建管道套接字
ret=socketpair(PF_UNIX,SOCK_STREAM,0,pipefd);
assert(ret!=-1);//设置管道写端为非阻塞,为什么写端要非阻塞?
setnonblocking(pipefd[1]);//设置管道读端为ET非阻塞
addfd(epollfd,pipefd[0],false);//传递给主循环的信号值,这里只关注SIGALRM和SIGTERM
addsig(SIGALRM,sig_handler,false);
addsig(SIGTERM,sig_handler,false);//循环条件
bool stop_server=false;//c超时标志
bool timeout=false;//每隔TIMESLOT时间触发SIGALRM信号
alarm(TIMESLOT);while(!stop_server)
{//监测发生事件的文件描述符int number=epoll_wait(epollfd,events,MAX_EVENT_NUMBER,-1);if(number<0&&errno!=EINTR){break;}//轮询文件描述符for(int i=0;i<number;i++){int sockfd=events[i].data.fd;//管道读端对应文件描述符发生读事件if((sockfd==pipefd[0])&&(events[i].events&EPOLLIN)){int sig;char signals[1024];//从管道读端读出信号值,成功返回字节数,失败返回-1//正常情况下,这里的ret返回值总是1,只有14和15两个ASCII码对应的字符ret=recv(pipefd[0],signals,sizeof(signals),0);if(ret==-1){//handler the errorcontinue;}else if(ret==0){continue;}else{//处理信号值对应的逻辑for(int i=0;i<ret;++i){//这里面是字符switch(signals[i]){//这里是整型case SIGALRM:{timeout=true;break;}case SIGTERM:{stop_server=true;}}}}}}
}
为什么管道写端要非阻塞?
send是将信息发送给套接字缓冲区,如果缓冲区满了,则会阻塞,这时候会进一步增加信号处理函数的执行事件,为此,将其修改为非阻塞。
没有对非阻塞返回值处理,如果阻塞是不是意味着这一次定时事件失效了?
是的,但定时事件是非必须立即处理的事件,可以允许这样的情况发生。
管道传递的是什么类型?switch-case的变量冲突?
信号本身是整型数值,管道中传递的是ASCII码表中整型数值对应的字符。
switch的变量一般为字符或整型,当switch的变量为字符时,case可以是字符,也可以是字符对应的ASCII码。
相关文章:
07 定时器处理非活动连接(上)
07 定时器处理非活动连接(上) 基础知识 非活跃,是指客户端(这里是浏览器)与服务器端建立连接后,长时间不交换数据,一直占用服务器端的文件描述符,导致连接资源的浪费。 定时事件&a…...
python——案例四:判断字符串中的元素组成
案例四:判断字符串中的元素组成str"Hello World! 666" print(str.isalnum()) #判读所有的字符都是数字或者是字母 print(str.isalpha()) #判读所有的字符都是字母 print(str.isdigit()) #判读所有的字符都是数字 print(str.islower()) #判读所有的字符都是…...
一起学算法(插入排序篇)
概念: 插入排序(inertion Sort)一般也被称为直接插入排序,是一种简单的直观的排序算法 工作原理:将待排列元素划分为(已排序)和(未排序)两部分,每次从&…...
JVM基础篇-本地方法栈与堆
JVM基础篇-本地方法栈与堆 本地方法栈 什么是本地方法? 本地方法即那些不是由java层面实现的方法,而是由c/c实现交给java层面进行调用,这些方法在java中使用native关键字标识 public native int hashCode()本地方法栈的作用? 为本地方法提供内存空…...
防雷保护区如何划分,防雷分区概念LPZ介绍
在防雷设计中,很重要的一点就是防雷分区的划分,只有先划分好防雷区域等级,才好做出比较好的防雷器设计方案。 因为标准对不同区安装的防雷浪涌保护器要求是不一样的。 那么,防雷保护区是如何划分的呢? 如上图所示&…...
随手笔记——3D−3D:ICP求解
随手笔记——3D−3D:ICP求解 使用 SVD 求解 ICP使用非线性优化来求解 ICP 原理参见 https://blog.csdn.net/jppdss/article/details/131919483 使用 SVD 求解 ICP 使用两幅 RGB-D 图像,通过特征匹配获取两组 3D 点,最后用 ICP 计算它们的位…...
Python调用各大机器翻译API大全
过去的二三年中,我一直关注的是机器翻译API在自动化翻译过程中的应用,包括采用CAT工具和Python编程语言来调用机器翻译API,然后再进行译后编辑,从而达到快速翻译的目的。 然而,我发现随着人工智能的发展,很…...
重生之我要学C++第六天
这篇文章的主要内容是const以及权限问题、static关键字、友元函数和友元类,希望对大家有所帮助,点赞收藏评论支持一下吧! 更多优质内容跳转: 专栏:重生之C启程(文章平均质量分93) 目录 const以及权限问题 1.const修饰…...
SpringBoot中ErrorPage(错误页面)的使用--【ErrorPage组件】
SpringBoot系列文章目录 SpringBoot知识范围-学习步骤–【思维导图知识范围】 文章目录 SpringBoot系列文章目录本系列校训 SpringBoot技术很多很多环境及工具:必要的知识深层一些的知识 上效果图在Spring Boot里使用ErrorPage还要注意的是 配套资源作业ÿ…...
【Android】APP网络优化学习笔记
网络优化原因 进行网络优化对于移动应用程序而言非常重要,原因如下: 用户体验: 网络连接是移动应用程序的核心功能之一。通过进行网络优化,可以提高应用的加载速度和响应速度,减少用户等待时间,提供更流…...
简单的知识图谱可视化+绘制nx.Graph()时报错TypeError: ‘_AxesStack‘ object is not callable
绘制nx.Graph时报错TypeError: _AxesStack object is not callable 写在最前面知识图谱可视化预期报错可能的原因 原代码原因确认解决后的代码解决! 写在最前面 实现一个简单的知识图谱的可视化功能。 使用了NetworkX库来构建知识图谱,并使用matplotlib…...
【Matlab】基于粒子群优化算法优化BP神经网络的时间序列预测(Excel可直接替换数据)
【Matlab】基于粒子群优化算法优化BP神经网络的时间序列预测(Excel可直接替换数据) 1.模型原理2.数学公式3.文件结构4.Excel数据5.分块代码5.1 fun.m5.2 main.m6.完整代码6.1 fun.m6.2 main.m7.运行结果1.模型原理 基于粒子群优化算法(Particle Swarm Optimization, PSO)优…...
【机器学习】Cost Function for Logistic Regression
Cost Function for Logistic Regression 1. 平方差能否用于逻辑回归?2. 逻辑损失函数loss3. 损失函数cost附录 导入所需的库 import numpy as np %matplotlib widget import matplotlib.pyplot as plt from plt_logistic_loss import plt_logistic_cost, plt_two_…...
【EI/SCOPUS会议征稿】2023年第四届新能源与电气科技国际学术研讨会 (ISNEET 2023)
作为全球科技创新大趋势的引领者,中国一直在为科技创新创造越来越开放的环境,提高学术合作的深度和广度,构建惠及全民的创新共同体。这些努力为全球化和创建共享未来的共同体做出了新的贡献。 为交流近年来国内外在新能源和电气技术领域的最新…...
【计算机网络】10、ethtool
文章目录 一、ethtool1.1 常见操作1.1.1 展示设备属性1.1.2 改变网卡属性1.1.2.1 Auto-negotiation1.1.2.2 Speed 1.1.3 展示网卡驱动设置1.1.4 只展示 Auto-negotiation, RX and TX1.1.5 展示统计1.1.7 排除网络故障1.1.8 通过网口的 LED 区分网卡1.1.9 持久化配置(…...
什么是前端工程化?
工程化介绍 什么是前端工程化? 前端工程化是一种思想,而不是某种技术。主要目的是为了提高效率和降低成本,也就是说在开发的过程中可以提高开发效率,减少不必要的重复性工作等。 tip 现实生活举例 建房子谁不会呢?请…...
【深度学习】【三维重建】windows11环境配置tiny-cuda-nn详细教程
【深度学习】【三维重建】windows11环境配置tiny-cuda-nn详细教程 文章目录 【深度学习】【三维重建】windows11环境配置tiny-cuda-nn详细教程前言确定版本对应关系源码编译安装tiny-cuda-nn总结 前言 本人windows11下使用【Instant Neural Surface Reconstruction】算法时需要…...
Matlab 一种自适应搜索半径的特征提取方法
文章目录 一、简介二、实现代码参考资料一、简介 在之前的博客(C++ ID3决策树)中,提到过一种信息熵的概念,其中它表达的大致意思为:香农认为熵是指“当一件事情有多种可能情况时,这件事情发生某种情况的不确定性”,也就是指如果一个事情的不确定性越大,那么这个信息的熵…...
基于opencv的几种图像滤波
一、介绍 盒式滤波、均值滤波、高斯滤波、中值滤波、双边滤波、导向滤波。 boxFilter() blur() GaussianBlur() medianBlur() bilateralFilter() 二、代码 #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> …...
puppeteer代理的搭建和配置
puppeteer代理的搭建和配置 本文深入探讨了Puppeteer在网络爬虫和自动化测试中的重要角色,着重介绍了如何搭建和配置代理服务器,以优化Puppeteer的功能和性能。文章首先介绍了Puppeteer作为一个强大的Headless浏览器自动化工具的优势和应用场景…...
多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...
51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...
大型活动交通拥堵治理的视觉算法应用
大型活动下智慧交通的视觉分析应用 一、背景与挑战 大型活动(如演唱会、马拉松赛事、高考中考等)期间,城市交通面临瞬时人流车流激增、传统摄像头模糊、交通拥堵识别滞后等问题。以演唱会为例,暖城商圈曾因观众集中离场导致周边…...
SCAU期末笔记 - 数据分析与数据挖掘题库解析
这门怎么题库答案不全啊日 来简单学一下子来 一、选择题(可多选) 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘:专注于发现数据中…...
Leetcode 3577. Count the Number of Computer Unlocking Permutations
Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接:3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯,要想要能够将所有的电脑解锁&#x…...
2.Vue编写一个app
1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...
ffmpeg(四):滤镜命令
FFmpeg 的滤镜命令是用于音视频处理中的强大工具,可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下: ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜: ffmpeg…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...
docker 部署发现spring.profiles.active 问题
报错: org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property spring.profiles.active imported from location class path resource [application-test.yml] is invalid in a profile specific resource [origin: class path re…...
mac 安装homebrew (nvm 及git)
mac 安装nvm 及git 万恶之源 mac 安装这些东西离不开Xcode。及homebrew 一、先说安装git步骤 通用: 方法一:使用 Homebrew 安装 Git(推荐) 步骤如下:打开终端(Terminal.app) 1.安装 Homebrew…...
