第三方庫的介紹
Protobuf
什么是Protobuf(Protocol Buffer)?
- Protobuf是數據結構序列化和反序列化框架
Protobuf的特點有哪些?
- 通用性:語??關、平臺?關。即 ProtoBuf ?持 Java、C++、Python 等多種語?,?持多個平臺
- ?效:即? XML 更?、更快、更為簡單
- 擴展性、兼容性好:你可以更新數據結構,?不影響和破壞原有的舊程序
Protobuf的使用
-
使用流程:
- 編寫后綴為.proto的文件,在該文件中定義結果對象及屬性內容
- 使? protoc 編譯器編譯 .proto ?件,?成?系列接?代碼,存放在新?成頭?件和源?件中
- 依賴?成的接?,將編譯?成的頭?件包含進我們的代碼中,實現對 .proto ?件中定義的字段進?設置和獲取,和對自定義對象進?序列化和反序列化
-
使用示例
-
.proto文件的編寫,以學生類為例
//指定proto3語法,若沒有指定,編譯器會使用proto2語法 syntax = "proto3"; //Protocol Buffers 語?版本3,簡稱 proto3,是 .proto ?件最新的語法版本。proto3 簡化了 Protocol Buffers 語?,既易于使?,?可以在更?泛的編程語?中使?。它允許你使? Java,C++,Python 等多種語??成 protocol buffer 代碼。在 .proto ?件中,要使? syntax = "proto3"; 來指定?件語法為 proto3,并且必須寫在除去注釋內容的第??。 如果沒有指定,編譯器會使?proto2語法。package Student;//確定命名空間 //package 是?個可選的聲明符,能表? .proto ?件的命名空間,在項?中要有唯?性。它的作?是為了避免我們定義的消息出現沖突。message Student//自定義結構 {uint64 StudentNumber = 1;string StudentName = 2; }; //ProtoBuf 就是以 message 的?式來?持我們定制協議字段,后期幫助我們形成類和?法來使?。
-
.proto文件的編寫規范:
- ?件命名應該使?全?寫字?命名,多個字?之間? _ 連接,如student.proto
- 書寫 .proto ?件代碼時,應使? 2 個空格的縮進
-
定義消息字段;
-
字段定義格式為:字段類型 字段名 = 字段唯?編號;
-
字段名稱命名規范:全?寫字?,多個字?之間? _ 連接
-
字段類型分為:標量數據類型 和 特殊類型(包括枚舉、其他消息類型等)
.proto 類型 C++ 類型 說明 double
double
雙精度浮點數 float
float
單精度浮點數 int32
int32
變長編碼的32位整數。負數編碼效率低,若字段可能為負值,建議改用 sint32
。int64
int64
變長編碼的64位整數。負數編碼效率低,若字段可能為負值,建議改用 sint64
。uint32
uint32
變長編碼的無符號32位整數 uint64
uint64
變長編碼的無符號64位整數 sint32
int32
變長編碼的32位整數,ZigZag編碼優化負數效率,適用于可能包含負值的場景。 sint64
int64
變長編碼的64位整數,ZigZag編碼優化負數效率,適用于可能包含負值的場景。 fixed32
uint32
定長4字節的無符號32位整數,數值常大于2^28時更高效(相比 uint32
)。fixed64
uint64
定長8字節的無符號64位整數,數值常大于2^56時更高效(相比 uint64
)。sfixed32
int32
定長4字節的有符號32位整數 sfixed64
int64
定長8字節的有符號64位整數 bool
bool
布爾值( true
/false
)string
std::string
UTF-8或ASCII編碼字符串,長度不超過2^32。 bytes
std::string
任意二進制字節序列,長度不超過2^32(實際存儲為 std::string
,但語義上區分于字符串)。enum
枚舉類型 用戶自定義的枚舉類型,對應C++的 enum
或enum class
。message
類(class) 用戶自定義的消息類型,對應C++中的類結構,可嵌套其他消息或枚舉。 repeated
std::vector<T>
動態數組,存儲相同類型的多個元素(例如 repeated int32
對應std::vector<int32>
)。map<K, V>
std::unordered_map<K, V>
鍵值對集合,鍵類型需為整數、布爾或字符串,值類型可為任意有效類型(例如 map<string, int32>
對應std::unordered_map<string, int32>
)。 -
字段唯?編號:?來標識字段,?旦開始使?就不能夠再改變。
-
-
字段唯?編號的范圍:1 ~ 536,870,911 (2^29 - 1) ,其中 19000 ~ 19999 不可?。19000 ~ 19999 不可?是因為:在 Protobuf 協議的實現中,對這些數進?了預留。如果?要在.proto?件中使?這些預留標識號,例如將 name 字段的編號設置為19000,編譯時就會報警
-
-
編譯.proto文件
protoc [--proto_path=IMPORT_PATH] --cpp_out=DST_DIR path/to/file.proto //將.proto文件編譯為cpp文件的格式 // protoc 是 Protocol Buffer 提供的命令?編譯?具。// --proto_path 指定 被編譯的.proto?件所在?錄,可多次指定。可簡寫成 -I IMPORT_PATH 。如不指定該參數,則在當前?錄進?搜索。當某個.proto ?件 import 其他.proto ?件時,或需要編譯的 .proto ?件不在當前?錄下,這時就要?-I來指定搜索?錄。// --cpp_out= 指編譯后的?件為 C++ ?件。// OUT_DIR 編譯后?成?件的?標路徑。// path/to/file.proto 要編譯的.proto?件
-
編譯student.proto文件命令如下
protoc --cpp_out=./ ./student.proto
-
編譯生成的cpp代碼內容:
- 對于每個message,都會生成一個對應的消息類
- 在消息類中,編譯器為每個字段提供了獲取和設置?法,以及?下其他能夠操作字段的?法
- 編輯器會針對于每個 .proto ?件?成 .h 和 .cc ?件,分別?來存放類的聲明與類的實現student.pb.h、student.pb.cc 部分代碼展?
-
-
序列化與反序列化的使用
-
示例程序
#include<iostream> #include"student.pb.h"int main() {//設置學生的屬性Student::Student s1;s1.set_studentname("張三");s1.set_studentnumber(1000);//將學生進行序列化std::string str;str = s1.SerializeAsString();//將學生信息反序列化Student::Student ret;ret.ParseFromString(str);//輸出結果std::cout<<ret.studentname()<<std::endl<<ret.studentnumber()<<std::endl;return 0; }
-
對程序編譯并執行
g++ -o testStudent student.pb.cc test_student.cc -std=c++11 -lprotobuf//-lprotobuf:鏈接protobuf庫?件 -std=c++11:?持C++11
-
程序結果
-
-
Muduo庫的介紹
什么是Muduo庫?
-
Muduo由陳碩?佬開發,是?個基于?阻塞IO和事件驅動的C++?并發TCP?絡編程庫。 它是?款基于主從Reactor模型的?絡庫,其使?的線程模型是one loop per thread, 所謂one loop per thread指的是:
-
?個線程只能有?個事件循環(EventLoop), ?于響應計時器和IO事件
-
?個?件描述符只能由?個線程進?讀寫,換句話說就是?個TCP連接必須歸屬于某個EventLoop管理
-
-
Muduo庫常用接?介紹
-
muduo::net::TcpServer類基礎介紹
// TcpConnection 的共享指針類型別名,用于安全地管理 TcpConnection 對象的生命周期 typedef std::shared_ptr<TcpConnection> TcpConnectionPtr;// 連接事件回調類型(連接建立/關閉時觸發) // 參數:const TcpConnectionPtr& 表示關聯的 TCP 連接對象 typedef std::function<void (const TcpConnectionPtr&)> ConnectionCallback;// 消息到達回調類型(當接收到數據時觸發) // 參數: // - const TcpConnectionPtr&: 關聯的 TCP 連接對象 // - Buffer*: 指向接收緩沖區的指針,存儲待處理的字節數據 // - Timestamp: 消息到達的時間戳(通常為系統時間或相對時間) typedef std::function<void (const TcpConnectionPtr&,Buffer*,Timestamp)> MessageCallback;// IP 地址封裝類(繼承自可 基類,允許對象拷貝) // 用途:表示一個網絡端點(IP + 端口),支持 IPv4/IPv6 class InetAddress : public muduo::copyable {public:// 構造函數// 參數:// - ip: 字符串形式的 IP 地址(如 "192.168.1.1" 或 "2001:db8::1")// - port: 16 位端口號// - ipv6: 是否為 IPv6 地址(默認 false,即 IPv4)InetAddress(StringArg ip, uint16_t port, bool ipv6 = false); };// TCP 服務器類(繼承自不可 基類,禁止對象拷貝) // 功能:管理 TCP 連接監聽、處理客戶端連接和消息收發 class TcpServer : noncopyable {public:// 端口復用選項枚舉enum Option {kNoReusePort, // 不啟用 SO_REUSEPORT(默認)kReusePort, // 啟用 SO_REUSEPORT,允許多進程/線程綁定同一端口};// 構造函數// 參數:// - loop: EventLoop 事件循環對象,用于驅動網絡事件處理// - listenAddr: 服務器監聽的地址和端口// - nameArg: 服務器名稱標識(用于日志或調試)// - option: 端口復用選項(默認 kNoReusePort)TcpServer(EventLoop* loop,const InetAddress& listenAddr,const string& nameArg,Option option = kNoReusePort);// 設置 IO 線程池的大小(用于處理連接的線程數)// 注:通常設置為 CPU 核心數,0 表示所有操作在 EventLoop 線程執行void setThreadNum(int numThreads); // 啟動服務器,開始監聽并處理連接void start(); // 設置新連接建立/關閉時的回調函數void setConnectionCallback(const ConnectionCallback& cb){ connectionCallback_ = cb; } // 賦值給成員變量 connectionCallback_// 設置接收到消息時的業務處理回調函數void setMessageCallback(const MessageCallback& cb){ messageCallback_ = cb; } // 賦值給成員變量 messageCallback_
-
muduo::net::EventLoop類基礎介紹
// 事件循環類(不可 ,遵循 one loop per thread 設計) // 核心職責:驅動 IO 事件監聽、定時器調度、跨線程任務執行 class EventLoop : noncopyable {public:/// 啟動事件循環(無限循環,阻塞直到調用 quit())/// **必須**在創建該對象的線程中調用void loop();/// 終止事件循環/// **非完全線程安全**:通過裸指針調用時需同步,建議通過 shared_ptr 調用確保安全void quit();/// 在指定時間點運行定時器回調/// 參數:/// - time: 觸發時間戳(基于 Timestamp 的時序)/// - cb: 定時器觸發時的回調函數/// 返回值:TimerId 用于后續取消定時器/// **線程安全**:可從其他線程調用TimerId runAt(Timestamp time, TimerCallback cb);/// 在延遲指定秒數后運行回調/// 參數:/// - delay: 延遲時間(單位:秒)/// - cb: 延遲結束后的回調函數/// 返回值:TimerId/// **線程安全**:可從其他線程調用TimerId runAfter(double delay, TimerCallback cb);/// 每隔指定間隔重復運行回調/// 參數:/// - interval: 間隔時間(單位:秒)/// - cb: 每次觸發時的回調函數/// 返回值:TimerId/// **線程安全**:可從其他線程調用TimerId runEvery(double interval, TimerCallback cb);/// 取消指定定時器/// 參數:/// - timerId: 由 runAt/runAfter/runEvery 返回的 ID/// **線程安全**:可從其他線程調用void cancel(TimerId timerId);private:std::atomic<bool> quit_; // 原子標志位,控制循環退出std::unique_ptr<Poller> poller_; // 事件監聽器(如 epoll/kqueue)mutable MutexLock mutex_; // 互斥鎖,保護 pendingFunctors_std::vector<Functor> pendingFunctors_ GUARDED_BY(mutex_); // 跨線程提交的待執行任務隊列 };
-
muduo::net::TcpConnection類基礎介紹
/*** TCP 連接核心類(不可 ,支持共享指針安全訪問)* 職責:封裝已建立的 TCP 連接,管理連接生命周期及事件處理* 設計特點:基于 Reactor 模式,通過 Channel 監聽 socket 事件,實現非阻塞 IO*/ class TcpConnection : noncopyable, // 禁止拷貝語義 public std::enable_shared_from_this<TcpConnection> // 支持安全共享指針[6,7] {public:/*** 構造函數(由 TcpServer/TcpClient 內部調用,用戶不應直接創建)* @param loop 所屬事件循環(sub-Reactor)* @param nameArg 連接名稱標識(用于日志)* @param sockfd 已連接的套接字描述符* @param localAddr 本地端點地址* @param peerAddr 對端端點地址* 初始化流程:* 1. 創建 Socket 和 Channel 對象[2,4]* 2. 注冊 Channel 的事件回調(讀->handleRead,寫->handleWrite)[2,4]* 3. 設置初始狀態為 kConnecting*/TcpConnection(EventLoop* loop,const string& name,int sockfd,const InetAddress& localAddr,const InetAddress& peerAddr);/// 連接狀態查詢(線程安全)bool connected() const { return state_ == kConnected; } // 已建立連接bool disconnected() const { return state_ == kDisconnected; } // 已斷開/// 數據發送接口(支持多種數據格式,線程安全)void send(string&& message); // C++11 移動語義優化void send(const void* message, int len); // 原始數據指針void send(const StringPiece& message); // 字符串片段void send(Buffer* message); // 交換緩沖區所有權/// 關閉連接(非線程安全,需避免并發調用)void shutdown();/// 上下文數據存取(支持任意類型擴展)void setContext(const boost::any& context) { context_ = context; }const boost::any& getContext() const { return context_; }boost::any* getMutableContext() { return &context_; }/// 回調函數設置接口void setConnectionCallback(const ConnectionCallback& cb) { connectionCallback_ = cb; } // 連接狀態變化回調void setMessageCallback(const MessageCallback& cb) { messageCallback_ = cb; } // 消息到達回調private:/// 連接狀態機(原子操作保障線程安全)enum StateE { kDisconnected, // 初始/已斷開狀態kConnecting, // 正在建立連接(Acceptor 創建后)kConnected, // 已建立連接(Channel 注冊到 Poller)kDisconnecting // 正在關閉連接(主動調用 shutdown)};// 核心成員變量EventLoop* loop_; // 所屬事件循環(保證IO操作線程安全)ConnectionCallback connectionCallback_; // 連接建立/關閉回調MessageCallback messageCallback_; // 數據到達回調WriteCompleteCallback writeCompleteCallback_; // 發送完成回調(需額外設置)[1]boost::any context_; // 用戶自定義上下文(如會話ID) };
-
muduo::net::TcpClient類基礎介紹
/*** TCP 客戶端核心類(不可 ),基于 muduo 網絡庫設計* 職責:管理客戶端與服務器的單條 TCP 連接,實現異步連接與消息處理* 設計特點:通過 Connector 發起連接,連接成功后創建 TcpConnection 管理會話*/ class TcpClient : noncopyable {public:/*** 構造函數* @param loop 事件循環對象(sub-Reactor)* @param serverAddr 服務器地址(IP + 端口)* @param nameArg 客戶端名稱標識(用于日志)* 初始化流程:* 1. 創建 Connector 對象發起異步連接* 2. 連接成功后創建 TcpConnection 對象*/TcpClient(EventLoop* loop,const InetAddress& serverAddr,const string& nameArg);/// 強制顯式定義析構函數(因包含 unique_ptr 成員)~TcpClient();/// 發起異步連接(線程安全,可在任意線程調用)void connect();/// 關閉連接(非線程安全,需在 IO 線程調用)void disconnect();/// 停止客戶端(停止重連機制)void stop();/// 獲取當前連接對象(需加鎖保證線程安全)TcpConnectionPtr connection() const {MutexLockGuard lock(mutex_); // 互斥鎖保護連接對象return connection_;}// 回調函數設置接口void setConnectionCallback(ConnectionCallback cb) { connectionCallback_ = std::move(cb); } // 連接狀態變化回調void setMessageCallback(MessageCallback cb) { messageCallback_ = std::move(cb); } // 消息到達回調private:EventLoop* loop_; // 所屬事件循環(sub-Reactor)ConnectionCallback connectionCallback_; MessageCallback messageCallback_;TcpConnectionPtr connection_ GUARDED_BY(mutex_); // 當前連接(受互斥鎖保護) };/*** 倒計時閂鎖類(不可 ),用于線程間同步控制* 用途:確保異步操作完成前阻塞等待(如等待 TCP 連接建立)* 原理:基于條件變量實現的線程同步原語*/ class CountDownLatch : noncopyable {public:/// 初始化計數器(count > 0)explicit CountDownLatch(int count);/// 阻塞等待直到計數器歸零(線程安全)void wait() {MutexLockGuard lock(mutex_);while (count_ > 0) { // 避免虛假喚醒condition_.wait(); // 條件變量等待}}/// 計數器減一(線程安全),歸零時喚醒所有等待線程void countDown() {MutexLockGuard lock(mutex_);--count_;if (count_ == 0) {condition_.notifyAll(); // 廣播喚醒}}private:mutable MutexLock mutex_; // 互斥鎖Condition condition_ GUARDED_BY(mutex_); // 條件變量int count_ GUARDED_BY(mutex_); // 計數器 };
-
muduo::net::Buffer類基礎介紹
/*** 網絡數據緩沖區類(支持高效拷貝)* 設計目標:提供高效的讀寫操作,避免內存拷貝,支持協議解析常用操作* 特性:* - 預留頭部空間(kCheapPrepend)便于添加協議頭* - 自動擴容機制保證寫入空間* - 提供網絡字節序與主機字節序轉換接口*/ class Buffer : public muduo::copyable { public:static const size_t kCheapPrepend = 8; ///< 頭部預留空間(用于添加協議頭等場景)static const size_t kInitialSize = 1024; ///< 初始緩沖區大小(不包含預留空間)/*** 構造函數* @param initialSize 初始可用空間大小(默認1024字節)* 總緩沖區大小 = kCheapPrepend + initialSize*/explicit Buffer(size_t initialSize = kInitialSize);/// 交換兩個緩沖區內容(高效無拷貝)void swap(Buffer& rhs);/// 獲取可讀數據字節數size_t readableBytes() const { return writerIndex_ - readerIndex_; }/// 獲取可寫空間字節數size_t writableBytes() const { return buffer_.size() - writerIndex_; }/// 獲取指向可讀數據的指針(不會消費數據)const char* peek() const { return begin() + readerIndex_; }/// 查找第一個CRLF("\r\n")位置,用于行協議解析const char* findEOL() const;const char* findEOL(const char* start) const;/// 消費指定長度數據(移動讀指針)void retrieve(size_t len);/// 消費整型數據(自動轉換網絡字節序到主機字節序)void retrieveInt64() { retrieve(sizeof(int64_t)); }void retrieveInt32() { retrieve(sizeof(int32_t)); }void retrieveInt16() { retrieve(sizeof(int16_t)); }void retrieveInt8() { retrieve(sizeof(int8_t)); }/// 獲取并消費所有可讀數據為字符串string retrieveAllAsString();/// 獲取并消費指定長度數據為字符串string retrieveAsString(size_t len);/// 追加數據到緩沖區(自動擴容)void append(const StringPiece& str);void append(const char* data, size_t len);void append(const void* data, size_t len);/// 獲取可寫空間起始指針(寫前需確保足夠空間)char* beginWrite() { return begin() + writerIndex_; }const char* beginWrite() const { return begin() + writerIndex_; }/// 確認已寫入數據長度(移動寫指針)void hasWritten(size_t len) { writerIndex_ += len; }/// 追加整型數據(自動轉換主機字節序到網絡字節序)void appendInt64(int64_t x);void appendInt32(int32_t x);void appendInt16(int16_t x);void appendInt8(int8_t x);/// 讀取并消費整型數據(自動轉換網絡字節序)int64_t readInt64();int32_t readInt32();int16_t readInt16();int8_t readInt8();/// 查看整型數據(不移動讀指針)int64_t peekInt64() const;int32_t peekInt32() const;int16_t peekInt16() const;int8_t peekInt8() const;/// 在頭部預留空間添加數據(常用于添加協議頭)void prependInt64(int64_t x);void prependInt32(int32_t x);void prependInt16(int16_t x);void prependInt8(int8_t x);void prepend(const void* data, size_t len);private:std::vector<char> buffer_; ///< 數據存儲容器size_t readerIndex_; ///< 讀指針(指向可讀數據起始位置)size_t writerIndex_; ///< 寫指針(指向可寫空間起始位置)static const char kCRLF[]; ///< 行結束符常量"\r\n"/// 獲取緩沖區首地址char* begin() { return &*buffer_.begin(); }const char* begin() const { return &*buffer_.begin(); }/// 空間不足時自動擴容(保證至少有len字節可寫空間)void ensureWritableBytes(size_t len); };// 示例用法: // Buffer buf; // buf.appendInt32(123); // 寫入4字節網絡序整數 // int32_t n = buf.readInt32(); // 讀取并轉換為主機序 // buf.prependInt32(0xdeadbeef); // 在頭部插入魔數
-
Muduo庫的快速上手
-
使用Muduo庫搭建一個簡單的英譯漢服務器和客戶端
-
大體流程:
一、服務端搭建流程(TranslateServer)
-
初始化事件循環與服務器
EventLoop loop; // 創建主事件循環 InetAddress listenAddr(8888); // 監聽端口 TcpServer server(&loop, listenAddr, "TranslateServer");
-
注冊回調函數
// 連接建立/關閉回調 server.setConnectionCallback(std::bind(&onConnection, ...)); // 消息到達回調 server.setMessageCallback(std::bind(&onMessage, ...));
-
啟動服務
server.start(); // 啟動監聽線程 loop.loop(); // 進入事件循環(阻塞在此)
-
實現核心回調函數
void onConnection(const TcpConnectionPtr& conn) {// 連接狀態變化處理if (conn->connected()) { /* 記錄新連接 */ }else { /* 清理連接資源 */ } }void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp) {string msg = buf->retrieveAllAsString(); // 讀取數據string result = translate(msg); // 業務處理conn->send(result); // 發送響應 }
二、客戶端搭建流程(TranslateClient)
-
創建IO線程與客戶端
EventLoopThread ioThread; // 創建IO線程 TcpClient client(ioThread.startLoop(), serverAddr, "Client");
-
設置回調函數
client.setConnectionCallback(std::bind(&onClientConnection, ...)); client.setMessageCallback(std::bind(&onClientMessage, ...));
-
連接服務器
client.connect(); // 異步連接 latch.wait(); // 等待連接建立(使用CountDownLatch)
-
實現客戶端回調
void onClientConnection(const TcpConnectionPtr& conn) {if (conn->connected()) {latch_.countDown(); // 連接成功通知conn_ = conn; // 保存連接對象} }void onClientMessage(const TcpConnectionPtr&, Buffer* buf, Timestamp) {cout << "收到響應: " << buf->retrieveAllAsString() << endl; }
-
發送請求
conn_->send("hello"); // 使用保存的連接對象發送數據
三、關鍵機制說明
-
Reactor模型
-
單線程處理所有IO事件(用戶代碼無需考慮鎖)
-
EventLoop::loop()
驅動事件分發
-
-
回調機制
通過std::bind
綁定成員函數,處理三類事件:-
連接建立/斷開
-
消息到達
-
寫完成通知(示例未使用)
-
-
Buffer設計
-
自動管理讀寫指針(
retrieveAllAsString()
消費數據) -
支持高效預置空間(處理協議頭)
-
-
線程安全
-
客戶端
send()
需在IO線程調用(示例用CountDownLatch
同步) -
服務端無需顯式鎖(muduo保證回調在IO線程)
-
-
-
音譯漢TCP服務器代碼
#include "muduo/net/EventLoop.h" #include "muduo/net/TcpConnection.h" #include "muduo/net/TcpServer.h" #include <functional> #include <iostream> #include <sys/types.h> #include <unordered_map>/*** 基于muduo網絡庫的簡易翻譯服務器* 功能:接收客戶端英文單詞,返回中文翻譯(示例實現簡單字典查詢)*/ class TranslateServer { public:/*** 構造函數* @param port 服務器監聽端口號* 初始化流程:* 1. 創建事件循環對象(_baseloop)* 2. 創建TCP服務器對象(_server),綁定全零地址(0.0.0.0)表示監聽所有網絡接口* 3. 設置連接回調與消息回調*/TranslateServer(uint16_t port): _server(&_baseloop, // 事件循環對象muduo::net::InetAddress("0.0.0.0", port), // 監聽地址(IPv4任意地址)"TranslateServer", // 服務器名稱(用于日志標識)muduo::net::TcpServer::kReusePort) // 啟用端口復用選項(允許多線程監聽相同端口){// 綁定連接狀態變化回調(lambda通過std::bind轉換為函數對象)_server.setConnectionCallback(std::bind(&TranslateServer::ConnectionCallback, this, std::placeholders::_1));// 綁定消息到達回調(參數占位符分別表示連接對象、緩沖區、時間戳)_server.setMessageCallback(std::bind(&TranslateServer::MessageCallback, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));}/// 啟動服務器(開始監聽并進入事件循環)void Start(){_server.start(); // 啟動監聽線程_baseloop.loop(); // 進入主事件循環(阻塞在此處)}private:/*** 連接狀態變化回調函數* @param connection TCP連接智能指針* 觸發時機:當新連接建立或現有連接關閉時*/void ConnectionCallback(const muduo::net::TcpConnectionPtr &connection){if (connection->connected()) {std::cout << "新連接建立 [" << connection->peerAddress().toIpPort() << "]" << std::endl;} else {std::cout << "連接關閉 ["<< connection->peerAddress().toIpPort() << "]" << std::endl;}}/*** 消息到達回調函數(核心業務邏輯)* @param connection TCP連接對象* @param buffer 接收緩沖區(存儲客戶端發送的數據)* @param timestamp 消息到達時間戳* 處理流程:* 1. 從緩沖區提取全部數據為字符串* 2. 調用翻譯函數獲取結果* 3. 通過TCP連接返回翻譯結果*/void MessageCallback(const muduo::net::TcpConnectionPtr &connection,muduo::net::Buffer *buffer,muduo::Timestamp timestamp){// 提取緩沖區所有數據(自動移動讀指針)std::string request = buffer->retrieveAllAsString(); // 執行翻譯(示例實現為內存字典查詢)std::string answer = translate(request);// 返回結果給客戶端(send方法線程安全)if (!answer.empty()) {connection->send(answer + "\r\n"); // 添加換行符作為消息分隔符} else {connection->send("未找到翻譯: " + request + "\r\n");}}/*** 簡單字典翻譯函數(實際項目應替換為數據庫查詢或API調用)* @param str 待翻譯的英文單詞* @return 中文翻譯結果,未找到返回空字符串* 特點:* - 使用靜態哈希表減少重復初始化開銷* - 當前僅支持全小寫單詞查詢*/const std::string translate(const std::string &str){// 靜態字典(初始化一次,后續查詢共享)static std::unordered_map<std::string, std::string> dictionary{{"hello", "你好"}, {"love", "愛"},{"computer", "計算機"},{"server", "服務器"}};auto it = dictionary.find(str);return (it != dictionary.end()) ? it->second : "";}private:muduo::net::EventLoop _baseloop; // 主事件循環對象(Reactor核心)muduo::net::TcpServer _server; // TCP服務器實例(管理監聽和連接) };int main() {// 創建服務器實例(監聽8888端口)TranslateServer tmpserver(8888);std::cout << "翻譯服務器啟動,監聽端口:8888" << std::endl;// 啟動服務(阻塞在此處直到程序終止)tmpserver.Start();return 0; }
-
音譯漢客戶端代碼
#include "muduo/base/CountDownLatch.h" #include "muduo/net/EventLoopThread.h" #include "muduo/net/TcpClient.h" #include "muduo/net/TcpConnection.h" #include <functional> #include <iostream> #include <sys/types.h>/*** 基于muduo網絡庫的翻譯客戶端* 功能:連接翻譯服務器,發送英文單詞并接收中文翻譯結果*/ class TanslateClient // 注意類名拼寫建議改為 TranslateClient { public:/*** 構造函數* @param ip 服務器IP地址(默認本地環回)* @param port 服務器端口(默認8888)* 初始化流程:* 1. 創建事件循環線程(_loopthread)* 2. 創建TCP客戶端(_client),綁定到指定服務器地址* 3. 設置連接和消息回調*/TanslateClient(const std::string &ip = "127.0.0.1", const int &port = 8888): _loopthread(), // 事件循環線程(自動啟動)_client(_loopthread.startLoop(), // 獲取事件循環對象(啟動IO線程)muduo::net::InetAddress(ip, port), // 服務器地址"TanslateClient" // 客戶端名稱(日志標識)),_count_down_lantch(1), // 連接同步閂鎖(初始計數器1)_conn_ptr(nullptr) // 當前連接指針(線程安全需改進){// 綁定連接狀態變化回調_client.setConnectionCallback(std::bind(&TanslateClient::ConnectionCallback, this, std::placeholders::_1));// 綁定消息到達回調_client.setMessageCallback(std::bind(&TanslateClient::MessageCallback, this,std::placeholders::_1, // TcpConnectionPtrstd::placeholders::_2, // Buffer*std::placeholders::_3 // Timestamp));}/// 連接到服務器(阻塞直到連接建立或失敗)void Connect(){_client.connect(); // 發起異步連接_count_down_lantch.wait(); // 阻塞等待連接成功信號}/// 發送翻譯請求到服務器(線程安全)void Send(const std::string &buffer){// 需添加互斥鎖保護_conn_ptr訪問(當前實現存在線程安全隱患)if (_conn_ptr && _conn_ptr->connected()) {_conn_ptr->send(buffer + "\r\n"); // 添加消息分隔符} else {std::cerr << "連接未就緒,發送失敗" << std::endl;}}private:/*** 連接狀態變化回調* @param conn TCP連接智能指針* 功能:處理連接建立/斷開事件,更新連接狀態*/void ConnectionCallback(const muduo::net::TcpConnectionPtr &conn){_conn_ptr = conn; // 需用互斥鎖保護(當前非線程安全)if (conn->connected()) {std::cout << "成功連接服務器 [" << conn->peerAddress().toIpPort() << "]" << std::endl;_count_down_lantch.countDown(); // 釋放等待線程} else {std::cout << "與服務器斷開連接" << std::endl;// 建議添加自動重連邏輯}}/*** 消息到達回調* @param buffer 接收緩沖區(包含服務器響應)* 功能:處理服務器返回的翻譯結果*/void MessageCallback(const muduo::net::TcpConnectionPtr &conn,muduo::net::Buffer *buffer,muduo::Timestamp timestamp){// 提取并打印所有接收數據std::cout << "[翻譯結果] " << buffer->retrieveAllAsString() // 清空緩沖區<< std::endl;}private:muduo::net::EventLoopThread _loopthread; // 事件循環線程(IO線程)muduo::net::TcpClient _client; // TCP客戶端實例muduo::net::TcpConnectionPtr _conn_ptr; // 當前連接(需線程安全訪問)muduo::CountDownLatch _count_down_lantch; // 連接同步閂鎖 };int main() {// 創建客戶端實例(連接指定服務器)TanslateClient myclient("112.74.40.147", 8888);try {myclient.Connect(); // 阻塞直到連接建立// 交互式發送模式while (true) {std::cout << "請輸入英文單詞(輸入quit退出): ";std::string input;std::cin >> input;if (input == "quit") break;myclient.Send(input); // 發送查詢請求}} catch (const std::exception& e) {std::cerr << "客戶端異常: " << e.what() << std::endl;}return 0; }
-
使用Makefile文件進行編譯(記得更改為自己的路徑)
# 默認構建目標(當直接運行make時,會構建這兩個目標) all: TranslateServer TanslateClient# 構建客戶端可執行文件 TanslateClient(注意目標名稱可能存在拼寫錯誤,建議改為TranslateClient) # 依賴關系:需要 TanslateClient.cpp 文件 TanslateClient: TanslateClient.cpp# 編譯命令說明:# -o $@ : 輸出文件名為目標名稱(即TanslateClient)# $^ : 輸入文件為所有依賴項(即TanslateClient.cpp)# -std=c++11 : 使用C++11標準# -I ../include : 添加頭文件搜索路徑(指向上級目錄的include文件夾)# -L ../lib : 添加庫文件搜索路徑(指向上級目錄的lib文件夾)# -lmuduo_net -lmuduo_base : 鏈接muduo網絡庫和基礎庫# -lpthread : 鏈接pthread線程庫g++ -o $@ $^ -std=c++11 -I ../include -L ../lib -lmuduo_net -lmuduo_base -lpthread# 構建服務端可執行文件 TranslateServer # 依賴關系:需要 TranslateServer.cpp 文件 TranslateServer: TranslateServer.cpp# 參數說明同上g++ -o $@ $^ -std=c++11 -I ../include -L ../lib -lmuduo_net -lmuduo_base -lpthread# 聲明偽目標(防止存在同名文件時make誤判) # 注:原寫法不夠規范,建議改為 .PHONY: all clean .PHONY: # 清理生成的可執行文件 clean:# 強制刪除客戶端和服務端可執行文件# 風險提示:rm -rf 需謹慎使用rm -rf TanslateClient TranslateServer
-
運行結果
-
服務器
-
客戶端
-
-
-
基于Muduo庫函數實現protobuf協議的通信
-
這里需要用到Muduo庫里自帶的協議處理器ProtobufCodec,以及自帶的請求分發器ProtobufDispatcher,與前面我們使用自己制定的協議不同,在使用Protobuf進行通信時,我們在 設置服務器接受消息時調用的回調函數時(_server.setMessageCallback())并不是我們自己設定的消息回調函數了,而是使用協議處理器ProtobufCodec中的ProtobufCodec::onMessage()函數。也就是說服務器接收到消息之后,立即傳給協議處理器ProtobufCodec,由它進行解析后,傳給請求分發器ProtobufDispatcher去找到對應的消息處理函數,對應的函數處理完請求后,通過_codec.send(conn, resp);將結果發回給協議處理器,經過協議處理再發給客戶端。
-
客戶端的接受服務器的Protobuf請求流程與服務器差不多,這里就不過多贅述
-
定義具體的業務請求類型(也就是編寫request.proto文件)
// 定義協議版本和包名 syntax = "proto3"; // 使用proto3語法 package HQJ; // 包命名空間(防?命名沖突)// 翻譯服務請求消息(客戶端 -> 服務端) message tanslate_request { string word = 1; // 待翻譯的單詞(如:輸入"hello") };// 翻譯服務響應消息(服務端 -> 客戶端) message tanslate_respond { string word = 1; // 翻譯結果(如:返回"你好") };// 加法計算請求消息(客戶端 -> 服務端) message add_request {uint64 a = 1; // 第?個操作數(示例值:100)uint64 b = 2; // 第?個操作數(示例值:200) };// 加法計算響應消息(服務端 -> 客戶端) message add_respond {uint64 result = 1; // 計算結果(示例值:300) };
-
編譯request.proto文件
protoc --cpp_out=./ ./request.proto
-
編寫服務端代碼
#include "muduo/protobuf/codec.h" #include "muduo/protobuf/dispatcher.h" #include "muduo/base/Logging.h" #include "muduo/base/Mutex.h" #include "muduo/net/EventLoop.h" #include "muduo/net/TcpServer.h" #include "request.pb.h" // 包含protobuf生成的頭文件 #include <iostream> // 使用muduo庫中的noncopyable類禁止拷貝 class protobuf_server : public muduo::noncopyable { public: // 定義protobuf消息的智能指針類型 typedef std::shared_ptr<HQJ::add_request> add_request_ptr; typedef std::shared_ptr<HQJ::add_respond> add_respond_ptr; typedef std::shared_ptr<HQJ::tanslate_request> tanslate_request_ptr; typedef std::shared_ptr<HQJ::tanslate_respond> tanslate_respond_ptr; // 構造函數,初始化服務器和消息處理邏輯 protobuf_server(int port = 8888) : _server(&_loop, muduo::net::InetAddress("0.0.0.0", port), "protobuf_server", muduo::net::TcpServer::kReusePort), // 注冊未知消息處理函數 _dispatcher(std::bind(&protobuf_server::onUnknownMessage, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)), // 初始化協議處理器,綁定消息處理函數 _codec(std::bind(&ProtobufDispatcher::onProtobufMessage, &_dispatcher, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)) { // 注冊特定消息處理函數 _dispatcher.registerMessageCallback<HQJ::tanslate_request>( std::bind(&protobuf_server::onTranslate, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); _dispatcher.registerMessageCallback<HQJ::add_request>( std::bind(&protobuf_server::onAdd, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); // 設置連接回調 _server.setConnectionCallback( std::bind(&protobuf_server::onConnection, this, std::placeholders::_1)); // 設置消息回調,用于處理接收到的消息 _server.setMessageCallback( std::bind(&ProtobufCodec::onMessage, &_codec, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); } // 啟動服務器 void start() { _server.start(); _loop.loop(); } private: // 簡單的翻譯函數 const std::string translate(const std::string &str) { static std::unordered_map<std::string, std::string> dictionary{{"hello", "你好"}, {"love", "愛"}}; auto cur = dictionary.find(str); if (cur == dictionary.end()) { return ""; } else { return cur->second; } } // 未知消息處理函數 void onUnknownMessage(const muduo::net::TcpConnectionPtr &conn, const MessagePtr &message, muduo::Timestamp) { LOG_INFO << "onUnknownMessage: " << message->GetTypeName(); conn->shutdown(); } // 處理翻譯請求 void onTranslate(const muduo::net::TcpConnectionPtr &conn, const tanslate_request_ptr&message, muduo::Timestamp) { std::string request = message->word(); std::string res = translate(request); HQJ::tanslate_respond resp; resp.set_word(res); _codec.send(conn, resp); } // 處理加法請求 void onAdd(const muduo::net::TcpConnectionPtr &conn, const add_request_ptr &message, muduo::Timestamp) { int num1 = message->a(); int num2 = message->b(); int result = num1 + num2; HQJ::add_respond resp; resp.set_result(result); _codec.send(conn, resp); } // 連接回調 void onConnection(const muduo::net::TcpConnectionPtr &connection) { if (connection->connected()) { std::cout << "創建新的連接!" << std::endl; } else { std::cout << "關閉連接!" << std::endl; } } private: muduo::net::EventLoop _loop; // 事件循環 muduo::net::TcpServer _server; // TCP服務器 ProtobufDispatcher _dispatcher; // 請求分發器 ProtobufCodec _codec; // Protobuf協議編解碼器 }; int main() { protobuf_server prot_server(8888); prot_server.start(); return 0; }
-
編寫客戶端代碼
#include "muduo/base/CountDownLatch.h" #include "muduo/base/Logging.h" #include "muduo/base/Mutex.h" #include "muduo/net/EventLoop.h" #include "muduo/net/EventLoopThread.h" #include "muduo/net/TcpClient.h" #include "muduo/protobuf/codec.h" #include "muduo/protobuf/dispatcher.h"#include "request.pb.h" #include <iostream>class Client {public:typedef std::shared_ptr<google::protobuf::Message> MessagePtr;typedef std::shared_ptr<HQJ::add_respond> add_respondPtr;typedef std::shared_ptr<HQJ::tanslate_respond> tanslate_respondPtr;Client(const std::string &sip, int sport) : _latch(1),_client(_loopthread.startLoop(), muduo::net::InetAddress(sip, sport), "Client"),_dispatcher(std::bind(&Client::onUnknownMessage, this, std::placeholders::_1,std::placeholders::_2, std::placeholders::_3)),_codec(std::bind(&ProtobufDispatcher::onProtobufMessage, &_dispatcher,std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)){// 注冊請求處理時的回調函數_dispatcher.registerMessageCallback<HQJ::tanslate_respond>(std::bind(&Client::onTranslate, this,std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));_dispatcher.registerMessageCallback<HQJ::add_respond>(std::bind(&Client::onAdd, this,std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));// 設置客戶端接收到消息時調用的函數,交給協議處理機中的消息回調函數,不用我們自己寫了_client.setMessageCallback(std::bind(&ProtobufCodec::onMessage, &_codec,std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));// 設置客戶端連接成功時調用的函數,這個是需要自己寫的_client.setConnectionCallback(std::bind(&Client::onConnection, this, std::placeholders::_1));}void connect(){_client.connect();_latch.wait(); // 阻塞等待,直到連接建立成功}//制造并發送一個翻譯請求void Translate(const std::string &msg){HQJ::tanslate_request req;req.set_word(msg);send(&req);}//制造并發送一個加法請求void Add(int num1, int num2){HQJ::add_request req;req.set_a(num1);req.set_b(num2);send(&req);}private:// 連接時調用void onConnection(const muduo::net::TcpConnectionPtr &conn){if (conn->connected()){_latch.countDown(); // 喚醒主線程中的阻塞_conn = conn;}else{// 連接關閉時的操作_conn.reset();}}bool send(const google::protobuf::Message *message){if (_conn->connected()){ // 連接狀態正常,再發送,否則就返回false_codec.send(_conn, *message);return true;}return false;}void onUnknownMessage(const muduo::net::TcpConnectionPtr &,const MessagePtr &message,muduo::Timestamp){LOG_INFO << "onUnknownMessage: " << message->GetTypeName();}void onTranslate(const muduo::net::TcpConnectionPtr &conn, const tanslate_respondPtr &message, muduo::Timestamp){std::cout << "翻譯結果:" << message->word() << std::endl;}void onAdd(const muduo::net::TcpConnectionPtr &conn, const add_respondPtr &message, muduo::Timestamp){std::cout << "加法結果:" << message->result() << std::endl;}private:muduo::CountDownLatch _latch; // 實現同步的muduo::net::EventLoopThread _loopthread; // 異步循環處理線程muduo::net::TcpConnectionPtr _conn; // 客戶端對應的連接muduo::net::TcpClient _client; // 客戶端ProtobufDispatcher _dispatcher; // 請求分發器ProtobufCodec _codec; // 協議處理器 };int main() {Client client("127.0.0.1", 8888);client.connect();client.Translate("hello");client.Add(11, 22);sleep(1);return 0; }
-
使用Makefile編譯
# 默認構建目標(當直接運行make時,會構建client和server兩個目標) all: client server# 構建客戶端可執行文件 # 依賴文件:protobuf_client.cpp + request.pb.cc + muduo的protobuf編解碼器實現 # 編譯說明: # -std=c++11 : 使用C++11標準 # $^ : 表示所有依賴文件(即冒號后的全部文件) # -o $@ : 輸出文件名為目標名稱(即client) # -I../include : 添加頭文件搜索路徑(指向muduo頭文件目錄) # -L../lib : 添加庫文件搜索路徑(指向muduo庫目錄) # -l參數鏈接的庫:muduo網絡庫/基礎庫、protobuf庫、zlib壓縮庫 # -pthread : 啟用POSIX線程支持 client: protobuf_client.cpp request.pb.cc ../include/muduo/protobuf/codec.ccg++ -std=c++11 $^ -o $@ -I../include -L../lib -lmuduo_net -lmuduo_base -pthread -lprotobuf -lz# 構建服務端可執行文件(參數含義與客戶端相同) server: protobuf_server.cpp request.pb.cc ../include/muduo/protobuf/codec.ccg++ -std=c++11 $^ -o $@ -I../include -L../lib -lmuduo_net -lmuduo_base -pthread -lprotobuf -lz# 聲明偽目標 .PHONY:# 清理生成的可執行文件 clean:rm -rf client server # 強制刪除客戶端和服務端可執行文件
-
執行結果
-
客戶端
-
服務端
-
-
SQLlite3的快速上手
SQLite3的官方文檔
https://www.sqlite.org/c3ref/funclist.html
SQLite3是什么?
- SQLite是?個進程內的輕量級數據庫,它實現了?給??的、?服務器的、零配置的、事務性的 SQL數據庫引擎。它是?個零配置的數據庫,這意味著與其他數據庫不?樣,我們不需要在系統中配置。像其他數據庫,SQLite 引擎不是?個獨?的進程,可以按應?程序需求進?靜態或動態連接,SQLite直接訪問其存儲?件
為什么選擇SQLite?
-
不需要?個單獨的服務器進程或操作的系統(?服務器的)
-
SQLite 不需要配置
-
?個完整的 SQLite 數據庫是存儲在?個單?的跨平臺的磁盤?件
-
SQLite 是?常?的,是輕量級的,完全配置時?于 400KiB,省略可選功能配置時?于250KiB
-
SQLite 是?給??的,這意味著不需要任何外部的依賴
-
SQLite 事務是完全兼容 ACID 的,允許從多個進程或線程安全訪問
-
SQLite ?持 SQL92(SQL2)標準的?多數查詢語?的功能
-
SQLite 使? ANSI-C 編寫的,并提供了簡單和易于使?的 API
-
SQLite 可在 UNIX(Linux, Mac OS-X, Android, iOS)和 Windows(Win32, WinCE, WinRT)中運?
SQLite3 C/C++ API介紹
sqlite3操作流程:0. 查看當前數據庫在編譯階段是否啟動了線程安全int sqlite3_threadsafe(); 0-未啟?; 1-啟?需要注意的是sqlite3是有三種安全等級的:1. ?線程安全模式2. 線程安全模式(不同的連接在不同的線程/進程間是安全的,即?個句柄不能?于多線程間)3. 串?化模式(可以在不同的線程/進程間使?同?個句柄)1. 創建/打開數據庫?件,并返回操作句柄int sqlite3_open(const char *filename, sqlite3 **ppDb) 成功返回SQLITE_OK//若在編譯階段啟動了線程安全,則在程序運?階段可以通過參數選擇線程安全等級int sqlite3_open_v2(const char *filename, sqlite3 **ppDb, int flags, constchar *zVfs );
flag:
SQLITE_OPEN_READWRITE -- 以可讀可寫?式打開數據庫?件SQLITE_OPEN_CREATE -- 不存在數據庫?件則創建SQLITE_OPEN_NOMUTEX--多線程模式,只要不同的線程使?不同的連接即可保證線程安全SQLITE_OPEN_FULLMUTEX--串?化模式返回:SQLITE_OK表?成功2. 執?語句int sqlite3_exec(sqlite3*, char *sql, int (*callback)(void*,int,char**,char**), void* arg, char **err)int (*callback)(void*,int,char**,char**)void* : 是設置的在回調時傳?的arg參數int:??中數據的列數char**:存儲??數據的字符指針數組char**:每?列的字段名稱這個回調函數有個int返回值,成功處理的情況下必須返回0,返回?0會觸發ABORT退出程序返回:SQLITE_OK表?成功3. 銷毀句柄int sqlite3_close(sqlite3* db); 成功返回SQLITE_OKint sqlite3_close_v2(sqlite3*); 推薦使?--?論如何都會返回SQLITE_OK獲取錯誤信息const char *sqlite3_errmsg(sqlite3* db);
SQLite3 C/C++ API 的簡單使用
- SqliteHelper類的編寫
- 用來方便我們進行數據庫的操作
#include <iostream>
#include <sqlite3.h>
#include <string>
#include <vector>
#include"Logger.hpp"class SqliteHelper
{ public: // 定義一個回調函數類型,用于sqlite3_exec的回調 typedef int (*SqliteCallback)(void *, int, char **, char **); // 構造函數,接收數據庫文件名 SqliteHelper(const std::string dbfilename) : _dbfilename(dbfilename) { } // 打開數據庫 // 參數safe_leve用于指定打開數據庫的附加模式,默認為SQLITE_OPEN_FULLMUTEX bool open(int safe_leve = SQLITE_OPEN_FULLMUTEX) { // 使用sqlite3_open_v2函數打開或創建數據庫 int ret = sqlite3_open_v2(_dbfilename.c_str(), &_handler, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | safe_leve, nullptr); if (ret != SQLITE_OK) { // std::cout << "創建/打開sqlite數據庫失敗: "; // std::cout << sqlite3_errmsg(_handler) << std::endl; ELOG("創建/打開sqlite數據庫失敗: %s",sqlite3_errmsg(_handler));return false; } return true; } // 關閉數據庫 void close() { // 使用sqlite3_close_v2函數關閉數據庫 if (_handler) sqlite3_close_v2(_handler); } // 執行SQL語句 // 參數sql為要執行的SQL語句,cb為回調函數,arg為回調函數的參數 bool exec(const std::string &sql, SqliteCallback cb, void *arg) { // 使用sqlite3_exec函數執行SQL語句 int ret = sqlite3_exec(_handler, sql.c_str(), cb, arg, nullptr); if (ret != SQLITE_OK) { // std::cout << sql << std::endl; // std::cout << "執行語句失敗: "; // std::cout << sqlite3_errmsg(_handler) << std::endl; ELOG("執行語句:%s 失敗!\t錯誤原因: %s",sql.c_str(),sqlite3_errmsg(_handler));return false; } return true; } private: std::string _dbfilename; // 數據庫文件名 sqlite3 *_handler; // 數據庫句柄
};
-
SqliteHelper類的測試,編寫main.cc文件
#include "test_sqlite.hpp" // 包含SQLite操作的輔助類頭文件 #include <cassert> // 包含斷言頭文件,用于檢查程序中的假設// SQLite查詢回調函數,用于處理查詢結果 int select_stu_callback(void *arg, int col_count, char **result, char **fields_name) {std::vector<std::string> *arry = (std::vector<std::string> *)arg; // 將void*類型的參數轉換為std::vector<std::string>*類型arry->push_back(result[0]); // 將查詢結果的第一列添加到向量中return 0; // 返回0表示成功 }int main() {SqliteHelper helper("./test.db"); // 創建一個SqliteHelper對象,用于操作數據庫// 1. 創建/打開庫文件assert(helper.open()); // 打開數據庫文件,如果文件不存在則創建// 2. 創建表(不存在則創建), 學生信息: 學號,姓名,年齡std::string ct = "create table if not exists student(sn int primary key, name varchar(32), age int);";assert(helper.exec(ct, nullptr, nullptr)); // 執行創建表的SQL語句// 3. 新增數據 , 修改, 刪除, 查詢std::string insert_sql = "insert into student values(1, '小明', 18), (2, '小黑', 19), (3, '小紅', 18);";assert(helper.exec(insert_sql, nullptr, nullptr)); // 執行插入數據的SQL語句std::string update_sql = "update student set name='張小明' where sn=1";assert(helper.exec(update_sql, nullptr, nullptr)); // 執行更新數據的SQL語句std::string delete_sql = "delete from student where sn=3";assert(helper.exec(delete_sql, nullptr, nullptr)); // 執行刪除數據的SQL語句std::string select_sql = "select name from student;";std::vector<std::string> arry;assert(helper.exec(select_sql, select_stu_callback, &arry)); // 執行查詢SQL語句,并使用回調函數處理查詢結果for (auto &name : arry){std::cout << name << std::endl; // 輸出查詢結果}// 4. 關閉數據庫helper.close(); // 關閉數據庫連接return 0; }
-
編譯
g++ -std=c++11 main.cc -o main -lsqlite3
-
運行結果
對GTest的快速上手
什么是GTest?
- GTest是?個跨平臺的 C++單元測試框架,由google公司發布。gtest是為了在不同平臺上為編寫C++單元測試??成的。它提供了豐富的斷?、致命和?致命判斷、參數化等等
GTest的使用
-
TEST宏
TEST(test_case_name, test_name) TEST_F(test_fixture,test_name)
-
TEST:主要?來創建?個簡單測試, 它定義了?個測試函數, 在這個函數中可以使?任何C++代碼并且使?框架提供的斷?進?檢查
-
TEST_F:主要?來進?多樣測試,適?于多個測試場景如果需要相同的數據配置的情況, 即相同的數據測不同的?為
-
GTest中的斷言
-
分類:
- ASSERT_系列:如果當前點檢測失敗則退出當前函數_
- EXPECT_系列:如果當前點檢測失敗則繼續往下執?
-
常用斷言的介紹
// bool值檢查 ASSERT_TRUE(參數),期待結果是true ASSERT_FALSE(參數),期待結果是false //數值型數據檢查 ASSERT_EQ(參數1,參數2),傳?的是需要?較的兩個數 equal ASSERT_NE(參數1,參數2),not equal,不等于才返回true ASSERT_LT(參數1,參數2),less than,?于才返回true ASSERT_GT(參數1,參數2),greater than,?于才返回true ASSERT_LE(參數1,參數2),less equal,?于等于才返回true ASSERT_GE(參數1,參數2),greater equal,?于等于才返回true
-
簡單的斷言測試程序(assert.cpp)
#include <gtest/gtest.h> // 引入Google Test框架的頭文件 #include <iostream> using std::cout; using std::endl; // 定義一個測試用例,屬于"test"測試套件,用例名稱為"testname_less_than" TEST(test, testname_less_than) { int age = 20; // 定義一個整型變量age,并初始化為20 EXPECT_LT(age, 18); // 使用EXPECT_LT斷言宏,期望age小于18,若不滿足則測試失敗,但繼續執行后續測試 } // 定義一個測試用例,屬于"test"測試套件,用例名稱為"testname_great_than" TEST(test, testname_great_than) { int age = 20; // 定義一個整型變量age,并初始化為20 EXPECT_GT(age, 18); // 使用EXPECT_GT斷言宏,期望age大于18,若滿足則測試通過,否則測試失敗,但繼續執行后續測試 } // 程序的主函數,程序的執行入口 int main(int argc,char* argv[]) { testing::InitGoogleTest(&argc,argv); // 初始化Google Test框架 RUN_ALL_TESTS(); // 運行所有已定義的測試用例 return 0; // 程序正常結束,返回0 }
-
編譯
g++ -o assert assert.cpp -std=c++11 -lgtest
-
結果
-
GTest中的事件機制
- 事件機制的最?好處就是能夠為我們各個測試?例提前準備好測試環境,并在測試完畢后?于銷毀環境,這樣有個好處就是如果我們有?端代碼需要進?多種不同?法的測試,則可以通過測試機制在每個測試?例進?之前初始化測試環境和數據,并在測試完畢后清理測試造成的影響。
-
測試程序:?個測試程序只有?個main函數,也可以說是?個可執?程序是?個測試程序。該級別的事件機制是在程序的開始和結束執?
-
測試套件:代表?個測試?例的集合體,該級別的事件機制是在整體的測試案例開始和結束執?
-
測試?例:該級別的事件機制是在每個測試?例開始和結束都執?
-
GTest提供的三種常見事件:
-
全局事件:針對整個測試程序。實現全局的事件機制,需要創建?個??的類,然后繼承
testing::Environment類,然后分別實現成員函數 SetUp 和 TearDown ,同時在main函數內進
?調? testing::AddGlobalTestEnvironment(new MyEnvironment); 函數添加全局的事件機制
-
簡單的測試程序
#include <gtest/gtest.h> // 引入Google Test框架的頭文件 #include <iostream> #include <map> using std::cout; // 使用std命名空間中的cout對象,用于標準輸出 using std::endl; // 使用std命名空間中的endl對象,用于輸出換行符 using std::string; // 自定義環境類,繼承自testing::Environment class MyEnvironment : public testing::Environment { public: // 重寫SetUp方法,用于單元測試前的環境初始化 virtual void SetUp() override { std::cout << "單元測試執行前的環境初始化!!\n"; } // 重寫TearDown方法,用于單元測試后的環境清理 virtual void TearDown() override { std::cout << "單元測試執行完畢后的環境清理!!\n"; } }; // 定義兩個測試用例,均屬于MyEnvironment測試套件 TEST(MyEnvironment, test1) { std::cout << "單元測試1\n"; } TEST(MyEnvironment, test2) { std::cout << "單元測試2\n"; } // 定義一個全局的map,用于測試 std::map<string, string> mymap; // 自定義的MyMapTest環境類,繼承自testing::Environment class MyMapTest : public testing::Environment { public: // 重寫SetUp方法,用于單元測試前的環境初始化,向map中插入數據 virtual void SetUp() override { cout << "測試mymap單元測試" << endl; mymap.insert(std::make_pair("hello", "你好")); mymap.insert(std::make_pair("no", "不要")); } // 重寫TearDown方法,用于單元測試后的環境清理,清空map virtual void TearDown() override { mymap.clear(); cout << "單元測試執行完畢" << endl; } }; // 定義兩個測試用例,均屬于MyMapTest測試套件 TEST(MyMapTest, test1) { // 期望mymap的大小為2 EXPECT_EQ(mymap.size(), 2); // 從mymap中刪除鍵為"no"的元素 mymap.erase("no"); } TEST(MyMapTest, test2) { // 期望mymap的大小仍為2(但由于test1中已經刪除了一個元素,這個期望實際上是不正確的) EXPECT_EQ(mymap.size(), 2); } // 程序的主函數,程序的執行入口 int main(int argc, char* argv[]) { testing::InitGoogleTest(&argc, argv); // 初始化Google Test框架 testing::AddGlobalTestEnvironment(new MyMapTest); // 注冊MyMapTest環境 testing::AddGlobalTestEnvironment(new MyEnvironment); // 注冊MyEnvironment環境 RUN_ALL_TESTS(); // 運行所有已定義的測試用例 return 0; // 程序正常結束,返回0 }
- 編譯
g++ -o global global.cpp -std=c++11 -lgtest
-
結果
-
-
TestSuite事件:針對?個個測試套件。測試套件的事件機制我們同樣需要去創建?個類,繼承?testing::Test ,實現兩個靜態函數 SetUpTestCase 和 TearDownTestCase ,測試套件的事件機制不需要像全局事件機制?樣在 main 注冊,?是需要將我們平時使?的 TEST 宏改為 TEST_F 宏。
-
SetUpTestCase() 函數是在測試套件第?個測試?例開始前執?
-
TearDownTestCase() 函數是在測試套件最后?個測試?例結束后執?
-
需要注意TEST_F的第?個參數是我們創建的類名,也就是當前測試套件的名稱,這樣在
TEST_F宏的測試套件中就可以訪問類中的成員了
-
簡單的測試程序
#include<iostream> // 包含標準輸入輸出流庫 #include<gtest/gtest.h> // 包含Google Test框架的頭文件 #include<map> // 包含標準庫中的map容器 // 使用聲明,避免每次調用時都需要std::前綴 using std::string; using std::cout; using std::endl; using std::make_pair; // 定義測試套件SuitTest,繼承自testing::Test class SuitTest : public testing::Test { public: // 在測試套件中的所有測試開始之前調用 static void SetUpTestCase() { std::cout << "環境1第一個TEST之前調用\n"; } // 在測試套件中的所有測試結束之后調用 static void TearDownTestCase() { std::cout << "環境1最后一個TEST之后調用\n"; } // 在每個測試用例之前調用,用于初始化測試環境 virtual void SetUp() override { mymap.insert(make_pair("hsq","哈士奇")); cout<<"這是每個單元測試自己的初始化"<<endl; } // 在每個測試用例之后調用,用于清理測試環境 virtual void TearDown() { cout<<"這是每個單元自己的結束函數"<<endl; } public: std::map<std::string,std::string> mymap; // 測試用例共享的成員變量 }; // 定義第一個測試用例testInsert,測試插入操作 TEST_F(SuitTest,testInsert) { mymap.insert(make_pair("nihao","你好")); EXPECT_EQ(mymap.size(),1); // 期望map的大小為1 } // 定義第二個測試用例testSize,測試map的大小 TEST_F(SuitTest,testSize) { EXPECT_EQ(mymap.size(),1); // 期望map的大小為1 } // 主函數,程序的入口點 int main(int argc,char* argv[]) { testing::InitGoogleTest(&argc,argv); // 初始化Google Test RUN_ALL_TESTS(); // 運行所有測試用例 return 0; }
-
編譯
g++ -o suit suit.cpp -std=c++11 -lgtest
-
結果
-
-
-
TestCase事件: 針對?個個測試?例。測試?例的事件機制的創建和測試套件的基本?樣,不同地?在于測試?例實現的兩個函數分別是 SetUp 和 TearDown , 這兩個函數也不是靜態函數
- SetUp()函數是在?個測試?例的開始前執?
- TearDown()函數是在?個測試?例的結束后執?
- 也就是說,在TestSuite/TestCase事件中,每個測試?例,雖然它們同?同?個事件環境類,可以訪問其中的資源,但是本質上每個測試?例的環境都是獨?的,這樣我們就不?擔?不同的測試?例之間會有數據上的影響了,保證所有的測試?例都使?相同的測試環境進?測試。
-