日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

定时器 槽函数没执行_Web服务器项目详解 07 定时器处理非活动连接(上)

發(fā)布時(shí)間:2023/12/15 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 定时器 槽函数没执行_Web服务器项目详解 07 定时器处理非活动连接(上) 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

點(diǎn)擊“兩猿社”

?

關(guān)注我們

?

Web服務(wù)器詳解目錄

  • 00 項(xiàng)目概述

  • 01 線程同步機(jī)制包裝類

  • 02 半同步/半反應(yīng)堆線程池(上)

  • 03 半同步/半反應(yīng)堆線程池(下)

  • 04 http連接處理(上)

  • 05 http連接處理(中)

  • 06 http連接處理(下)

  • 07 定時(shí)器處理非活動(dòng)連接(上)

  • 08 定時(shí)器處理非活動(dòng)連接(下)

  • 09 日志系統(tǒng)(上)

  • 10 日志系統(tǒng)(下)

  • 11 數(shù)據(jù)連接池

  • 12 注冊和登錄校驗(yàn)

  • 13 服務(wù)器測試

  • 14 項(xiàng)目遇到的問題及解決方案

  • 15 項(xiàng)目涉及的常見面試題

基礎(chǔ)知識(shí)

非活躍,是指客戶端(這里是瀏覽器)與服務(wù)器端建立連接后,長時(shí)間不交換數(shù)據(jù),一直占用服務(wù)器端的文件描述符,導(dǎo)致連接資源的浪費(fèi)。

定時(shí)事件,是指固定一段時(shí)間之后觸發(fā)某段代碼,由該段代碼處理一個(gè)事件,如定期檢測非活躍連接。

定時(shí)器,是指利用結(jié)構(gòu)體或其他形式,將多種定時(shí)事件進(jìn)行封裝起來。具體的,這里只涉及一種定時(shí)事件,即定期檢測非活躍連接,這里將該定時(shí)事件與連接資源封裝為一個(gè)結(jié)構(gòu)體定時(shí)器。

定時(shí)器容器,是指使用某種容器類數(shù)據(jù)結(jié)構(gòu),將上述多個(gè)定時(shí)器組合起來,便于對定時(shí)事件統(tǒng)一管理。具體的,項(xiàng)目中使用升序鏈表將所有定時(shí)器串聯(lián)組織起來。

整體概述

本項(xiàng)目中,服務(wù)器主循環(huán)為每一個(gè)連接創(chuàng)建一個(gè)定時(shí)器,并對每個(gè)連接進(jìn)行定時(shí)。另外,利用升序時(shí)間鏈表容器將所有定時(shí)器串聯(lián)起來,若主循環(huán)接收到定時(shí)通知,則在鏈表中依次執(zhí)行定時(shí)任務(wù)。

Linux下提供了三種定時(shí)的方法:

  • socket選項(xiàng)SO_RECVTIMEO和SO_SNDTIMEO

  • SIGALRM信號(hào)

  • I/O復(fù)用系統(tǒng)調(diào)用的超時(shí)參數(shù)

三種方法沒有一勞永逸的應(yīng)用場景,也沒有絕對的優(yōu)劣。由于項(xiàng)目中使用的是SIGALRM信號(hào),這里僅對其進(jìn)行介紹,另外兩種方法可以查閱游雙的Linux高性能服務(wù)器編程 第11章 定時(shí)器。

具體的,利用alarm函數(shù)周期性地觸發(fā)SIGALRM信號(hào),信號(hào)處理函數(shù)利用管道通知主循環(huán),主循環(huán)接收到該信號(hào)后對升序鏈表上所有定時(shí)器進(jìn)行處理,若該段時(shí)間內(nèi)沒有交換數(shù)據(jù),則將該連接關(guān)閉,釋放所占用的資源。

從上面的簡要描述中,可以看出定時(shí)器處理非活動(dòng)連接模塊,主要分為兩部分,其一為定時(shí)方法與信號(hào)通知流程,其二為定時(shí)器及其容器設(shè)計(jì)與定時(shí)任務(wù)的處理。

本文內(nèi)容

本篇將介紹定時(shí)方法與信號(hào)通知流程,具體的涉及到基礎(chǔ)API、信號(hào)通知流程和代碼實(shí)現(xiàn)。

基礎(chǔ)API,描述sigaction結(jié)構(gòu)體、sigaction函數(shù)、sigfillset函數(shù)、SIGALRM信號(hào)、SIGTERM信號(hào)、alarm函數(shù)、socketpair函數(shù)、send函數(shù)。

