【項目】仿muduo庫one thread one loop式并發服務器前置知識準備

??📚?博主的專欄

🐧?Linux???|?? 🖥??C++???|?? 📊?數據結構??|?💡C++ 算法?|?🅒?C 語言? |?🌐?計算機網絡?|🗃??mysql

本文介紹了一種基于muduo庫實現的主從Reactor模型高并發服務器框架以及前置知識準備。框架采用OneThreadOneLoop設計思想,主Reactor負責監聽連接,子Reactor處理通信,實現高效并發。前置知識包括:1. 使用timerfd_create和timerfd_settime實現秒級定時任務;2. 設計時間輪定時器管理連接超時;3. 應用正則表達式解析HTTP請求;4. 實現通用any類型容器存儲不同協議上下文。測試表明,該框架可有效支持高并發場景,并提供了靈活的業務邏輯擴展接口。

目錄

一、項目初了解

1.1 目標定位:One Thread One Loop主從Reactor模型高并發服務器

1.2 模塊關系圖:

二、前置知識技術點功能用例:

2.1 C++11中的bind:這篇文章有詳細的講解

2.2 高效實現秒級定時任務:

2.2.1 Linux系統提供了以下定時器解決方案:

示例:

2.2.2?時間輪定時器的基本思想理解以及設計完善

時間輪

shared_ptr來管理定時器任務對象

2.2.3?時間輪定時器的代碼設計

***智能指針的使用***

2.2.4?時間輪定時器的代碼實現

2.2.5?時間輪定時器的代碼測試

2.3 正則庫的簡單使用

2.3.1 正則表達式基本認識

正則表達式匹配函數

2.3.2 正則表達式提取 HTTP 請求方法

2.3.3 正則表達式提取 HTTP 請求路徑

2.3.4 正則表達式提取 HTTP 查詢字符串

2.3.5 正則表達式提取 HTTP 協議版本

2.3.6 正則表達式提取 HTTP 元素細節完善

情況1:所給字符串后邊跟了\r\n,用以上的e,無法提取出字符串

情況2:所給字符串根本沒有查詢字符串?user=pupu&passwd=12312

2.4 實現通用的any類型:

2.4.1通用類型容器any類設計思想

2.4.2通用類型容器any類結構設計

2.4.3通用類型容器any類功能實現

2.4.4 通用類型容器any類功能測試

2.4.5 通用類型容器C++17中any的使用


一、項目初了解

基于muduo庫的One Thread One Loop主從Reactor模型高并發服務器實現:

我們實現的高并發服務器組件能夠快速搭建高性能服務器架構。該組件提供多種應用層協議支持,可便捷構建高性能應用服務器(項目演示中已內置HTTP協議組件支持)。

需要說明的是,本項目定位為高并發服務器組件框架,因此不包含具體業務邏輯實現。

1.1 目標定位:One Thread One Loop主從Reactor模型高并發服務器

我們將采用主從Reactor模型構建服務器,其中主Reactor線程專門負責監聽連接請求,確保高效處理新連接,從而提升服務器并發性能。

當主Reactor獲得新連接后,會將其分配給子Reactor進行通信。各子Reactor線程獨立監控其負責的描述符,處理讀寫事件并完成數據傳輸及業務邏輯處理。

One Thread One Loop的核心思想是將所有操作集中在一個線程內完成,每個線程對應一個獨立的事件處理循環。

當前實現考慮到組件使用者的多樣化需求,默認僅提供主從Reactor模型,而不內置業務層工作線程池。Worker線程池的實現與否,完全由組件使用者根據實際需求自行決定。

1.2 模塊關系圖:

?

二、前置知識技術點功能用例:

2.1 C++11中的bind:這篇文章有詳細的講解

bind (Fn&& fn, Args&&... args);

見見使用:

#include <iostream>
#include <string>
#include <functional>void print(const std::string &str, int num)
{std::cout << str << num << std::endl;
}int main()
{auto func = std::bind(print, "hello", std::placeholders::_1);func(10);return 0;
}

利用bind的特性,在設計線程池或任務池時,可以將任務設置為函數類型。通過bind直接綁定任務函數的參數,任務池只需取出并執行這些預綁定好的函數即可。

這種設計的優勢在于:任務池不需要關心具體任務的處理方式、函數設計或參數數量,有效降低了代碼之間的耦合度。

#include <iostream>
#include <string>
#include <functional>
#include <vector>
void print(const std::string &str, int num)
{std::cout << str << num << std::endl;
}int main()
{using Task = std::function<void()>;std::vector<Task> array;array.push_back(std::bind(print, "hello", 10));array.push_back(std::bind(print, "linux", 20));array.push_back(std::bind(print, "c++", 30));array.push_back(std::bind(print, "pupu", 40));for (auto &f : array){f();}return 0;
}

2.2 高效實現秒級定時任務:

在高并發服務器環境中,連接超時管理至關重要。長時間閑置的連接會持續占用系統資源,因此需要及時關閉這些無效連接。

為此,我們需要一個精準的定時任務機制,定期清理超時連接。

2.2.1 Linux系統提供了以下定時器解決方案:

timerfd_create-創建定時器

功能:創建一個定時器

#include <sys/timerfd.h>int timerfd_create(int clockid, int flags);/** 參數說明:* clockid: *   - CLOCK_REALTIME 系統實時時間(系統時間發生了改變就會出問題)*   - CLOCK_MONOTONIC 系統啟動后的單調時間(相對時間,定時不會隨著系統時間的改變而改變)* * flags:*   - 0 默認阻塞模式*/
返回值:文件描述符

Linux下“一切皆文件”,定時器的操作也是跟文件操作并沒有什么區別,而定時器的原理就是:

