手撕基于AMQP協議的簡易消息隊列-2(所用第三方庫的介紹與簡單使用)

第三方庫的介紹

Protobuf

什么是Protobuf(Protocol Buffer)?
  • Protobuf是數據結構序列化和反序列化框架
Protobuf的特點有哪些?
  • 通用性:語??關、平臺?關。即 ProtoBuf ?持 Java、C++、Python 等多種語?,?持多個平臺
  • ?效:即? XML 更?、更快、更為簡單
  • 擴展性、兼容性好:你可以更新數據結構,?不影響和破壞原有的舊程序
Protobuf的使用
  • 使用流程:

    1. 編寫后綴為.proto的文件,在該文件中定義結果對象及屬性內容
    2. 使? protoc 編譯器編譯 .proto ?件,?成?系列接?代碼,存放在新?成頭?件和源?件中
    3. 依賴?成的接?,將編譯?成的頭?件包含進我們的代碼中,實現對 .proto ?件中定義的字段進?設置和獲取,和對自定義對象進?序列化和反序列化
  • 使用示例

    1. .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文件的編寫規范:

        1. ?件命名應該使?全?寫字?命名,多個字?之間? _ 連接,如student.proto
        2. 書寫 .proto ?件代碼時,應使? 2 個空格的縮進
      • 定義消息字段;

        1. 字段定義格式為:字段類型 字段名 = 字段唯?編號;

        2. 字段名稱命名規范:全?寫字?,多個字?之間? _ 連接

        3. 字段類型分為:標量數據類型 和 特殊類型(包括枚舉、其他消息類型等)

          .proto 類型C++ 類型說明
          doubledouble雙精度浮點數
          floatfloat單精度浮點數
          int32int32變長編碼的32位整數。負數編碼效率低,若字段可能為負值,建議改用 sint32
          int64int64變長編碼的64位整數。負數編碼效率低,若字段可能為負值,建議改用 sint64
          uint32uint32變長編碼的無符號32位整數
          uint64uint64變長編碼的無符號64位整數
          sint32int32變長編碼的32位整數,ZigZag編碼優化負數效率,適用于可能包含負值的場景。
          sint64int64變長編碼的64位整數,ZigZag編碼優化負數效率,適用于可能包含負值的場景。
          fixed32uint32定長4字節的無符號32位整數,數值常大于2^28時更高效(相比uint32)。
          fixed64uint64定長8字節的無符號64位整數,數值常大于2^56時更高效(相比uint64)。
          sfixed32int32定長4字節的有符號32位整數
          sfixed64int64定長8字節的有符號64位整數
          boolbool布爾值(true/false
          stringstd::stringUTF-8或ASCII編碼字符串,長度不超過2^32。
          bytesstd::string任意二進制字節序列,長度不超過2^32(實際存儲為std::string,但語義上區分于字符串)。
          enum枚舉類型用戶自定義的枚舉類型,對應C++的enumenum class
          message類(class)用戶自定義的消息類型,對應C++中的類結構,可嵌套其他消息或枚舉。
          repeatedstd::vector<T>動態數組,存儲相同類型的多個元素(例如 repeated int32 對應 std::vector<int32>)。
          map<K, V>std::unordered_map<K, V>鍵值對集合,鍵類型需為整數、布爾或字符串,值類型可為任意有效類型(例如 map<string, int32> 對應 std::unordered_map<string, int32>)。
        4. 字段唯?編號:?來標識字段,?旦開始使?就不能夠再改變。

      • 字段唯?編號的范圍:1 ~ 536,870,911 (2^29 - 1) ,其中 19000 ~ 19999 不可?。19000 ~ 19999 不可?是因為:在 Protobuf 協議的實現中,對這些數進?了預留。如果?要在.proto?件中使?這些預留標識號,例如將 name 字段的編號設置為19000,編譯時就會報警

    2. 編譯.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代碼內容:

        1. 對于每個message,都會生成一個對應的消息類
        2. 在消息類中,編譯器為每個字段提供了獲取和設置?法,以及?下其他能夠操作字段的?法
        3. 編輯器會針對于每個 .proto ?件?成 .h 和 .cc ?件,分別?來存放類的聲明與類的實現student.pb.h、student.pb.cc 部分代碼展?
    3. 序列化與反序列化的使用

      • 示例程序

        #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指的是:

    1. ?個線程只能有?個事件循環(EventLoop), ?于響應計時器和IO事件

    2. ?個?件描述符只能由?個線程進?讀寫,換句話說就是?個TCP連接必須歸屬于某個EventLoop管理

    3. 請添加圖片描述

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)

      1. 初始化事件循環與服務器

        EventLoop loop;  // 創建主事件循環
        InetAddress listenAddr(8888);  // 監聽端口
        TcpServer server(&loop, listenAddr, "TranslateServer");
        
      2. 注冊回調函數

        // 連接建立/關閉回調
        server.setConnectionCallback(std::bind(&onConnection, ...));
        // 消息到達回調
        server.setMessageCallback(std::bind(&onMessage, ...));
        
      3. 啟動服務

        server.start();  // 啟動監聽線程
        loop.loop();     // 進入事件循環(阻塞在此)
        
      4. 實現核心回調函數

        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)

      1. 創建IO線程與客戶端

        EventLoopThread ioThread;  // 創建IO線程
        TcpClient client(ioThread.startLoop(), serverAddr, "Client");
        
      2. 設置回調函數

        client.setConnectionCallback(std::bind(&onClientConnection, ...));
        client.setMessageCallback(std::bind(&onClientMessage, ...));
        
      3. 連接服務器

        client.connect();  // 異步連接
        latch.wait();      // 等待連接建立(使用CountDownLatch)
        
      4. 實現客戶端回調

        void onClientConnection(const TcpConnectionPtr& conn) {if (conn->connected()) {latch_.countDown();  // 連接成功通知conn_ = conn;        // 保存連接對象}
        }void onClientMessage(const TcpConnectionPtr&, Buffer* buf, Timestamp) {cout << "收到響應: " << buf->retrieveAllAsString() << endl;
        }
        
      5. 發送請求

        conn_->send("hello");  // 使用保存的連接對象發送數據
        

      三、關鍵機制說明

      1. Reactor模型

        • 單線程處理所有IO事件(用戶代碼無需考慮鎖)

        • EventLoop::loop() 驅動事件分發

      2. 回調機制
        通過std::bind綁定成員函數,處理三類事件:

        • 連接建立/斷開

        • 消息到達

        • 寫完成通知(示例未使用)

      3. Buffer設計

        • 自動管理讀寫指針(retrieveAllAsString()消費數據)

        • 支持高效預置空間(處理協議頭)

      4. 線程安全

        • 客戶端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請求流程與服務器差不多,這里就不過多贅述

      請添加圖片描述

    1. 定義具體的業務請求類型(也就是編寫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)
      };
      
    2. 編譯request.proto文件

      protoc --cpp_out=./ ./request.proto
      
    3. 編寫服務端代碼

      #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;  
      }
      
    4. 編寫客戶端代碼

      #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;
      }
      
    5. 使用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  # 強制刪除客戶端和服務端可執行文件
      
    6. 執行結果

      • 客戶端

        請添加圖片描述

      • 服務端

        請添加圖片描述

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中的斷言
  • 分類:

    1. ASSERT_系列:如果當前點檢測失敗則退出當前函數_
    2. 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提供的三種常見事件:

    1. 全局事件:針對整個測試程序。實現全局的事件機制,需要創建?個??的類,然后繼承

      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
        
        • 結果

          請添加圖片描述

    2. TestSuite事件:針對?個個測試套件。測試套件的事件機制我們同樣需要去創建?個類,繼承?testing::Test ,實現兩個靜態函數 SetUpTestCase 和 TearDownTestCase ,測試套件的事件機制不需要像全局事件機制?樣在 main 注冊,?是需要將我們平時使?的 TEST 宏改為 TEST_F 宏。

      1. SetUpTestCase() 函數是在測試套件第?個測試?例開始前執?

      2. TearDownTestCase() 函數是在測試套件最后?個測試?例結束后執?

      3. 需要注意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
          
        • 結果

          請添加圖片描述

    3. TestCase事件: 針對?個個測試?例。測試?例的事件機制的創建和測試套件的基本?樣,不同地?在于測試?例實現的兩個函數分別是 SetUp 和 TearDown , 這兩個函數也不是靜態函數

      1. SetUp()函數是在?個測試?例的開始前執?
      2. TearDown()函數是在?個測試?例的結束后執?
      3. 也就是說,在TestSuite/TestCase事件中,每個測試?例,雖然它們同?同?個事件環境類,可以訪問其中的資源,但是本質上每個測試?例的環境都是獨?的,這樣我們就不?擔?不同的測試?例之間會有數據上的影響了,保證所有的測試?例都使?相同的測試環境進?測試。

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

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

