网络编程:信号、定时器、Libevent

发布时间:2023年12月23日

1. 信号

(1)信号:由用户、系统或进程发送给目标进程的信息,以通知目标进程某个状态的改变或系统异常;

可由下述条件产生:

  • 对前台进程,用户可以通过终端给它发送信号,如输入 Ctrl+C
  • 系统异常,如浮点异常等
  • 系统状态变化,如定时器到期
  • 运行 kill 命令或调用 kill 函数

1.1 信号概述

1.1.1 发送信号

一个进程给其他进程发送信号

#include <sys/types.h>
#include <signal.h>
// 成功返回 0,失败返回 -1 并设置 errno
int kill( pid_t pid, int sig );

目标进程由 pid 指定

pid取值含义
pid > 0信号发送给 PID 为 pid 的进程
pid = 0信号发送给本进程组内的其他进程
pid = -1发给除 init 进程外的所有进程,发送者需要拥有对目标进程发送信号的权限
pid < -1发给 PGID 为 -pid 的进程组的所有成员

信号值 sig 为 0,不发送任何信号(检测目标进程或进程组是否存在,这种检测方式不可靠);

errno 为 EINVAL 表示无效的信号;EPERM 该进程没有权限给目标进程发送信号;ESRCH 目标进程或进程组不存在;

1.1.2 信号处理函数
#include <signal.h>
typedef void (*__sighandler_t) ( int );

参数用来指示信号的类型,必须保证是可重入的;

#include <bits/signum.h>
#define SIG_DFL ((__sighandler_t) 0)
#define SIG_IGN ((__sighandler_t) 1)

SIG_IGN 表示忽略目标信号,SIG_DFL 表示使用信号默认处理方式,例如结束进程(Term)、忽略信号(Ign)、结束进程并生成核心转储文件(Corn)、暂停进程(Stop)、继续进程(Cont);

1.1.3 Linux 信号
信号默认行为含义
SIGHUPTerm控制终端挂起
SIGPIPETerm往读端被关闭的管道或 socket 连接中写数据
SIGURGIgnsocket 连接上接收到紧急数据
SIGALRMTerm由 alarm 或 setitimer 设置的实时闹钟超时引起
SIGCHLDIgn子进程状态发送变化(退出或暂停)
1.1.4 中断系统调用

处于阻塞状态的系统调用接收到信号,并且为该信号设置了信号处理函数,默认情况下系统调用将被中断,其返回的 errno 为 EINTR;

可以使用 sigaction 为信号设置 SA_RESTART 标志以重新启动被该信号中断的系统调用;

Linux 独有:对默认行为是 Stop 的信号,没有设置信号处理函数,也可以中断某些系统调用(如 connectepoll_wait

1.2 信号函数

1.2.1 signal系统调用

为信号设置处理函数;

#include <signal.h>
_sighandler_t signal ( int sig, _sighandler_t _handler );

成功时返回一个函数指针,指向前一次该信号处理函数的函数指针(可能是默认处理函数指针 SIG_DEF 也可能是上一次调用 signal 函数传入的函数指针)

出错时返回 SIG_ERR,设置 errno;

struct sigaction
{
	union
	{
		_sighandler_t sa_handler;
		void (*sa_sigaction) ( int, siginfo_t*, void* );
	} _sigaction_handler;

	_sigset_t sa_mask;
	int sa_flags;
	void (*sa_restorer) (void);
};

sa_handler 指定信号处理函数;sa_mask 设置进程的信号掩码;
sa_flags 设置程序收到信号时的行为;

选项含义
SA_RESTART重新调用被该信号终止的系统调用
1.2.2 sigaction 系统调用

更健壮的设置信号处理函数

// 成功时返回 0,失败返回 -1 设置 errno
int sigaction ( int sig, const struct sigaction* act, struct sigaction* oact );

act 指定新的信号处理方式,oact 输出先前的处理方式;

1.3 信号集

sigset_t 是一个长整型数组,每一位代表一个信号,共 1024 位;

1.4 网络编程相关信号

1.4.1 SIGHUP
1.4.2 SIGHUP
1.4.3 SIGURG

(1)select异常事件检测带外数据
(2)SIGURG 信号;

2. 定时器

alarm 系统调用一次只会引起一次 SIGALRM 信号;

2.1 socket 选项 SO_RCVTIMEO 和 SO_SNDTIMEO

分别用来设置 socket 接收数据 / 发送数据超时时间,数据类型为 timeval(和 select 超时参数相同);
在这里插入图片描述

2.2 SIGALRM 信号

2.2.1 基于升序链表的定时器

定时器通常包含至少两个成员:超时时间和任务回调函数;

按照超时时间做升序排序链表;

2.2.2 处理非活动连接

管理所有长时间处于非活动状态的连接

通过alarm每隔一段时间,发送 SIGALRM 信号;
有新的连接到来时,会在 connfd 上设置定时器; 每当 connfd 上有数据可读时,会重置其定时器;
超时处理函数中会检测在升序链表中是否有定时器超时,若有,则删除其 fd 上的注册事件并关闭 fd;

2.3 IO 复用系统调用的超时参数

IO 复用系统调用都可以设置超时参数,但可能会被就绪事件触发提前返回,因此需要不断更新定时参数以反馈剩余时间;

在系统调用执行前后记录当前时间,从而获得执行该系统调用的时间;

2.4 高性能定时器

2.4.1 时间轮

循环队列 + 哈希思想(类似手表),将循环队列划分为 N 个槽,槽之间的时间间隔为 SI;

插入定时器时,插入到每个 Slot 头部;

每过 SI 时间,调用 tick(),判断当前 Slot 是否有到期的定时器,并向前走一个 Slot;

2.4.2 时间堆

用最小堆实现

将堆顶定时器的超时值作为定时间隔,一旦 tick 被调用,堆顶定时器必然到期,再从剩余的定时器中找到超时时间最小的一个,并将这段时间间隔作为下一次的定时间隔;

3. 高性能 IO 框架库

3.1 概述

在这里插入图片描述

(1)句柄与事件绑定,内核通过句柄向应用程序通知事件就绪;

在 Linux 下,IO事件对应的句柄就是文件描述符,信号事件对应的句柄就是信号值;定时器事件对应固定值;

(2)事件多路分发器;统一各系统的 IO 复用系统调用;

(3)事件处理器;实现业务逻辑;与句柄绑定;

(4)Reactor;

  • handle_events;执行事件循环,重复过程:等待事件,依次调用就绪事件对应的事件处理器;
  • register_handler;往事件多路分发器中注册事件;
  • remove_handler;删除事件多路分发器中的事件;

3.2 libevent 源码分析

event 是一个事件处理器;

(1)event_base :相当于一个 Reactor 实例;

base->evsel 表示选择的 IO 复用机制,即 selectepoll等;

(2)event_new :创建事件处理器;参数为关联的 base、事件对应句柄、事件类型、具体处理函数;称为 initialized;

(3)event_add :内部调用 event_queue_insert,将事件处理器加入到各种事件队列中,根据 ev->ev_flags 进行区分;称为 pending;

(4)event_base_dispatch / event_base_loop:执行事件循环,遍历 event_base 上注册的事件直到有事件就绪,将就绪事件加入 active 事件队列中(按优先级划分的相应队列),调用相应的事件处理器;

文章来源:https://blog.csdn.net/weixin_44835655/article/details/135113232
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。