每隔一段時間(定時器的超時時間),系統就會給這個描述符對應的定時器寫入一個8字節的數據

創建了一個定時器,定時器定立的超時時間是3s,也就是說每3s算一次超時

從啟動開始,每隔3s中,系統就會給fd寫入一個1,表示從上一次讀取數據到現在超時了1次

假設30s之后才讀取數據,則這時候就會讀取到一個10,表示上一次讀取數據到限制超時了10次

timerfd_settime-啟動定時器

功能:啟動定時器

int timerfd_settime(int fd, int flags, struct itimerspec *new, struct
itimerspec *old);

fd: timerfd_create返回的文件描述符
flags: 0-相對時間, 1-絕對時間;默認設置為0即可.
new: 用于設置定時器的新超時時間
old: 用于接收當前定時器原有的超時時間

?

struct timespec
{time_t tv_sec; /* Seconds */long tv_nsec; ?/* Nanoseconds */
};
struct itimerspec
{struct timespec it_interval; /* 第?次之后的超時間隔時間 */struct timespec it_value; ? ?/* 第?次超時時間 */
};

定時器每次超時時,會自動向文件描述符(fd)寫入8字節數據,該數據表示從上次讀取操作到當前讀取操作之間發生的超時次數。

示例:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/timerfd.h>
#include <cstdint>int main()
{// 創建一個定時器int timerfd = timerfd_create(CLOCK_MONOTONIC, 0);if (timerfd < 0){perror("timerfd_create failed\n");return -1;}struct itimerspec itime;// 設置第一次超時時間為3s后itime.it_value.tv_sec = 3;itime.it_value.tv_nsec = 0;// 第一次超時后,每次超時的間隔時間itime.it_interval.tv_sec = 3;itime.it_interval.tv_nsec = 0;timerfd_settime(timerfd, 0, &itime, NULL);while (1){uint64_t times;int ret = read(timerfd, &times, 8);if (ret < 0){perror("read error\n");return -1;}printf("超時了,距離上一次超時了%ld次\n", times);}close(timerfd);return 0;
}
這是一個定時器使用的示例,每3秒會觸發一次超時事件,否則程序會阻塞在read數據讀取操作上。基于此例,我們可以實現每3秒檢測一次超時連接,并將超時的連接釋放。

2.2.2?時間輪定時器的基本思想理解以及設計完善

時間輪

這個示例存在一個明顯問題:每次超時都需要遍歷所有連接,當連接數量達到上萬時,效率會非常低下。

為此,我們可以改進方案:根據每個連接最后一次通信的系統時間建立小根堆。這樣只需檢查堆頂的連接,逐個釋放超時的連接即可,這將顯著提升處理效率。

雖然上述方法能實現定時任務,但這里要介紹另一種更優的解決方案:時間輪

時間輪的靈感來自鐘表機制。就像設定3點鐘鬧鐘后,時針走到3時就會觸發鈴聲。

同樣地,我們可以定義一個數組和一個指針,指針每秒移動一個位置。當指針移動到某個位置時,就執行該位置對應的任務。

具體實現時,如果想設定3秒后的任務,只需將任務添加到指針當前位置+3的位置。隨著指針每秒移動一步,3秒后就會到達對應位置,執行該任務。

然而,在同一個時間點可能會出現大量定時任務同時觸發的情況。為此,我們可以為數組的每個位置創建一個子數組(下拉數組),從而允許在相同時間點存儲多個定時任務。

?

tick(滴答指針,指向哪里,就表示哪里的任務超時了,3s后被執行)

如果tick滴答,是以秒作為計時單位,則當前這數組有7個元素,則最大定時時間就只有7s。

如果定時器想要設置一個超大時間的定時任務就可以使用多級時間輪

多級時間輪(這里是一個天級:60s 60min 24h)

?

缺陷:

1.同一時刻的定時任務只能添加一個,需要考慮如何在同一時刻支持添加多個定時任務

解決方案:將時間輪的一維數組設計為二維數組(時間輪一維數組的每一個節點也是一個數組)

2.假設當前的定時任務是一個連接的非活躍銷毀任務,這個任務什么時候添加到時間輪中比較合適?

一個連接30s內都沒有通信,則是一個非活躍連接,這時候就銷毀。但是一個連接在建立的時候添加了一個30s后銷毀的任務,并且這個連接30s內人家有數據通信,在第30s的時候就不是一個非活躍連接。

思想:需要在一個連接有IO事件產生的時候,延遲定時任務的執行。

作為一個時間輪定時器,本身并不關注任務類型,只要是時間到了,就需要被執行(我們要研究的是如何繞開,并且讓該任務延遲)

解決方案:類的析構函數+智能指針shared_ptr,通過這兩個技術可以實現定時任務的延時

? ? ? ? 1.使用一個類,對定時任務進行封裝,類實例化每一個對象,就是一個定時任務對象,當對象被銷毀的時候,再去執行定時任務(將定時任務的執行,放到析構函數中)

? ? ? ? 2.shared_ptr用于對new的對象進行空間管理,當shared_ptr對一個對象進行管理的時候,內部有一個計數器,計數器為0的時候,則釋放所管理的對象。

int *a = new int;

std::shared_ptr<int> pi(a); ---a對象只有在pi計數為0的時候才會被釋放

std::shared_ptr<int>pi1(pi);--針對pi又構建了一個shared_ptr對象,則pi和pi1計數器為2

當pi和pi1中任意一個被釋放的時候,只有計數器-1,因此他們管理的a對象并沒有被釋放,只有當pi和pi1都被釋放了,計數器為0了,這時候才會釋放管理的a對象

基于這個思想,我們可以使用shared_ptr來管理定時器任務對象,智能指針的使用詳解

