muduo源碼分析之TcpServer模塊

来源:https://www.cnblogs.com/shilinkun/archive/2022/04/24/16187509.html
-Advertisement-
Play Games

《The Go Programming Language》 知識點記載,學習筆記、章節練習與個人思考。第一章內容。 ...


這次我們開始muduo源代碼的實際編寫,首先我們知道muduoLT模式,Reactor模式,下圖為Reactor模式的流程圖[來源1]

image-20220220154310731

然後我們來看下muduo的整體架構[來源1]

muduo

首先muduo有一個主反應堆mainReactor以及幾個子反應堆subReactor,其中子反應堆的個數由用戶使用setThreadNum函數設置,mainReactor中主要有一個Acceptor,當用戶建立新的連接的時候,Acceptor會將connfd和對應的事件打包為一個channel然後採用輪詢的演算法,指定將該channel給所選擇的subReactor,以後該subReactor就負責該channel的所有工作。

TcpServer類

我們按照從上到下的思路進行講解,以下內容我們按照一個簡單的EchoServer的實現思路來講解,我們知道當我們自己實現一個Server的時候,會在構造函數中實例化一個TcpServer

EchoServer(EventLoop *loop,
           const InetAddress &addr, 
           const std::string &name)
    : server_(loop, addr, name)
        , loop_(loop)
    {
        // 註冊回調函數
        server_.setConnectionCallback(
            std::bind(&EchoServer::onConnection, this, std::placeholders::_1)
        );

        server_.setMessageCallback(
            std::bind(&EchoServer::onMessage, this,
                      std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)
        );

        // 設置合適的loop線程數量 loopthread 不包括baseloop
        server_.setThreadNum(3);
    }

於是我們去看下TcpServer的構造函數是在乾什麼

TcpServer::TcpServer(EventLoop *loop,
                const InetAddress &listenAddr,
                const std::string &nameArg,
                Option option)
                : loop_(CheckLoopNotNull(loop))
                , ipPort_(listenAddr.toIpPort())
                , name_(nameArg)
                , acceptor_(new Acceptor(loop, listenAddr, option == kReusePort))
                , threadPool_(new EventLoopThreadPool(loop, name_))
                , connectionCallback_()
                , messageCallback_()
                , nextConnId_(1)
                , started_(0)
{
    // 當有新用戶連接時候,會執行該回調函數
    acceptor_->setNewConnectionCallback(std::bind(&TcpServer::newConnection, this, 
        std::placeholders::_1, std::placeholders::_2));
}

我們只需要關註acceptor_(new Acceptor(loop, listenAddr, option == kReusePort))threadPool_(new EventLoopThreadPool(loop, name_))
首先很明確的一點,構造了一個Acceptor,我們首先要知道Acceptor主要就是連接新用戶並打包為一個Channel,所以我們就應該知道Acceptor按道理應該實現socketbindlistenaccept這四個函數。

Acceptor::Acceptor(EventLoop *loop, const InetAddress &listenAddr, bool reuseport)
    : loop_(loop), acceptSocket_(createNonblocking()) // socket
      ,
      acceptChannel_(loop, acceptSocket_.fd()), listenning_(false)
{
    acceptSocket_.setReuseAddr(true);
    acceptSocket_.setReusePort(true);
    acceptSocket_.bindAddress(listenAddr); // 綁定套接字
    // 有新用戶的連接,執行一個回調(打包為channel)
    acceptChannel_.setReadCallback(std::bind(&Acceptor::handleRead, this));
}

其中Acceptor中有個acceptSocket_,其實就是我們平時所用的listenfd,構造函數中實現了socketbind,而其餘的兩個函數的使用在其餘代碼

// 開啟伺服器監聽
void TcpServer::start()
{
	// 防止一個TcpServer被start多次
    if (started_++ == 0) 
    {
        threadPool_->start(threadInitCallback_); // 啟動底層的loop線程池,這裡會按照設定了threadnum設置pool的數量
        loop_->runInLoop(std::bind(&Acceptor::listen, acceptor_.get()));
    }
}

