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浏览器自动化工具的优势和应用场景…...

【简单认识MySQL的MHA高可用配置】
文章目录 一、简介1、概述2、MHA 的组成3.MHA 的特点4、MHA工作原理 二、搭建MHA高可用数据库群集1.主从复制2.MHA配置 三、故障模拟四、故障修复步骤: 一、简介 1、概述 MHA(Master High Availability)是一套优秀的MySQL高可用…...

【云原生】一文学会Docker存储所有特性
目录 1.Volumes 1.Volumes使用场景 2.持久将资源存放 3. 只读挂载 2.Bind mount Bind mounts使用场景 3.tmpfs mounts使用场景 4.Bind mounts和Volumes行为上的差异 5.docker file将存储内置到镜像中 6.volumes管理 1.查看存储卷 2.删除存储卷 3.查看存储卷的详细信息…...

Android Ble蓝牙App(一)扫描
Ble蓝牙App(一)扫描 前言正文一、基本配置二、扫描准备三、扫描页面① 增加UI布局② 点击监听③ 扫描处理④ 广播处理 四、权限处理五、扫描结果① 列表适配器② 扫描结果处理③ 接收结果 六、源码 前言 关于低功耗的蓝牙介绍我已经做过很多了࿰…...

mac pd安装ubuntu并配置远程连接
背景 一个安静的下午,我又想去折腾点什么了。准备学习一下k8s的,但是没有服务器。把我给折腾的,在抱怨了:为什么M系列芯片的资源怎么这么少。 好在伙伴说,你可以尝试一下ubantu。于是,我只好在我的mac上安…...

1.3 eureka+ribbon,完成服务注册与调用,负载均衡源码追踪
本篇继先前发布的1.2 eureka注册中心,完成服务注册的内容。 目录 环境搭建 采用eurekaribbon的方式,对多个user服务发送请求,并实现负载均衡 负载均衡原理 负载均衡源码追踪 负载均衡策略 如何选择负载均衡策略? 饥饿加载…...

mysql修改字段长度是否锁表
Varchar对于小于等于255字节以内的长度可以使用一个byte 存储。大于255个字节的长度则需要使用2个byte存储 1, 如果是255长度之内的扩展,或者255之外的扩展,则不锁表,采用in-place方式执行 2, 如果从varchar长度从(0,2…...

SpringCloud集成OpenTelemetry的实现
SpringCloud项目做链路追踪,比较常见的会集成SleuthZipKin来完成,但这次的需求要集成开源框架OpenTelemetry,这里整理下实现过程。相关文章: 【SpringCloud集成SleuthZipkin进行链路追踪】 【OpenTelemetry框架Trace部分整理】 …...

Python爬取IP归属地信息及各个地区天气信息
一、实现样式 二、核心点 1、语言:Python、HTML,CSS 2、python web框架 Flask 3、三方库:requests、xpath 4、爬取网站:https://ip138.com/ 5、文档结构 三、代码 ipquery.py import requests from lxml import etree # 请求…...

RedLock + Redisson
目录 2.9 RedLock2.9.1 上述实现的分布式锁在集群状态下失效的原因2.9.2 解决方式-RedLock 2.10 redisson中的分布式锁2.10.0 redisson简介以及简单使用简单使用redisson中的锁Redisson常用配置 2.10.1 Redisson可重入锁实现原理2.10.2 公平锁(Fair Lock)…...

计算机视觉:卷积层的参数量是多少?
本文重点 卷积核的参数量是卷积神经网络中一个重要的概念,它决定了网络的复杂度和计算量。在深度学习中,卷积操作是一种常用的操作,用于提取图像、语音等数据中的特征。卷积神经网络的优势点在于稀疏连接和权值共享,这使得卷积核的参数相较于传统的神经网络要少很多。 举例…...