shared_ptr來管理定時器任務對象

在實現過程中,我們采用了智能指針shared_ptr。shared_ptr通過引用計數器管理對象生命周期,只有當計數歸零時才會釋放資源。假設連接在第10秒進行一次通信,我們會向定時任務隊列中添加一個30秒后(即第40秒)執行的任務類對象的shared_ptr。此時兩個任務shared_ptr的引用計數變為2。當第30秒的定時任務釋放時,計數減1變為1,由于不為0,不會觸發實際析構。這意味著第30秒的任務自動失效,而真正的資源釋放會延遲到第40秒的任務執行時才完成

2.2.3?時間輪定時器的代碼設計

#include <memory>
#include <functional>
#include <iostream>
#include <vector>
#include <unordered_map>
#include <cstdint>using TaskFunc = std::function<void()>; // 定時任務函數類型
using ReleaseFunc = std::function<void()>;
class TimerTask
{
private:uint64_t _id;         // 定時器任務對象IDuint32_t _timeout;    // 定時任務的超時時間TaskFunc _task_cb;    // 定時器對象要執行的定時任務ReleaseFunc _release; // 用于刪除TimerWheel中保存的定時器對象信息
public:TimerTask(uint64_t id, uint32_t delay /*延遲時間*/, const TaskFunc &cb) : _id(id), _timeout(delay), _task_cb(cb) {};~TimerTask(){_task_cb();_release();}void SetRelease(const ReleaseFunc &cb) { _release = cb; }
};class TimerWheel
{
private:using WeakTask = std::weak_ptr<TimerTask>;using PtrTask = std::shared_ptr<TimerTask>;int _tick;     // tick走到哪里就釋放哪里的對象,釋放哪里,就相當于執行哪里的任務int _capacity; // 表盤最大數量---其實就是最大延遲時間// 當我們要二次添加同一個定時器任務對象的時候,得能夠找到他們的同一個計數器,使用weak_ptr輔助shared_ptr// 保存所有定時器的weak_ptr對象,因為只有保存了WeakTask才有可能通過WeakTask構造出新的shared_ptr,// 并且他們共享計數,并且WeakTask自身不影響計數std::vector<std::vector<PtrTask>> _wheel;std::unordered_map<uint64_t, WeakTask> _timers;public:TimerWheel() : _capacity(60), _tick(0), _wheel(_capacity) {}void TimerAdd(uint64_t id, uint32_t delay, const TaskFunc &cb); // 添加定時任務void TimerRefresh(uint64_t id);                                 // 刷新/延遲定時任務
};

這段代碼實現了一個基于定時輪盤(Timer Wheel)的定時任務調度器,用于管理和執行定時任務。以下是詳細的代碼講解:

1.?TimerTask?類

  • 功能 :表示一個定時任務,封裝了任務的 ID、延遲時間、任務回調函數以及釋放回調函數。

  • 主要成員變量

    • _id :定時器任務對象的唯一標識符,用于區分不同的定時任務。

    • _timeout :定時任務的超時時間,即任務需要延遲執行的時間。

    • _task_cb :定時任務的具體執行邏輯,當定時任務到期時,會調用這個回調函數來執行相應的操作。

    • _release :用于刪除 TimerWheel 中保存的定時器對象信息的回調函數,在定時任務執行完畢或被取消時,會調用這個函數來清理資源。

  • 構造函數 :初始化定時任務的 ID、延遲時間和任務回調函數。

  • 析構函數 :在定時任務對象被銷毀時,執行任務回調函數 _task_cb 和釋放回調函數 _release ,以確保任務被執行并且相關資源被正確釋放。

  • SetRelease 方法 :用于設置釋放回調函數 _release ,以便在需要時能夠清理 TimerWheel 中的定時任務記錄。

2.?TimerWheel?類

  • 功能 :一個定時輪盤,用于管理多個定時任務,支持添加新的定時任務和刷新已存在的定時任務的延遲時間。

  • 主要成員變量

    • _wheel :一個二維向量,模擬定時輪盤的結構。每個元素是一個包含 PtrTaskstd::shared_ptr<TimerTask> 類型)的向量,代表定時輪盤上的一個槽(slot)。定時任務根據其延遲時間被放置在相應的槽中,當輪盤的指針(_tick)移動到該槽時,就會執行其中的定時任務。

    • _tick :表示定時輪盤當前所指向的槽的位置。隨著時間的推移,_tick 會不斷遞增,當達到 _capacity 時,會重新從 0 開始循環,模擬輪盤的旋轉。

    • _capacity :定時輪盤的最大容量,即輪盤上槽的數量,同時也是定時任務的最大延遲時間限制。在這個例子中,初始容量被設置為 60,意味著定時任務的延遲時間不能超過 60 個時間單位(具體的時間單位可以根據實際應用場景來定義,例如秒、毫秒等)。

    • _timers :一個無序映射(std::unordered_map),用于保存所有定時任務的弱引用(std::weak_ptr<TimerTask>)。鍵是定時任務的 ID,值是對應的弱引用。通過使用弱引用,可以在不增加引用計數的情況下,跟蹤定時任務對象,并且可以在需要時通過弱引用來構造新的共享指針,從而訪問定時任務對象。

***智能指針的使用***
  • 使用 std::shared_ptr<TimerTask>PtrTask)來管理定時任務對象的生命周期,確保多個部分可以安全地共享對定時任務對象的訪問,并且對象會在所有共享指針都釋放后自動被銷毀。

  • 使用 std::weak_ptr<TimerTask>WeakTask)來保存定時任務的弱引用,避免在 _timers 映射中直接保存共享指針而導致對象的引用計數增加,從而防止定時任務對象被意外地延長生命周期。通過弱引用,可以在需要時檢查定時任務對象是否仍然存在,并在存在時獲取其共享指針。

  • 任務生命周期管理