相關文章

Altera系列FPGA實現圖像視頻采集轉HDMI/LCD輸出,提供4套Quartus工程源碼和技術支持

目錄 1、前言工程概述免責聲明 2、相關方案推薦我已有的所有工程源碼總目錄----方便你快速找到自己喜歡的項目Altera系列FPGA相關方案推薦 3、設計思路框架工程設計原理框圖輸入Sensor之-->OV7725攝像頭輸入Sensor之-->OV5640攝像頭輸入Sensor之-->串口傳圖輸入圖像緩…

ABP vNext 集成 CAP + RabbitMQ 實現可靠事件總線

&#x1f680; ABP vNext 集成 CAP RabbitMQ 實現可靠事件總線 在分布式系統中&#xff0c;事件總線是實現服務解耦與最終一致性的核心手段。本文將以 ABP vNext 8.1 為基礎&#xff0c;手把手教你如何集成 CAP RabbitMQ 構建可靠的事件驅動架構。 &#x1f3af; 本文適用于…

Linux 服務器靜態 IP 配置初始化指南

? 第一步&#xff1a;確認網絡管理方式 運行以下命令判斷系統使用的網絡管理服務&#xff1a; # 檢查 NetworkManager 是否活躍 systemctl is-active NetworkManager# 檢查 network&#xff08;舊服務&#xff09;是否活躍 systemctl is-active network或者檢查配置路徑&…