信號(hào)通知流程,介紹統(tǒng)一事件源和信號(hào)處理機(jī)制。

代碼實(shí)現(xiàn),結(jié)合代碼對信號(hào)處理函數(shù)的設(shè)計(jì)與使用進(jìn)行詳解。

基礎(chǔ)API

為了更好的源碼閱讀體驗(yàn),這里提前對代碼中使用的一些API進(jìn)行簡要介紹,更豐富的用法可以自行查閱資料。

sigaction結(jié)構(gòu)體

1struct?sigaction?{
2????void?(*sa_handler)(int);
3????void?(*sa_sigaction)(int,?siginfo_t?*,?void?*);
4????sigset_t?sa_mask;
5????int?sa_flags;
6????void?(*sa_restorer)(void);
7}
  • sa_handler是一個(gè)函數(shù)指針,指向信號(hào)處理函數(shù)

  • sa_sigaction同樣是信號(hào)處理函數(shù),有三個(gè)參數(shù),可以獲得關(guān)于信號(hào)更詳細(xì)的信息

  • sa_mask用來指定在信號(hào)處理函數(shù)執(zhí)行期間需要被屏蔽的信號(hào)

  • sa_flags用于指定信號(hào)處理的行為

    • SA_RESTART,使被信號(hào)打斷的系統(tǒng)調(diào)用自動(dòng)重新發(fā)起

    • SA_NOCLDSTOP,使父進(jìn)程在它的子進(jìn)程暫停或繼續(xù)運(yùn)行時(shí)不會(huì)收到 SIGCHLD 信號(hào)

    • SA_NOCLDWAIT,使父進(jìn)程在它的子進(jìn)程退出時(shí)不會(huì)收到 SIGCHLD 信號(hào),這時(shí)子進(jìn)程如果退出也不會(huì)成為僵尸進(jìn)程

    • SA_NODEFER,使對信號(hào)的屏蔽無效,即在信號(hào)處理函數(shù)執(zhí)行期間仍能發(fā)出這個(gè)信號(hào)

    • SA_RESETHAND,信號(hào)處理之后重新設(shè)置為默認(rèn)的處理方式

    • SA_SIGINFO,使用 sa_sigaction 成員而不是 sa_handler 作為信號(hào)處理函數(shù)

  • sa_restorer一般不使用

sigaction函數(shù)

1#include?
2
3int?sigaction(int?signum,?const?struct?sigaction?*act,?struct?sigaction?*oldact);
  • signum表示操作的信號(hào)。

  • act表示對信號(hào)設(shè)置新的處理方式。

  • oldact表示信號(hào)原來的處理方式。

  • 返回值,0 表示成功,-1 表示有錯(cuò)誤發(fā)生。

sigfillset函數(shù)

1#include?
2
3int?sigfillset(sigset_t?*set);

用來將參數(shù)set信號(hào)集初始化,然后把所有的信號(hào)加入到此信號(hào)集里。

SIGALRM、SIGTERM信號(hào)

1#define?SIGALRM??14?????//由alarm系統(tǒng)調(diào)用產(chǎn)生timer時(shí)鐘信號(hào)
2#define?SIGTERM??15?????//終端發(fā)送的終止信號(hào)

alarm函數(shù)

1#include?;
2
3unsigned?int?alarm(unsigned?int?seconds);

設(shè)置信號(hào)傳送鬧鐘,即用來設(shè)置信號(hào)SIGALRM在經(jīng)過參數(shù)seconds秒數(shù)后發(fā)送給目前的進(jìn)程。如果未設(shè)置信號(hào)SIGALRM的處理函數(shù),那么alarm()默認(rèn)處理終止進(jìn)程.

socketpair函數(shù)

在linux下,使用socketpair函數(shù)能夠創(chuàng)建一對套接字進(jìn)行通信,項(xiàng)目中使用管道通信。

1#include?
2#include?
3
4int?socketpair(int?domain,?int?type,?int?protocol,?int?sv[2]);
  • domain表示協(xié)議族,PF_UNIX或者AF_UNIX

  • type表示協(xié)議,可以是SOCK_STREAM或者SOCK_DGRAM,SOCK_STREAM基于TCP,SOCK_DGRAM基于UDP

  • protocol表示類型,只能為0

  • sv[2]表示套節(jié)字柄對,該兩個(gè)句柄作用相同,均能進(jìn)行讀寫雙向操作

  • 返回結(jié)果, 0為創(chuàng)建成功,-1為創(chuàng)建失敗