?

2.2.4?時間輪定時器的代碼實現

#include <memory>
#include <functional>
#include <iostream>
#include <vector>
#include <unordered_map>
#include <cstdint>#include <unistd.h>using TaskFunc = std::function<void()>; // 定時任務函數類型
using ReleaseFunc = std::function<void()>;
class TimerTask
{
private:uint64_t _id;         // 定時器任務對象IDuint32_t _timeout;    // 定時任務的超時時間bool _canceled;       // false表示未被取消,true表示被取消TaskFunc _task_cb;    // 定時器對象要執行的定時任務ReleaseFunc _release; // 用于刪除TimerWheel中保存的定時器對象信息
public:TimerTask(uint64_t id, uint32_t delay /*延遲時間*/, const TaskFunc &cb): _id(id), _timeout(delay), _task_cb(cb), _canceled(false) {}~TimerTask(){if (_canceled == false)_task_cb();_release();}void Cancel(){_canceled = true;}void SetRelease(const ReleaseFunc &cb) { _release = cb; }uint32_t DelayTime() { return _timeout; };
};class TimerWheel
{
private:using WeakTask = std::weak_ptr<TimerTask>;using PtrTask = std::shared_ptr<TimerTask>;int _tick;     // tick走到哪里就釋放哪里的對象,釋放哪里,就相當于執行哪里的任務int _capacity; // 表盤最大數量---其實就是最大延遲時間// 當我們要二次添加同一個定時器任務對象的時候,得能夠找到他們的同一個計數器,使用weak_ptr輔助shared_ptr// 保存所有定時器的weak_ptr對象,因為只有保存了WeakTask才有可能通過WeakTask構造出新的shared_ptr,// 并且他們共享計數,并且WeakTask自身不影響計數std::vector<std::vector<PtrTask>> _wheel;std::unordered_map<uint64_t, WeakTask> _timers;private:void RemoveTimerInfo(uint64_t id){auto it = _timers.find(id);if (it != _timers.end()){_timers.erase(it);}}public:TimerWheel() : _capacity(60), _tick(0), _wheel(_capacity) {}// 添加定時任務void TimerAdd(uint64_t id, uint32_t delay, const TaskFunc &cb){PtrTask pt(new TimerTask(id, delay, cb));pt->SetRelease(std::bind(&TimerWheel::RemoveTimerInfo, this, id));int pos = (_tick + delay) % _capacity;_wheel[pos].push_back(pt);_timers[id] = WeakTask(pt);}// 刷新/延遲定時任務void TimerRefresh(uint64_t id){// 通過保存的定時器對象的weak_ptr構造一個shared_ptr出來,添加到輪子中auto it = _timers.find(id);if (it == _timers.end()){// 沒找到定時任務,沒法刷新,沒法延時return;}PtrTask pt = it->second.lock(); // lock獲取weak_ptr所管理對象對應的的shared_ptrint delay = pt->DelayTime();int pos = (_tick + delay) % _capacity;_wheel[pos].push_back(pt);}// 取消一個定時任務void TimerCancel(uint64_t id){// 通過保存的定時器對象的weak_ptr構造一個shared_ptr出來,添加到輪子中auto it = _timers.find(id);if (it == _timers.end()){// 沒找到定時任務,沒法刷新,沒法延時return;}PtrTask pt = it->second.lock(); // lock獲取weak_ptr所管理對象對應的的shared_ptrif (pt)pt->Cancel();}// 這個函數每秒鐘執行一次,相當于秒針向后走了一步void RunTimerTask(){_tick = (_tick + 1) % _capacity;_wheel[_tick].clear(); // 清空指定位置的數組,就能將數組中保存的所有管理對象的shared_ptr釋放掉}
};

1. 動態創建定時任務對象

TimerAdd 函數中,使用 std::shared_ptr 動態創建 TimerTask 對象:

PtrTask pt(new TimerTask(id, delay, cb));

這種方式允許在代碼運行時根據需要創建定時任務對象,并通過智能指針管理其生命周期。

2. 設置釋放回調函數

在創建定時任務對象后,調用 SetRelease 方法將 RemoveTimerInfo 函數綁定到定時任務對象的釋放回調函數上:

pt->SetRelease(std::bind(RemoveTimerInfo, this, id));

當定時任務對象被銷毀時,會自動調用 RemoveTimerInfo 函數,從 _timers 映射中移除該定時任務的記錄。

3. 計算定時任務在輪盤上的位置
TimerAddTimerRefresh 函數中,根據當前的 _tick 值(輪盤指針位置)和任務的延遲時間 delay,計算定時任務在輪盤上的位置:

int pos = (_tick + delay) % _capacity;

這種方法確保定時任務能夠根據其延遲時間被正確地放置在輪盤的相應槽中。

4. 將定時任務添加到輪盤

TimerAddTimerRefresh 函數中,將定時任務的共享指針添加到 _wheel 的對應槽中:

_wheel[pos].push_back(pt);

這使得定時任務能夠在輪盤到達該槽時被觸發。

5. 更新定時任務映射

TimerAdd 函數中,將定時任務的弱引用添加到 _timers 映射中:

_timers[id] = WeakTask(pt);

通過弱引用,可以在不增加引用計數的情況下跟蹤定時任務對象,便于后續的刷新操作。

6. 刷新定時任務

TimerRefresh 函數中,使用弱引用來獲取定時任務的共享指針,并重新計算其在輪盤上的位置:

PtrTask pt = it->second.lock();
...
int pos = (_tick + delay) % _capacity;
_wheel[pos].push_back(pt);