C++ 工具鏈與開發實踐:構建安全、高效與創新的開發生態

引言 在 C 的技術演進中&#xff0c;工具鏈的革新與開發實踐的迭代始終是推動語言生命力的核心動力。從內存安全的攻防體系到嵌入式設備的能效優化&#xff0c;從跨平臺開發的降本增效到開發者社區的生態構建&#xff0c;C 正通過工具鏈與方法論的雙重升級&#xff0c;應對復雜…

跨瀏覽器自動化測試的智能生成方法

一、背景與挑戰&#xff1a;跨瀏覽器測試為什么“難”&#xff1f; 在現代Web應用開發中&#xff0c;跨瀏覽器兼容性是用戶體驗的底線保障。面對Chrome、Firefox、Safari、Edge乃至IE、移動瀏覽器等多種運行環境&#xff0c;開發者與測試人員常面臨&#xff1a; 相同DOM在不同…

【Hive入門】Hive安全管理與權限控制:用戶認證與權限管理深度解析

目錄 引言 1 Hive安全管理體系概述 2 Hive用戶認證機制 2.1 Kerberos集成認證 2.1.1 Kerberos基本原理 2.1.2 Hive集成Kerberos配置步驟 2.1.3 Kerberos認證常見問題排查 2.2 LDAP用戶同步 2.2.1 LDAP協議概述 2.2.2 Hive集成LDAP配置 2.2.3 LDAP與Hive用戶同步架構…

0X. Linux嵌入式系統(課堂筆記)

目錄 一. 開發板橋接 二. 開發板白屏 三. 0324-MPU6050開發 3.1 函數詳解 3.2 常用 ioctl 請求碼&#xff08;request&#xff09; 3.3 頭文件詳解 四. 獲取鼠標信息 4.1 獲取鼠標信息 4.2 內核修改并編譯 五. QT基礎使用 六. 內核打印Hello world 七. 內核GPIO …

qml中的TextArea使用QSyntaxHighlighter顯示高亮語法

效果圖&#xff0c;左側顯示行號&#xff0c;右側用TextArea顯示文本內容&#xff0c;并且語法高亮。 2025年5月8號更新 1、多行文本注釋 多行文本注釋跟普通的高亮規則代碼不太一樣&#xff0c;代碼需要修改&#xff0c;這里以JavaScript舉例。 先制定多行文本注釋規則&…

【Python從入門到精通】--‘@‘符號的作用

在Python中&#xff0c;符號主要有三種用途&#xff1a;裝飾器&#xff08;Decorator&#xff09;、矩陣乘法運算符&#xff08;Python 3.5&#xff09;以及類型提示中的修飾符&#xff08;如typing&#xff09;。 目錄 1.--裝飾器&#xff08;Decorator&#xff09; 2.--矩…

VAE和Stable Diffusion的關系

文章目錄 ? 簡單回顧&#xff1a;什么是 VAE&#xff1f;&#x1f504; Stable Diffusion 和 VAE 的關系&#xff1a;&#x1f3af; 編碼器&#xff1a;&#x1f4a5; 解碼器&#xff1a; &#x1f914; 那 Stable Diffusion 本身是 VAE 嗎&#xff1f;&#x1f9e0; 簡要對比…