我們知道,當我們設置了threadnum之後,就會有一個mainloop,那麼這個loop_就是那個mainloop,其中可以看見這個loop_就只做一個事情Acceptor::listen

void Acceptor::listen()
{
    listenning_ = true;
    acceptSocket_.listen();         // listen
    acceptChannel_.enableReading(); // acceptChannel_ => Poller
}

這裡就實現了listen函數,還有最後一個函數accept,我們慢慢向下分析,從代碼可以知道acceptChannel_.enableReading()之後就會使得這個listenfd所在的channel對讀事件感興趣,那什麼時候會有讀事件呢,就是當用戶建立新連接的時候,那麼我們應該想一下,那當感興趣的事件發生之後,listenfd應該乾什麼呢,應該執行一個回調函數呀。註意Acceptor構造函數中有這樣一行代碼acceptChannel_.setReadCallback(std::bind(&Acceptor::handleRead, this));這就是那個回調,我們去看下handleRead在幹嘛。

// listenfd有事件發生了,就是有新用戶連接了
void Acceptor::handleRead()
{
    InetAddress peerAddr;
    int connfd = acceptSocket_.accept(&peerAddr);
    if (connfd >= 0)
    {
        // 若用戶實現定義了,則執行,否則說明用戶對新到來的連接沒有需要執行的,所以直接關閉
        if (newConnectionCallback_)
        {
            newConnectionCallback_(connfd, peerAddr); // 輪詢找到subLoop,喚醒,分發當前的新客戶端的Channel
        }
        else
        {
            ::close(connfd);
        }
    }
    ...
}

這裡是不是就實現了accept函數,至此當用戶建立一個新的連接時候,Acceptor就會得到一個connfd和其對應的peerAddr返回給mainloop,這時候我們在註意到TcpServer構造函數中有這樣一行代碼acceptor_->setNewConnectionCallback(std::bind(&TcpServer::newConnection, this,std::placeholders::_1, std::placeholders::_2));我們給acceptor_設置了一個newConnectionCallback_,於是由上面的代碼就可以知道,if (newConnectionCallback_)為真,就會執行這個回調函數,於是就會執行TcpServer::newConnection,我們去看下這個函數是在幹嘛。

void TcpServer::newConnection(int sockfd, const InetAddress &peerAddr)
{
    // 輪詢演算法選擇一個subloop來管理對應的這個新連接
    EventLoop *ioLoop = threadPool_->getNextLoop(); 
    char buf[64] = {0};
    snprintf(buf, sizeof buf, "-%s#%d", ipPort_.c_str(), nextConnId_);
    ++nextConnId_;
    std::string connName = name_ + buf;

    LOG_INFO("TcpServer::newConnection [%s] - new connection [%s] from %s \n",
        name_.c_str(), connName.c_str(), peerAddr.toIpPort().c_str());

    // 通過sockfd獲取其綁定的本地ip和埠
    sockaddr_in local;
    ::bzero(&local, sizeof local);
    socklen_t addrlen = sizeof local;
    if (::getsockname(sockfd, (sockaddr*)&local, &addrlen) < 0)
    {
        LOG_ERROR("sockets::getLocalAddr");
    }
    InetAddress localAddr(local);

    // 根據連接成功的sockfd,創建TcpConnection
    TcpConnectionPtr conn(new TcpConnection(
                            ioLoop,
                            connName,
                            sockfd,   // Socket Channel
                            localAddr,
                            peerAddr));
    connections_[connName] = conn;
	// 下麵的回調時用戶設置給TcpServer,TcpServer又設置給TcpConnection,TcpConnetion又設置給Channel,Channel又設置給Poller,Poller通知channel調用這個回調
    conn->setConnectionCallback(connectionCallback_);
    conn->setMessageCallback(messageCallback_);
    conn->setWriteCompleteCallback(writeCompleteCallback_);

    // 設置瞭如何關閉連接的回調
    conn->setCloseCallback(
        std::bind(&TcpServer::removeConnection, this, std::placeholders::_1)
    );

    // 直接調用connectEstablished
    ioLoop->runInLoop(std::bind(&TcpConnection::connectEstablished, conn));
}