這允許在定時任務已經存在的情況下,更新其延遲時間并重新將其添加到輪盤中。

7. 執行定時任務

RunTimerTask 函數中,將輪盤指針向前移動,并清空當前槽中的所有定時任務:

_tick = (_tick + 1) % _capacity;
_wheel[_tick].clear();

清空槽中的任務會減少這些任務的引用計數,如果引用計數變為零,任務對象將被銷毀,從而觸發其析構函數,執行任務邏輯和釋放操作。

8. 在?TimerTask?類中新增任務取消功能

  • 新增 _canceled 成員變量 :布爾類型,默認值為 false,表示定時任務未被取消。當 _canceledtrue 時,表示任務已被取消。

  • 新增 Cancel 方法 :用于將 _canceled 標志設置為 true,表示取消該定時任務。

  • 在析構函數中增加條件判斷 :如果任務未被取消(_canceled == false),則執行任務回調函數 _task_cb 。否則,跳過任務的執行,直接調用釋放回調函數 _release 。這實現了任務取消功能,即當任務被取消時,其對應的回調函數不會被執行。

9. 在?TimerWheel?類中新增?TimerCancel?方法

  • 調用定時任務的 Cancel 方法 :如果成功獲取到共享指針(即定時任務對象存在),則調用該定時任務對象的 Cancel 方法,將其 _canceled 標志設置為 true,從而取消任務。

2.2.5?時間輪定時器的代碼測試

通過以下測試代碼,測試代碼的正確性