PyTorch_點積運算

點積運算要求第一個矩陣 shape:(n, m)&#xff0c;第二個矩陣 shape: (m, p), 兩個矩陣點積運算shape為&#xff1a;(n,p) 運算符 用于進行兩個矩陣的點乘運算torch.mm 用于進行兩個矩陣點乘運算&#xff0c;要求輸入的矩陣為3維 &#xff08;mm 代表 mat, mul&#xff09;to…

02_JVM

1、JVM虛擬機組成及內存分配 三大部分&#xff1a; 類裝載子系統JVM虛擬機字節碼執行引擎 其中&#xff0c;JVM虛擬機運行時數據區&#xff08;內存模型&#xff09;包含五部分&#xff1a;堆、棧&#xff08;線程&#xff09;、方法區&#xff08;元空間&#xff09;、本地…

基于FPGA控制PCF8591開展ADC采樣,以采樣煙霧模塊輸出模擬電壓為例(IIC通信)

基于FPGA控制PCF8591開展ADC采樣 前言一、芯片手冊閱讀1.設備地址2.字節地址3.IIC通信協議 二、仿真分析三、代碼分析總結視頻演示 前言 這段時間做設計總是遇到一些傳感器模塊輸出模擬電壓&#xff0c;采集模擬電壓進而了解傳感器輸出的濃度占比&#xff0c;在淘寶上找到了一…

在Python和C/C++之間共享std::vector<std::vector<int>>數據

在Python和C/C之間共享std::vector<std::vector>數據 在Python和C/C之間共享嵌套向量數據(std::vector<std::vector<int>>)可以通過幾種方法實現。以下是幾種常見的方法&#xff1a; 方法1: 使用Cython Cython是連接Python和C的很好選擇&#xff0c;它可以…

Linux NVIDIA 顯卡驅動安裝指南(適用于 RHEL/CentOS)

&#x1f4cc; 一、禁用 Nouveau 開源驅動 NVIDIA 閉源驅動與開源的 nouveau 驅動沖突&#xff0c;需先禁用&#xff1a; if [ ! -f /etc/modprobe.d/blacklist-nouveau.conf ]; thenecho -e "blacklist nouveau\noptions nouveau modeset0" | sudo tee /etc/modpr…

Python爬蟲實戰:獲取千庫網各類素材圖片,為設計師提供參考

一、引言 在當今設計領域,豐富的素材積累對設計師而言至關重要。千庫網作為一個素材資源豐富的平臺,擁有海量的各類素材圖片。然而,手動從該網站收集素材不僅耗時,而且效率低下。Python 作為一種功能強大的編程語言,具備豐富的庫和工具,可用于開發高效的爬蟲程序。通過 …

vue截圖-html2canvas

使用html2canvas進行截圖操作 在 Vue 中使用 ??html2canvas?? 將 HTML 元素&#xff08;如包含貝塞爾曲線的 Canvas/SVG&#xff09;轉換為圖片 下載html2canvas npm install html2canvas在頁面中使用&#xff0c;要截取哪個div的內容&#xff0c;先給這個div加一個ref標…

介紹Unity中的Dictionary

在 Unity&#xff08;C#&#xff09;中&#xff0c;Dictionary 是一個非常常用的數據結構&#xff0c;它提供 鍵值對&#xff08;Key-Value Pair&#xff09; 的存儲方式。類似于 Python 的 dict 或 JavaScript 的對象&#xff08;Object&#xff09;&#xff0c;但它是強類型的…

MySQL 常用函數(詳解)

目錄 一、數學函數1.1 四舍五入函數1.2 求絕對值函數二、日期時間函數2.1 獲取當前日期和時間三、字符串函數3.1 字符串拼接函數3.2 提取子字符串函數四、聚合函數4.1 計算平均值函數4.2 計算最大值函數五、轉換函數5.1 類型轉換函數六、總結MySQL 提供了豐富的內置函數,涵蓋了…

SOFA編譯-Ubuntu20.04-SOFA22.12

一、事前說明 單純的編譯sofa是很簡單的&#xff0c;但是想要同時編譯SofaPython3則比較難了&#xff0c;我編譯了v22.12分支&#xff0c;其他版本sofa的編譯也可以參考此篇教程&#xff0c;需注意的是&#xff1a; 1、確定SOFA需要的Python版本&#xff0c;sofa22.12需要的是…