這裡就比較長了,我先說下大概他幹了啥事情:首先通過輪詢找到下一個subloop,然後將剛剛返回的connfd和對應的peerAddr以及localAddr構造為一個TcpConnectionsubloop,然後給這個conn設置了一系列的回調函數,比如讀回調,寫回調,斷開回調等等。下一章我們來說下上面的代碼最後幾行在幹嘛。

自己的網址:www.shicoder.top
歡迎加群聊天 452380935
本文由博客一文多發平臺 OpenWrite 發佈!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • BIM模型輕量化顯示地圖引擎、BIM模型文件轉換引擎、BIM線上編輯器是BIM模型輕量化的核心技術,具有一定的技術門檻。易景空間ESMap本著開放的精神,把核心技術提供給廣大的BIM開發者,使得開發者即使沒有掌握任何圖形技術,也能輕鬆開發強大的BIM應用。 ...
  • 背景介紹 當頁面內容很多的時候,用戶可能會一直往下滑動頁面。但是當他想返回頁面頂部進行其他操作時,他可能需要不斷滾動滑鼠滾輪,這就導致用戶體驗將很差。鑒於這種情況, “回到頂部”這一功能便出現了。 如今在我們瀏覽網站的時候,常常能遇見“回到頂部”這個按鈕。目前主流的回到頁面頂部分表現為兩種,一種是直 ...
  • 狀態模式是什麼 狀態模式是一種行為設計模式,讓你能在一個對象的內部狀態變化時改變其行為,使其看上去就像改變了自身所屬的類一樣。 為什麼要用狀態模式 如果對象需要根據自身當前狀態進行不同行為,同時狀態的數量非常多且與狀態相關的代碼會頻繁變更的話,可使用狀態模式。相似狀態和基於條件的狀態機轉換中存在許多 ...
  • 本篇主要講從技術體繫到商業洞察,分為五個小節,新一代開發範式雲原生、建立自己的技術體系、技術判斷力、技術業務產業周期,技術與商業洞察。對於新一代開發範式和技術體系的建立,文字部分我就不再補充,具體可以看看PPT。這裡我想講技術判斷力和商業洞察,這是建立技術體系之後,對關鍵點的判斷和洞察,是從點到面再 ...
  • 此文系【大話雲原生】系列第四篇,該系列文章期望用最通俗、簡單的語言說明白雲原生生態系統內的組成、架構以及應用關係。從這篇開始我們要開始針對Kubernetes進行介紹了,本文內容如下: 一、Kubernetes的Pod概念解析 前文說到老婆過生日了我們一起出去旅游,上了團體服務班車,小娜同學(老婆) ...
  • 什麼是分散式鎖 當多個進程在同一個系統中,用分散式鎖控制多個進程對資源的訪問 分散式鎖應用場景 傳統的單體應用單機部署情況下,可以使用java併發處理相關的API進行互斥控制。 分散式系統後由於多線程,多進程分佈在不同機器上,使單機部署情況下的併發控制鎖策略失效,為瞭解決跨JVM互斥機制來控制共用資 ...
  • 一、安裝matplotlib 1)由於已安裝anaconda,可直接打開anaconda prompt,再用命令pip install matplotlib進行安裝,因鏡像問題,可能較慢,建議第2種方式。 2)訪問https://pypi.org/project/matplotlib/#files, ...
  • 為什麼寫這個教程? 最近在學C++,翻了幾次菜鳥教程後,想寫個項目練練手。之前瞭解過ngrok,一個內網穿透工具,簡單講就是在內網中部署自己的服務,通過這個工具,可以讓公網上的用戶訪問到服務。 之前看了部分源碼,想把自己的一些優化想法用C實現一下,所以斷斷續續花了2個多月寫了C版本的CProxy。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...