// 測試類
//  通過釋放過程來看任務執行情況
class Test
{
private:
public:Test() { std::cout << "構造" << std::endl; }~Test() { std::cout << "析構" << std::endl; }
};void DelTest(Test *t)
{delete t;
}int main()
{TimerWheel tw;Test *t = new Test();tw.TimerAdd(8, 5, std::bind(DelTest, t));for (int i = 0; i < 5; i++){sleep(1);tw.TimerRefresh(8); // 刷新定時任務tw.RunTimerTask();  // 向后移動秒針std::cout << "刷新了一下定時任務,重新需要5s才會被銷毀" << std::endl;}tw.TimerCancel(8);while (1){std::cout << "-----------------------" << std::endl;sleep(1);tw.RunTimerTask(); // 向后移動指針}return 0;
}

2.3 正則庫的簡單使用

2.3.1 正則表達式基本認識

正則表達式就是一種字符串匹配規則。正則庫就是給我們提供一套接口,讓我們使用正則匹配功能。正則表達式能夠簡化HTTP請求的解析過程(主要減輕程序員的工作負擔),讓開發的HTTP組件庫更易使用。需要注意的是,這種方式雖然提高了開發效率,但在處理速度上會略遜于直接的字符串操作。

<regex> - C++ Reference

正則表達式匹配函數

std::regex_match
功能:精確匹配整個字符串
參數

bool regex_match(const std::string& str,        // 待匹配字符串std::smatch& matches,          // 存儲匹配結果const std::regex& pattern,     // 正則表達式std::regex_constants::match_flag_type flags = std::regex_constants::match_default
);

HTTP 使用場景:匹配完整請求行(如?GET /index.html HTTP/1.1

std::regex_search
功能:在字符串中搜索子匹配
參數:同?regex_match
HTTP 使用場景:提取請求頭中的鍵值對(如?Host: www.example.com

示例:

#include <iostream>
#include <string>
#include <regex>int main()
{std::string str = "/numbers/1234";// 以numbers作為起始字符串,數字是\d+(多加一個\,轉義)// 匹配以/numbers/起始,后邊跟了一個或多個數字字符的字符串,并且在匹配的過程中提取這個匹配到的數字字符串std::regex e("/numbers/(\\d+)");std::smatch matches;bool ret = std::regex_match(str, matches, e);if (ret == false){return -1;}for (auto &s : matches){std::cout << s << std::endl;}return 0;
}

輸出:

/numbers/1234
1234

2.3.2 正則表達式提取 HTTP 請求方法

HTTP請求行格式: GET /pupu's_blog/login?user=pupu&passwd=12312 HTTP/1.1\r\n?

示例:?????請求方法的匹配

(GET|HEAD|POST|PUT|DELETE),表示匹配并提取其中任意一個字符串

#include <iostream>
#include <string>
#include <regex>int main()
{std::string str = "GET /pupu's_blog/login?user=pupu&passwd=12312 HTTP/1.1";std::smatch matches;// 請求方法的匹配:GET HEAD POST PUT DELETEstd::regex e("(GET|HEAD|POST|PUT|DELETE) .*");bool ret = std::regex_match(str, matches, e);if (ret == false){std::cout << "未找到\n";return -1;}for (auto &s : matches){std::cout << s << std::endl;}return 0;
}

輸出:

GET /pupu's_blog/login?user=pupu&passwd=12312 HTTP/1.1
GET

2.3.3 正則表達式提取 HTTP 請求路徑

示例:[^?]*)

[^?]表示匹配一個非?字符,后邊*代表匹配0次或多次(+表示匹配1次或多次)

  std::regex e("(GET|HEAD|POST|PUT|DELETE) ([^?]*) .*");

輸出:

GET /pupu's_blog/login?user=pupu&passwd=12312 HTTP/1.1
GET
/pupu's_blog/login

2.3.4 正則表達式提取 HTTP 查詢字符串

示例:\\?(.*) ????????

\\?表示原始的?字符,后邊的(.*)表示提取?之后的任意字符0次或多次,直到遇到空格

   std::regex e("(GET|HEAD|POST|PUT|DELETE) ([^?]*)\\?(.*) .*");

輸出:

GET /pupu's_blog/login?user=pupu&passwd=12312 HTTP/1.1
GET
/pupu's_blog/login
user=pupu&passwd=12312

2.3.5 正則表達式提取 HTTP 協議版本

示例:(HTTP/1\\.[01])

[01]表示匹配的是里邊的01任意一個字符

std::regex e("(GET|HEAD|POST|PUT|DELETE) ([^?]*)\\?(.*) (HTTP/1\\.[01]).*");

輸出:

GET /pupu's_blog/login?user=pupu&passwd=12312 HTTP/1.1
GET
/pupu's_blog/login
user=pupu&passwd=12312
HTTP/1.1

2.3.6 正則表達式提取 HTTP 元素細節完善

情況1:所給字符串后邊跟了\r\n,用以上的e,無法提取出字符串
std::string str = "GET /pupu's_blog/login?user=pupu&passwd=12312 HTTP/1.1\r\n";

如果:(\n|\r\n)

輸出結果:

?

得到這樣的結果并不是我們的目的,我們的目的是過濾掉\r\n:

使用:(?:\n|\r\n)

輸出結果:(分析的串后邊跟的是\n或者\r\n)

GET /pupu's_blog/login?user=pupu&passwd=12312 HTTP/1.1GET
/pupu's_blog/login
user=pupu&passwd=12312
HTTP/1.1

但如果,分析的串后邊沒有\r\n或者\n:就輸出失敗

解決辦法:(?:\n|\r\n)?

(?:\n|\r\n)? : (?: ...) 表示匹配某個格式字符串,但是不提取他,最后的?表示的是匹配前邊的表達式0次或1次

    std::regex e("(GET|HEAD|POST|PUT|DELETE) ([^?]*)\\?(.*) (HTTP/1\\.[01])(?:\n|\r\n)?");

輸出結果:

GET /pupu's_blog/login?user=pupu&passwd=12312 HTTP/1.1
GET
/pupu's_blog/login
user=pupu&passwd=12312
HTTP/1.1

情況2:所給字符串根本沒有查詢字符串?user=pupu&passwd=12312

由現有的e,無法輸出

解決辦法:(?:\\?(.*))?

匹配一個可選的問號(?),如果問號存在,則捕獲問號后的所有內容(包括空內容)作為一個分組。

 std::regex e("(GET|HEAD|POST|PUT|DELETE) ([^?]*)(?:\\?(.*))? (HTTP/1\\.[01])(?:\n|\r\n)?");

輸出:

GET /pupu's_blog/login HTTP/1.1
GET
/pupu's_blog/loginHTTP/1.1

2.4 實現通用的any類型:

  • 每個Connection都需要管理網絡連接,這最終都會涉及應用層協議的處理。因此,Connection中需要配置一個協議處理上下文來控制處理流程。考慮到應用層協議種類繁多,為避免耦合,這個協議解析上下文不應偏向特定協議,而應該能夠容納任意協議的上下文信息,這就需要一個通用的數據類型來存儲不同的數據結構。
  • 在C語言中,通用類型可通過void*實現。而在C++中,boost庫和C++17都提供了any這一靈活的類型。如需提高代碼可移植性并減少第三方庫依賴,可以選擇使用C++17的any特性或自行實現。

2.4.1通用類型容器any類設計思想

設計實現一個any類

是一個容器,容器中可以保存各種不同類型的數據

解決方案:

1.模版--不可用

template <class T>
class Any
{
private:T _content;
};

實例化對象的時候,必須指定容器保存的數據類型: Any<int> a;

我們需要的是:直接用Any a;a = 10,a = "abcd"....

2.設計嵌套類,類型擦除(Type Erasure)技術的核心思想,允許在單個容器中存儲任意類型的值,

多態存儲架構,基類?holder?作為抽象接口模板子類,placeholder<T>?存儲具體類型值

在Any類中存儲了holder類的指針。當需要保存數據時,Any容器只需通過placeholder子類實例化一個特定類型的子類對象,由該子類對象來實際存儲數據。

?

2.4.2通用類型容器any類結構設計

核心設計思想

類型擦除:通過基類指針指向模板派生類,擦除具體類型信息

多態克隆:通過虛函數實現深拷貝

類型安全訪問:運行時檢查類型匹配

class Any
{
private:class holder{public:virtual ~holder() {}virtual std::type_info type() = 0;virtual holder *clone() = 0;};template <class T>class placeholder : holder{public:placeholder(const T &val) _val(val) {}// 獲取子類對象保存的數據類型std::type_info type() override;// 針對當前的對象自身,克隆出一個新的子類對象holder *clone() override;};holder *_content; // 在new一個子類對象的時候指定類型,讓父類指向這個子類
public:Any();template <class T>Any(const T &val);Any(const Any &other);~Any();// 返回在子類對象中保存的數據的指針template <class T>T *get();// 賦值運算符的重載函數template <class T>Any &operator=(const T &val);Any &operator=(const Any &other);
};

2.4.3通用類型容器any類功能實現

#include <iostream>
#include <typeinfo>
#include <cassert>
#include <string>
class Any
{
private:class holder{public:virtual ~holder() {}virtual const std::type_info &type() = 0;// 深拷貝支持:多態克隆確保復制完整對象virtual holder *clone() = 0;};template <class T>class placeholder : public holder{public:placeholder(const T &val) : _val(val) {}// 獲取子類對象保存的數據類型const std::type_info &type() override{return typeid(T);}// 針對當前的對象自身,克隆出一個新的子類對象holder *clone() override{return new placeholder(_val);}public:T _val;};// 指向不同類型數據的通用接口holder *_content; // 在new一個子類對象的時候指定類型,讓父類指向這個子類public:// 無參構造Any() : _content(nullptr){}template <class T>// 通用構造Any(const T &val) : _content(new placeholder<T>(val)){}// 拷貝構造Any(const Any &other) : _content(other._content ? other._content->clone() : nullptr){}~Any(){delete _content;}//&為了進行一個連續的交換Any &swap(Any &other){std::swap(_content, other._content);return *this;}// 返回在子類對象中保存的數據的指針template <class T>T *get(){if (!_content)return nullptr;// 想要獲取的數據類型必須和保存的數據類型一致assert(typeid(T) == _content->type());// 向下轉型獲取存儲的值return &static_cast<placeholder<T> *>(_content)->_val;}// 賦值運算符的重載函數template <class T>Any &operator=(const T &val){// 為val構造一個臨時的通用容器,然后與當前容器自身進行指針交換,臨時對象釋放的時候,原先保存的數據也就被釋放Any(val).swap(*this); // 創建臨時對象并交換return *this;}Any &operator=(const Any &other){Any(other).swap(*this); // 拷貝構造臨時對象并交換return *this;}
};

類結構分析?

?

注意模板派生類 placeholder派生類:

  • 關鍵點:必須使用?public?繼承基類 holder

2.4.4 通用類型容器any類功能測試

示例1:

class Test
{
public:Test(){std::cout << "構造" << std::endl;}Test(const Test &t){std::cout << "拷貝構造" << std::endl;}~Test(){std::cout << "析構" << std::endl;}
};int main()
{Any a;{Test t;a = t;}a = 10;int *pa = a.get<int>();std::cout << *pa << std::endl;a = std::string("nihao");std::string *ps = a.get<std::string>();std::cout << *ps << std::endl;return 0;
}

運行結果:

構造
拷貝構造
析構
析構
10
nihao

?示例2:

int main()
{{Any a;Test t;a = t;}while (1){sleep(1);}return 0;
}

運行結果:

?

未來可以選擇使用boost庫\c++17\自己實現的Any?

2.4.5 通用類型容器C++17中any的使用

std::any - cppreference.cn - C++參考手冊

使用:

?

結語:

? ? ? ?隨著這篇博客接近尾聲,我衷心希望我所分享的內容能為你帶來一些啟發和幫助。學習和理解的過程往往充滿挑戰,但正是這些挑戰讓我們不斷成長和進步。我在準備這篇文章時,也深刻體會到了學習與分享的樂趣。 ? ?

? ? ? ? ?在此,我要特別感謝每一位閱讀到這里的你。是你的關注和支持,給予了我持續寫作和分享的動力。我深知,無論我在某個領域有多少見解,都離不開大家的鼓勵與指正。因此,如果你在閱讀過程中有任何疑問、建議或是發現了文章中的不足之處,都歡迎你慷慨賜教。

? ? ? ? 你的每一條反饋都是我前進路上的寶貴財富。同時,我也非常期待能夠得到你的點贊、收藏,關注這將是對我莫大的支持和鼓勵。當然,我更期待的是能夠持續為你帶來有價值的內容。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/85060.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/85060.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/85060.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

steam報網絡錯誤,但電腦是網絡連接的

steam報網絡錯誤&#xff0c;但電腦是網絡連接的 如&#xff1a; 解決辦法&#xff1a; 關閉電腦防火墻和所有殺毒軟件&#xff0c;然后重新打開steam開代理&#xff0c;可能國內有時候訪問不了 首選1進行嘗試 steam安裝路徑一定要在純英文路徑下 已ok

Vue 組合式 API 與 選項式 API 全面對比教程

一、前言&#xff1a;Vue 的兩種 API 風格 Vue 提供了兩種編寫組件邏輯的方式&#xff1a;組合式 API (Composition API) 和 選項式 API (Options API)。理解這兩種方式的區別和適用場景&#xff0c;對于 Vue 開發者至關重要。 為什么會有兩種 API&#xff1f; 選項式 API&a…

HarmonyOS 應用模塊化設計 - 面試核心知識點

HarmonyOS 應用模塊化設計 - 面試核心知識點 在 HarmonyOS 開發面試中&#xff0c;模塊化設計是必考知識點。本文從面試官角度深度解析 HarmonyOS 應用模塊化設計&#xff0c;涵蓋 HAP、HAR、HSP 等核心概念&#xff0c;助你輕松應對技術面試&#xff01; &#x1f3af; 面試高…

Maven高級學習筆記

分模塊設計 為什么分模塊設計?將項目按照功能拆分成若干個子模塊&#xff0c;方便項目的管理維護、擴展&#xff0c;也方便模塊間的相互調用&#xff0c;資源共享。 注意事項&#xff1a;分模塊開發需要先針對模塊功能進行設計&#xff0c;再進行編碼。不會先將工程開發完畢&…

[創業之路-423]:經濟學 - 大國競爭格局下的多維博弈與科技核心地位

在當今風云變幻的國際舞臺上&#xff0c;大國競爭已成為時代的主旋律&#xff0c;其激烈程度與復雜性遠超以往。這場全方位的較量&#xff0c;涵蓋了制度、思想、文化、經濟、科技、軍事等諸多關鍵領域&#xff0c;每一個維度都深刻影響著大國的興衰成敗&#xff0c;而科技在其…

【企業容災災備系統規劃】

一、企業災備體系 1.1 災備體系 災備切換的困境: 容災領域的標準化方法和流程、算法體系是確保業務連續性和數據可靠性的核心,以下從標準框架、流程規范、算法體系三個維度進行系統分析: 1.1.1、標準化方法體系? ?1. 容災等級標準? ?國際標準SHARE78?: 將容災能力劃…

Kafka Connect基礎入門與核心概念

一、Kafka Connect是什么&#xff1f; Apache Kafka Connect是Kafka生態中用于構建可擴展、可靠的數據集成管道的組件&#xff0c;它允許用戶將數據從外部系統&#xff08;如數據庫、文件系統、API等&#xff09;導入Kafka&#xff08;Source Connector&#xff09;&#xff0…

從零手寫Java版本的LSM Tree (四):SSTable 磁盤存儲

&#x1f525; 推薦一個高質量的Java LSM Tree開源項目&#xff01; https://github.com/brianxiadong/java-lsm-tree java-lsm-tree 是一個從零實現的Log-Structured Merge Tree&#xff0c;專為高并發寫入場景設計。 核心亮點&#xff1a; ? 極致性能&#xff1a;寫入速度超…

Kotlin的5個主要作用域函數

applay, also,let, run, with 是kotlin標準庫提供的5個主要的作用域函數&#xff08;Scope Functions&#xff09;?&#xff0c;它們的設計目的是為了在特定作用域內更簡潔地操作對象。 如何使用這5個函數&#xff0c;要從它的設計目的來區分&#xff1a; apply : 配置/對象…

原型模式Prototype Pattern

模式定義 用原型實例指定創建對象的種類&#xff0c;并且通過復制這些原型創建新的對象&#xff0c;其允許一個對象再創建 另外一個可定制的對象&#xff0c;無須知道任何創建的細節 對象創建型模式 基本工作原理是通過將一個原型對象傳給那個要發動創建的對象&#xff0c;這…

基于深度學習的智能交通流量預測系統:技術與實踐

前言 隨著城市化進程的加速&#xff0c;交通擁堵問題日益嚴重&#xff0c;給人們的日常生活和經濟發展帶來了巨大的挑戰。智能交通系統&#xff08;ITS&#xff09;作為解決交通問題的重要手段&#xff0c;逐漸成為研究的熱點。其中&#xff0c;交通流量預測是智能交通系統中的…

Cilium動手實驗室: 精通之旅---23.Advanced Gateway API Use Cases

Cilium動手實驗室: 精通之旅---23.Advanced Gateway API Use Cases 1. Lab說明1.1 高級網關 API 使用案例 2. 負載均衡器2.1 部署應用程序2.2 部署 Gateway 和 HTTPRoute 3. HTTP 標頭請求修飾符3.1 部署 HTTPRoute3.2 可觀測性 4. HTTP 響應標頭重寫5. HTTP 流量鏡像5.1 demo應…

Agentic Workflow是什么?Agentic Workflow會成為下一個AI風口嗎?

無論是想要學習人工智能當做主業營收&#xff0c;還是像我一樣作為開發工程師但依然要運用這個顛覆開發的時代寵兒&#xff0c;都有必要了解、學習一下人工智能。 近期發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;入行門檻低&#x…

Some chunks are larger than 500 KiB after minification. Consider

在 vue3vite 項目開發中&#xff0c;build 打包時出現以下警告報錯&#xff1a; (!) Some chunks are larger than 500 KiB after minification. Consider: - Using dynamic import() to code-split the application - Use build.rollupOptions.output.manualChunks to improve…

NodeJS11和10以及之前的版本,關鍵差異?

Node.js 11 相比 10&#xff08;及更早版本&#xff09;&#xff0c;除了事件循環行為的重大改變&#xff0c;還有多個核心模塊和底層機制的升級。以下是它們的關鍵差異和新特性對比&#xff0c;幫助你快速掌握兩個版本的重要變化。 &#x1f527; 一、事件循環行為變化&#x…

調和級數 斂散性

調和級數的斂散性是一個非常經典的問題。我們來全面分析它。 &#x1f9e0; 調和級數定義 調和級數是指&#xff1a; ∑ n 1 ∞ 1 n 1 1 2 1 3 1 4 ? \sum_{n1}^{\infty} \frac{1}{n} 1 \frac{1}{2} \frac{1}{3} \frac{1}{4} \cdots n1∑∞?n1?121?31?41?? …

Python?元組集合字符串

????˙?˙? ? 元組&#x1f6e5;?創建訪問修改解包其他操作比較的依據 集合&#x1f6f8;創建添加和刪除其他操作 字符串&#x1fa82;創建索引和切片基本操作連接加號join() 重復查找in 關鍵字index()find()startswith()endswith() ??替換??分割??大小寫刪除 能…

??信息系統項目管理師-項目整合管理 知識點總結與例題分析??

??一、項目整合管理概述?? ??1. 定義與重要性?? 項目整合管理是項目管理知識領域中的核心過程,它協調所有其他知識領域的過程和活動,確保項目各要素有效整合。其核心目標是: ??統一項目目標??:確保各要素服務于共同目標??協調沖突??:解決項目執行中的各…

『uniapp』onThemeChange監聽主題樣式,動態主題不正確生效,樣式被覆蓋的坑

目錄 問題示例代碼解決思路1&#xff08;缺點影響顯示效果有延遲&#xff09;解決思路2——通過路由刷新頁面&#xff08;缺點只適用于部分網頁&#xff09;解決思路3——vuex&#xff08;沒學會~&#xff09;總結 歡迎關注 『uniapp』 專欄&#xff0c;持續更新中 歡迎關注 『…

LeetCode 高頻 SQL 50 題(基礎版)【題解】合集

點擊下方標題可跳轉至對應部分&#xff1a; LeetCode 高頻 SQL 50 題&#xff08;基礎版&#xff09;之 【查詢】部分 LeetCode 高頻 SQL 50 題&#xff08;基礎版&#xff09;之 【連接】部分 上 LeetCode 高頻 SQL 50 題&#xff08;基礎版&#xff09;之 【連接】部分 下…