send函數(shù)

1#include?
2#include?
3
4ssize_t?send(int?sockfd,?const?void?*buf,?size_t?len,?int?flags);

當(dāng)套接字發(fā)送緩沖區(qū)變滿時(shí),send通常會(huì)阻塞,除非套接字設(shè)置為非阻塞模式,當(dāng)緩沖區(qū)變滿時(shí),返回EAGAIN或者EWOULDBLOCK錯(cuò)誤,此時(shí)可以調(diào)用select函數(shù)來監(jiān)視何時(shí)可以發(fā)送數(shù)據(jù)。

信號(hào)通知流程

Linux下的信號(hào)采用的異步處理機(jī)制,信號(hào)處理函數(shù)和當(dāng)前進(jìn)程是兩條不同的執(zhí)行路線。具體的,當(dāng)進(jìn)程收到信號(hào)時(shí),操作系統(tǒng)會(huì)中斷進(jìn)程當(dāng)前的正常流程,轉(zhuǎn)而進(jìn)入信號(hào)處理函數(shù)執(zhí)行操作,完成后再返回中斷的地方繼續(xù)執(zhí)行。

為避免信號(hào)競態(tài)現(xiàn)象發(fā)生,信號(hào)處理期間系統(tǒng)不會(huì)再次觸發(fā)它。所以,為確保該信號(hào)不被屏蔽太久,信號(hào)處理函數(shù)需要盡可能快地執(zhí)行完畢。

一般的信號(hào)處理函數(shù)需要處理該信號(hào)對應(yīng)的邏輯,當(dāng)該邏輯比較復(fù)雜時(shí),信號(hào)處理函數(shù)執(zhí)行時(shí)間過長,會(huì)導(dǎo)致信號(hào)屏蔽太久。

這里的解決方案是,信號(hào)處理函數(shù)僅僅發(fā)送信號(hào)通知程序主循環(huán),將信號(hào)對應(yīng)的處理邏輯放在程序主循環(huán)中,由主循環(huán)執(zhí)行信號(hào)對應(yīng)的邏輯代碼。

統(tǒng)一事件源

統(tǒng)一事件源,是指將信號(hào)事件與其他事件一樣被處理。

具體的,信號(hào)處理函數(shù)使用管道將信號(hào)傳遞給主循環(huán),信號(hào)處理函數(shù)往管道的寫端寫入信號(hào)值,主循環(huán)則從管道的讀端讀出信號(hào)值,使用I/O復(fù)用系統(tǒng)調(diào)用來監(jiān)聽管道讀端的可讀事件,這樣信號(hào)事件與其他文件描述符都可以通過epoll來監(jiān)測,從而實(shí)現(xiàn)統(tǒng)一處理。

信號(hào)處理機(jī)制

每個(gè)進(jìn)程之中,都有存著一個(gè)表,里面存著每種信號(hào)所代表的含義,內(nèi)核通過設(shè)置表項(xiàng)中每一個(gè)位來標(biāo)識(shí)對應(yīng)的信號(hào)類型。

  • 信號(hào)的接收

    • 接收信號(hào)的任務(wù)是由內(nèi)核代理的,當(dāng)內(nèi)核接收到信號(hào)后,會(huì)將其放到對應(yīng)進(jìn)程的信號(hào)隊(duì)列中,同時(shí)向進(jìn)程發(fā)送一個(gè)中斷,使其陷入內(nèi)核態(tài)。注意,此時(shí)信號(hào)還只是在隊(duì)列中,對進(jìn)程來說暫時(shí)是不知道有信號(hào)到來的。

  • 信號(hào)的檢測

    • 進(jìn)程從內(nèi)核態(tài)返回到用戶態(tài)前進(jìn)行信號(hào)檢測

    • 進(jìn)程在內(nèi)核態(tài)中,從睡眠狀態(tài)被喚醒的時(shí)候進(jìn)行信號(hào)檢測

    • 進(jìn)程陷入內(nèi)核態(tài)后,有兩種場景會(huì)對信號(hào)進(jìn)行檢測:

    • 當(dāng)發(fā)現(xiàn)有新信號(hào)時(shí),便會(huì)進(jìn)入下一步,信號(hào)的處理。

  • 信號(hào)的處理

    • ( 內(nèi)核 )信號(hào)處理函數(shù)是運(yùn)行在用戶態(tài)的,調(diào)用處理函數(shù)前,內(nèi)核會(huì)將當(dāng)前內(nèi)核棧的內(nèi)容備份拷貝到用戶棧上,并且修改指令寄存器(eip)將其指向信號(hào)處理函數(shù)。

    • ( 用戶 )接下來進(jìn)程返回到用戶態(tài)中,執(zhí)行相應(yīng)的信號(hào)處理函數(shù)。

    • ( 內(nèi)核 )信號(hào)處理函數(shù)執(zhí)行完成后,還需要返回內(nèi)核態(tài),檢查是否還有其它信號(hào)未處理。

    • ( 用戶 )如果所有信號(hào)都處理完成,就會(huì)將內(nèi)核棧恢復(fù)(從用戶棧的備份拷貝回來),同時(shí)恢復(fù)指令寄存器(eip)將其指向中斷前的運(yùn)行位置,最后回到用戶態(tài)繼續(xù)執(zhí)行進(jìn)程。

