??📚?博主的專欄
🐧?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?時間輪定時器的基本思想理解以及設計完善
時間輪
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, ×, 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
:一個二維向量,模擬定時輪盤的結構。每個元素是一個包含PtrTask
(std::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. 計算定時任務在輪盤上的位置
在TimerAdd
和TimerRefresh
函數中,根據當前的_tick
值(輪盤指針位置)和任務的延遲時間delay
,計算定時任務在輪盤上的位置:int pos = (_tick + delay) % _capacity;
這種方法確保定時任務能夠根據其延遲時間被正確地放置在輪盤的相應槽中。
4. 將定時任務添加到輪盤
在
TimerAdd
和TimerRefresh
函數中,將定時任務的共享指針添加到_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
,表示定時任務未被取消。當_canceled
為true
時,表示任務已被取消。新增
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++參考手冊
使用:
?
結語:
? ? ? ?隨著這篇博客接近尾聲,我衷心希望我所分享的內容能為你帶來一些啟發和幫助。學習和理解的過程往往充滿挑戰,但正是這些挑戰讓我們不斷成長和進步。我在準備這篇文章時,也深刻體會到了學習與分享的樂趣。 ? ?
? ? ? ? ?在此,我要特別感謝每一位閱讀到這里的你。是你的關注和支持,給予了我持續寫作和分享的動力。我深知,無論我在某個領域有多少見解,都離不開大家的鼓勵與指正。因此,如果你在閱讀過程中有任何疑問、建議或是發現了文章中的不足之處,都歡迎你慷慨賜教。
? ? ? ? 你的每一條反饋都是我前進路上的寶貴財富。同時,我也非常期待能夠得到你的點贊、收藏,關注這將是對我莫大的支持和鼓勵。當然,我更期待的是能夠持續為你帶來有價值的內容。