至此,一個(gè)完整的信號(hào)處理流程便結(jié)束了,如果同時(shí)有多個(gè)信號(hào)到達(dá),上面的處理流程會(huì)在第2步和第3步驟間重復(fù)進(jìn)行。

代碼分析

信號(hào)處理函數(shù)

自定義信號(hào)處理函數(shù),創(chuàng)建sigaction結(jié)構(gòu)體變量,設(shè)置信號(hào)函數(shù)。

1//信號(hào)處理函數(shù)
2void?sig_handler(int?sig) 3{
4????//為保證函數(shù)的可重入性,保留原來的errno
5????//可重入性表示中斷后再次進(jìn)入該函數(shù),環(huán)境變量與之前相同,不會(huì)丟失數(shù)據(jù)
6????int?save_errno?=?errno;
7????int?msg?=?sig;
8
9????//將信號(hào)值從管道寫端寫入,傳輸字符類型,而非整型
10????send(pipefd[1],?(char?*)&msg,?1,?0);
11
12????//將原來的errno賦值為當(dāng)前的errno
13????errno?=?save_errno;
14}

信號(hào)處理函數(shù)中僅僅通過管道發(fā)送信號(hào)值,不處理信號(hào)對應(yīng)的邏輯,縮短異步執(zhí)行時(shí)間,減少對主程序的影響。

1//設(shè)置信號(hào)函數(shù)
2void?addsig(int?sig,?void(handler)(int),?bool?restart?=?true)
3{
4????//創(chuàng)建sigaction結(jié)構(gòu)體變量
5????struct?sigaction?sa;
6????memset(&sa,?'\0',?sizeof(sa));
7
8????//信號(hào)處理函數(shù)中僅僅發(fā)送信號(hào)值,不做對應(yīng)邏輯處理
9????sa.sa_handler?=?handler;
10????if?(restart)
11????????sa.sa_flags?|=?SA_RESTART;
12????//將所有信號(hào)添加到信號(hào)集中
13????sigfillset(&sa.sa_mask);
14
15????//執(zhí)行sigaction函數(shù)
16????assert(sigaction(sig,?&sa,?NULL)?!=?-1);
17}

項(xiàng)目中設(shè)置信號(hào)函數(shù),僅關(guān)注SIGTERM和SIGALRM兩個(gè)信號(hào)。

信號(hào)通知邏輯

  • 創(chuàng)建管道,其中管道寫端寫入信號(hào)值,管道讀端通過I/O復(fù)用系統(tǒng)監(jiān)測讀事件

  • 設(shè)置信號(hào)處理函數(shù)SIGALRM(時(shí)間到了觸發(fā))和SIGTERM(kill會(huì)觸發(fā),Ctrl+C)

    • 通過struct sigaction結(jié)構(gòu)體和sigaction函數(shù)注冊信號(hào)捕捉函數(shù)

    • 在結(jié)構(gòu)體的handler參數(shù)設(shè)置信號(hào)處理函數(shù),具體的,從管道寫端寫入信號(hào)的名字

  • 利用I/O復(fù)用系統(tǒng)監(jiān)聽管道讀端文件描述符的可讀事件

  • 信息值傳遞給主循環(huán),主循環(huán)再根據(jù)接收到的信號(hào)值執(zhí)行目標(biāo)信號(hào)對應(yīng)的邏輯代碼

代碼分析

1//創(chuàng)建管道套接字
2ret?=?socketpair(PF_UNIX,?SOCK_STREAM,?0,?pipefd);
3assert(ret?!=?-1);
4
5//設(shè)置管道寫端為非阻塞,為什么寫端要非阻塞?
6setnonblocking(pipefd[1]);
7
8//設(shè)置管道讀端為ET非阻塞
9addfd(epollfd,?pipefd[0],?false);
10
11//傳遞給主循環(huán)的信號(hào)值,這里只關(guān)注SIGALRM和SIGTERM
12addsig(SIGALRM,?sig_handler,?false);
13addsig(SIGTERM,?sig_handler,?false);
14
15//循環(huán)條件
16bool?stop_server?=?false;
17
18//超時(shí)標(biāo)志
19bool?timeout?=?false;
20
21//每隔TIMESLOT時(shí)間觸發(fā)SIGALRM信號(hào)
22alarm(TIMESLOT);
23
24while?(!stop_server)
25{
26????//監(jiān)測發(fā)生事件的文件描述符
27????int?number?=?epoll_wait(epollfd,?events,?MAX_EVENT_NUMBER,?-1);
28????if?(number?0?&&?errno?!=?EINTR)
29????{
30????????break;
31????}
32
33????//輪詢文件描述符
34????for?(int?i?=?0;?i?35????{
36????????int?sockfd?=?events[i].data.fd;
37
38????????//管道讀端對應(yīng)文件描述符發(fā)生讀事件
39????????if?((sockfd?==?pipefd[0])?&&?(events[i].events?&?EPOLLIN))
40????????{
41????????????int?sig;
42????????????char?signals[1024];
43
44????????????//從管道讀端讀出信號(hào)值,成功返回字節(jié)數(shù),失敗返回-1
45????????????//正常情況下,這里的ret返回值總是1,只有14和15兩個(gè)ASCII碼對應(yīng)的字符
46????????????ret?=?recv(pipefd[0],?signals,?sizeof(signals),?0);
47????????????if?(ret?==?-1)
48????????????{
49????????????????//?handle?the?error
50????????????????continue;
51????????????}
52????????????else?if?(ret?==?0)
53????????????{
54????????????????continue;
55????????????}
56????????????else
57????????????{
58????????????????//處理信號(hào)值對應(yīng)的邏輯
59????????????????for?(int?i?=?0;?i?60????????????????{
61????????????????????//這里面明明是字符
62????????????????????switch?(signals[i])
63????????????????????{
64????????????????????//這里是整型
65????????????????????case?SIGALRM:
66????????????????????{
67????????????????????????timeout?=?true;
68????????????????????????break;
69????????????????????}
70????????????????????case?SIGTERM:
71????????????????????{
72????????????????????????stop_server?=?true;
73????????????????????}
74????????????????????}
75????????????????}
76????????????}
77????????}
78????}
79}

為什么管道寫端要非阻塞?

send是將信息發(fā)送給套接字緩沖區(qū),如果緩沖區(qū)滿了,則會(huì)阻塞,這時(shí)候會(huì)進(jìn)一步增加信號(hào)處理函數(shù)的執(zhí)行時(shí)間,為此,將其修改為非阻塞。

沒有對非阻塞返回值處理,如果緩沖區(qū)滿了是不是意味著這一次定時(shí)事件失效了?

是的,但定時(shí)事件是非必須立即處理的事件,可以允許這樣的情況發(fā)生。

管道傳遞的是什么類型?switch-case的變量沖突?

信號(hào)本身是整型數(shù)值,管道中傳遞的是ASCII碼表中整型數(shù)值對應(yīng)的字符。switch的變量一般為字符或整型,當(dāng)為字符時(shí),case中可以是字符,也可以是字符對應(yīng)的ASCII碼。

如果本文對你有幫助,閱讀原文star一下服務(wù)器項(xiàng)目,我們需要你的星星。

完。

?

關(guān)

兩猿社

微信號(hào) : twomonkeysclub

懂點(diǎn)互聯(lián)網(wǎng),懂點(diǎn)IC的程序猿。

帶你豐富項(xiàng)目經(jīng)驗(yàn),輕松校招。

我知道你在看

總結(jié)

以上是生活随笔為你收集整理的定时器 槽函数没执行_Web服务器项目详解 07 定时器处理非活动连接(上)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。