服務(wù)器實(shí)戰(zhàn) (一):前置知識(shí)與功能目標(biāo))
系列文章目錄文章目錄系列文章目錄前言一、項(xiàng)目認(rèn)識(shí)二、知識(shí)背景2.1 HTTP服務(wù)器2.2 Reactor模型三、選擇的原因3.1 單 Reactor 單線程單 I/O 多路復(fù)用 業(yè)務(wù)處理3.2 單 Reactor 多線程單 I/O 多路復(fù)用 線程池業(yè)務(wù)處理3.3 多 Reactor 多線程多 I/O 多路復(fù)用 線程池業(yè)務(wù)處理四、項(xiàng)目解析4.1 SERVER模塊4.2 HTTP協(xié)議模塊總結(jié)前言大家好我是曦夜日長(zhǎng)。經(jīng)過(guò)這么長(zhǎng)時(shí)間的學(xué)習(xí)也是修得正果正式來(lái)到我們的標(biāo)準(zhǔn)項(xiàng)目欄目正如專欄所示本系列實(shí)現(xiàn)仿muduo庫(kù)OneThreadOneLoop式主從Reactor模型實(shí)現(xiàn)?并發(fā)服務(wù)器從零到有的開(kāi)發(fā)。請(qǐng)跟著我的腳步開(kāi)始我們第一個(gè)項(xiàng)目的開(kāi)發(fā)。一、項(xiàng)目認(rèn)識(shí)我們要開(kāi)發(fā)的項(xiàng)目服務(wù)器可以說(shuō)是最經(jīng)典的服務(wù)器模板這里給出百度資料主從 Reactor OneThreadOneLoop 模型至今仍廣泛用于工業(yè)級(jí)高性能服務(wù)C 生態(tài) muduo、brpcJava 生態(tài) Netty 均采用同類(lèi)架構(gòu)被應(yīng)用在即時(shí)通訊 IM、游戲網(wǎng)關(guān)、流媒體服務(wù)器、IoT 設(shè)備網(wǎng)關(guān)、RPC 網(wǎng)關(guān)等海量長(zhǎng)連接場(chǎng)景。當(dāng)然我們開(kāi)發(fā)的只是服務(wù)器底層其中內(nèi)部業(yè)務(wù)的實(shí)現(xiàn)還需要我們自己部署作為主從Reactor它有著這樣的特性主Reactor獲取到新連接后分發(fā)給?Reactor進(jìn)?通信事件監(jiān)控。而子Reactor線程監(jiān)控各?的描述符的讀寫(xiě)事件進(jìn)?數(shù)據(jù)讀寫(xiě)以及業(yè)務(wù)處理。在談及這樣的Reactor模型One Thread OneLoop常被重點(diǎn)標(biāo)注其的思想就是把所有的操作都放到?個(gè)線程中進(jìn)行?個(gè)線程對(duì)應(yīng)?個(gè)事件處理的循環(huán)。借上面的圖我們來(lái)分析一下具體的流程客戶端發(fā)起 TCP 連接請(qǐng)求多個(gè)客戶端向服務(wù)器監(jiān)聽(tīng)端口發(fā)起連接。主 ReactormainReactor只負(fù)責(zé)監(jiān)聽(tīng) accept 事件mainReactor 內(nèi)部 epoll 監(jiān)聽(tīng) listenfd捕獲新連接事件調(diào)用accept()拿到新客戶端 socket 的 fd完成三次握手得到 TcpConnection。新連接分發(fā)mainReactor 通過(guò)負(fù)載均衡策略muduo 是輪詢將這個(gè)新連接 fd投遞到其中一個(gè)從屬 Reactor (subReactor)。從屬 Reactor 接管連接注冊(cè) IO 事件被選中的 subReactor一個(gè)線程對(duì)應(yīng)一個(gè) EventLoopOneThreadOneLoop把這個(gè)新 fd 注冊(cè)進(jìn)自己內(nèi)部的 epoll后續(xù)這個(gè)連接永久綁定該 subReactor不會(huì)切換線程。自此mainReactor 不再管這條連接只繼續(xù)等待下一個(gè)新客戶端連接。從屬 Reactor 循環(huán)監(jiān)控 IO 事件subReactor 的 epoll 持續(xù)等待事件客戶端發(fā)數(shù)據(jù) → socket可讀事件觸發(fā)內(nèi)核緩沖區(qū)可寫(xiě)入數(shù)據(jù) → socket可寫(xiě)事件觸發(fā)連接斷開(kāi) → 關(guān)閉事件。事件回調(diào)處理subReactor 線程檢測(cè)到可讀事件在當(dāng)前 IO 線程內(nèi)執(zhí)行讀操作從 socket 讀取數(shù)據(jù)直接在這個(gè) IO 線程調(diào)用onMessage業(yè)務(wù)回調(diào)解析數(shù)據(jù)包、執(zhí)行業(yè)務(wù)邏輯業(yè)務(wù)處理完成后如果需要回復(fù)客戶端在當(dāng)前線程執(zhí)行 send 寫(xiě)數(shù)據(jù)。連接關(guān)閉當(dāng)檢測(cè)到連接關(guān)閉事件subReactor 在本線程清理該 TcpConnection 資源從 epoll 移除 fd。二、知識(shí)背景我們想實(shí)現(xiàn)這樣一個(gè)項(xiàng)目主要依靠的是我們之前學(xué)習(xí)的HTTP服務(wù)器和Reactor事件派發(fā)器現(xiàn)在爆發(fā)鬼腦回想一下2.1 HTTP服務(wù)器HTTPHyperTextTransferProtocol超?本傳輸協(xié)議是應(yīng)?層協(xié)議是?種簡(jiǎn)單的請(qǐng)求-響應(yīng)協(xié)議但我們要明白的是它只是服務(wù)于應(yīng)用層的數(shù)據(jù)傳輸方式根本還是在于TCP為基礎(chǔ)搭建的服務(wù)器上。因此可以說(shuō)HTTP服務(wù)器本質(zhì)就是個(gè)TCP服務(wù)器所以就需要socket-bind-listen-accept這幾個(gè)我們熟知的流程在內(nèi)部對(duì)數(shù)據(jù)HTTP協(xié)議封裝、調(diào)用業(yè)務(wù)邏輯當(dāng)然在當(dāng)前項(xiàng)目中的服務(wù)器本?并不存在業(yè)務(wù)。2.2 Reactor模型Reactor 模式是指通過(guò)?個(gè)或多個(gè)輸?同時(shí)傳遞給服務(wù)器進(jìn)?請(qǐng)求處理時(shí)的事件驅(qū)動(dòng)處理模式服務(wù)端程序處理傳?多路請(qǐng)求并將它們同步分派給請(qǐng)求對(duì)應(yīng)的處理線程。又名Dispatcher 模式基礎(chǔ)的Reactor模型的思路與基礎(chǔ)的TcpServer的阻塞式IO不同采用多路復(fù)用的方法這兩者的區(qū)別就是一直等和等多個(gè)比如釣魚(yú)張三只帶了一個(gè)魚(yú)竿釣魚(yú)時(shí)只能等著魚(yú)上鉤而多路復(fù)用則是李四帶了多個(gè)魚(yú)竿等著只要有上鉤就去處理。但這兩種方法本質(zhì)區(qū)別是等的區(qū)別處理事件過(guò)程是相同的。Reactor基本思想是監(jiān)控fd如果fd就緒就告訴內(nèi)核進(jìn)而響應(yīng)到用戶應(yīng)用層處理后重新等待。這里就不講述過(guò)多了詳細(xì)內(nèi)容可以查看有關(guān)的博客我的沒(méi)寫(xiě):(三、選擇的原因那就有人要疑惑了我之前不是實(shí)現(xiàn)了一個(gè)Reactor服務(wù)器嗎那為什么還有跟著你的項(xiàng)目寫(xiě)一個(gè)我寫(xiě)過(guò)的東西。其實(shí)不然我們之前實(shí)現(xiàn)的只是一個(gè)單Reactor 多線程的模式它有著這樣的一種缺陷同一個(gè)連接 fd 上先后觸發(fā)的 IO 事件會(huì)被封裝為獨(dú)立任務(wù)直接投遞至全局線程池線程池不保證任務(wù)完成順序與事件入隊(duì)順序一致若后提交的請(qǐng)求任務(wù)執(zhí)行更快會(huì)優(yōu)先返回響應(yīng)造成同一連接上請(qǐng)求與響應(yīng)順序錯(cuò)亂引發(fā)協(xié)議解析異常。因此我們不可能使用這樣一種具有嚴(yán)重處理缺陷的代碼作為能夠勝任高并發(fā)服務(wù)器開(kāi)發(fā)的基礎(chǔ)實(shí)現(xiàn)。那讓我們認(rèn)識(shí)一下Reactor的種類(lèi)3.1 單 Reactor 單線程單 I/O 多路復(fù)用 業(yè)務(wù)處理這是最樸素的一種實(shí)現(xiàn)整個(gè)服務(wù)器就一個(gè)線程既負(fù)責(zé)監(jiān)聽(tīng)事件又負(fù)責(zé)干活。工作流程通過(guò) I/O 多路復(fù)用模型進(jìn)行客戶端請(qǐng)求監(jiān)控。觸發(fā)事件后進(jìn)行事件處理a. 如果是新建連接請(qǐng)求則獲取新建連接并添加至多路復(fù)用模型進(jìn)行事件監(jiān)控。b. 如果是數(shù)據(jù)通信請(qǐng)求則進(jìn)行對(duì)應(yīng)的數(shù)據(jù)處理接收數(shù)據(jù)、處理數(shù)據(jù)、發(fā)送響應(yīng)。優(yōu)點(diǎn)所有操作均在同一線程中完成思想流程較為簡(jiǎn)單不涉及進(jìn)程/線程間通信及資源爭(zhēng)搶問(wèn)題。缺點(diǎn)無(wú)法有效利用 CPU 多核資源很容易達(dá)到性能瓶頸。適用場(chǎng)景適用于客戶端數(shù)量較少、且處理速度較為快速的場(chǎng)景。處理較慢或活躍連接較多時(shí)會(huì)導(dǎo)致串行處理后處理的連接長(zhǎng)時(shí)間無(wú)法得到響應(yīng)??梢钥吹剿暮?jiǎn)單是靠犧牲并發(fā)能力換來(lái)的——一旦某個(gè)業(yè)務(wù)處理耗時(shí)變長(zhǎng)整個(gè)服務(wù)器都會(huì)被它拖住因?yàn)樗緵](méi)有第二個(gè)線程能接手。3.2 單 Reactor 多線程單 I/O 多路復(fù)用 線程池業(yè)務(wù)處理既然單線程扛不住最直接的想法就是把監(jiān)聽(tīng)和干活拆開(kāi)干活交給線程池。這就是單 Reactor 多線程模式。工作流程Reactor 線程通過(guò) I/O 多路復(fù)用模型進(jìn)行客戶端請(qǐng)求監(jiān)控。觸發(fā)事件后進(jìn)行事件處理a. 如果是新建連接請(qǐng)求則獲取新建連接并添加至多路復(fù)用模型進(jìn)行事件監(jiān)控。b. 如果是數(shù)據(jù)通信請(qǐng)求則接收數(shù)據(jù)后分發(fā)給 Worker 線程池進(jìn)行業(yè)務(wù)處理。c. 工作線程處理完畢后將響應(yīng)交給 Reactor 線程進(jìn)行數(shù)據(jù)響應(yīng)。優(yōu)點(diǎn)充分利用 CPU 多核資源。缺點(diǎn)多線程間的數(shù)據(jù)共享訪問(wèn)控制較為復(fù)雜且單個(gè) Reactor 承擔(dān)所有事件的監(jiān)聽(tīng)和響應(yīng)在單線程中運(yùn)行高并發(fā)場(chǎng)景下容易成為性能瓶頸。注意第 2.c 步這個(gè)細(xì)節(jié)——響應(yīng)最終還是要回到 Reactor 線程去發(fā)送。這既帶來(lái)了各線程都要和 Reactor 打交道的同步成本也正是我們?cè)谏弦还?jié)踩過(guò)的坑所在業(yè)務(wù)任務(wù)一旦并發(fā)投遞進(jìn)線程池同一連接上請(qǐng)求的處理與響應(yīng)的先后順序就沒(méi)人保證了順序錯(cuò)亂、協(xié)議解析異常隨之而來(lái)。3.3 多 Reactor 多線程多 I/O 多路復(fù)用 線程池業(yè)務(wù)處理單 Reactor 的瓶頸在于所有事件的監(jiān)聽(tīng)和響應(yīng)都?jí)涸谕粋€(gè)線程上。那思路就很清楚了把 Reactor 本身也拆開(kāi)讓它多干活。這就是多 Reactor 多線程模式也就是常說(shuō)的主從 ReactorMain-Sub Reactor。工作流程在主 Reactor中處理新連接請(qǐng)求事件有新連接到來(lái)則分發(fā)到子 Reactor中監(jiān)控。在子 Reactor中進(jìn)行客戶端通信監(jiān)控有事件觸發(fā)則接收數(shù)據(jù)分發(fā)給Worker 線程池。Worker 線程池分配獨(dú)立的線程進(jìn)行具體的業(yè)務(wù)處理。a. 工作線程處理完畢后將響應(yīng)交給子 Reactor線程進(jìn)行數(shù)據(jù)響應(yīng)。優(yōu)點(diǎn)充分利用 CPU 多核資源主從 Reactor 各司其職。缺點(diǎn)實(shí)現(xiàn)復(fù)雜度最高——連接如何分發(fā)、線程間如何通信、生命周期如何管理都需要自己設(shè)計(jì)好代碼量和對(duì)細(xì)節(jié)的把握要求也遠(yuǎn)高于前兩種。與前兩種模式相比它最大的變化是主 Reactor 只負(fù)責(zé)接客子 Reactor 負(fù)責(zé)服務(wù)。新連接被主 Reactor 接進(jìn)來(lái)后會(huì)被分配給某一個(gè)子 Reactor此后這條連接上的所有 I/O 事件都由這個(gè)子 Reactor 處理?;氐介_(kāi)頭那個(gè)問(wèn)題為什么我們要在已經(jīng)有一個(gè)單 Reactor 多線程的服務(wù)器之后再寫(xiě)一個(gè)主從 Reactor答案就在 3.3 的分工結(jié)構(gòu)里——同一個(gè)連接 fd 上的事件從此由同一個(gè)子 Reactor 線程順序處理。既不會(huì)出現(xiàn)后提交的任務(wù)先返回的亂序問(wèn)題又因?yàn)樽?Reactor 可以有多個(gè)把 CPU 多核真正用了起來(lái)同時(shí)主 Reactor 只做分發(fā)、不做業(yè)務(wù)保證它不會(huì)被慢業(yè)務(wù)拖住能持續(xù)穩(wěn)定地接收新連接。所以主從 Reactor 不是更花哨的寫(xiě)法而是為了解決單 Reactor 線程池在連接級(jí)順序性和高并發(fā)擴(kuò)展性上的雙重缺陷——這也正是我們選擇它的原因。四、項(xiàng)目解析基于以上的理解我們要實(shí)現(xiàn)的是?個(gè)帶有協(xié)議?持的Reactor模型?性能服務(wù)器因此將整個(gè)項(xiàng)?的實(shí)現(xiàn)劃分為兩個(gè)?的模塊SERVER模塊實(shí)現(xiàn)Reactor模型的TCP服務(wù)器協(xié)議模塊對(duì)當(dāng)前的Reactor模型服務(wù)器提供應(yīng)?層協(xié)議?持4.1 SERVER模塊SERVER模塊就是對(duì)所有的連接以及線程進(jìn)?管理讓它們各司其職在合適的時(shí)候做合適的事最終完成?性能服務(wù)器組件的實(shí)現(xiàn)。并且這些管理可以詳細(xì)分為監(jiān)聽(tīng)連接管理對(duì)監(jiān)聽(tīng)連接進(jìn)?管理。通信連接管理對(duì)通信連接進(jìn)?管理。超時(shí)連接管理對(duì)超時(shí)連接進(jìn)?管理。而這個(gè)大模塊就可以分成多個(gè)小模塊組裝實(shí)現(xiàn)Buff模塊緩沖區(qū)模塊?于實(shí)現(xiàn)通信中??態(tài)的接收緩沖區(qū)和發(fā)送緩沖區(qū)功能Socket模塊負(fù)責(zé)服務(wù)器的系列操作Channel模塊對(duì)每個(gè)連接進(jìn)行監(jiān)控的模塊實(shí)現(xiàn)對(duì)描述符可讀可寫(xiě)錯(cuò)誤…事件的管理操作以及Poller模塊對(duì)描述符進(jìn)?IO事件監(jiān)控就緒后根據(jù)不同的事件回調(diào)不同的處理函數(shù)功能Poller模塊是對(duì)epoll模型的封裝實(shí)現(xiàn)對(duì)事件的添加、修改和刪除等功能Connection模塊是對(duì)Buff、Socket、Channel模塊的封裝使得可以管理所有的連接Acceptor模塊Acceptor模塊是對(duì)Socket模塊Channel模塊的?個(gè)整體封裝實(shí)現(xiàn)了對(duì)?個(gè)監(jiān)聽(tīng)套接字的整體的管理TimerQueue模塊是實(shí)現(xiàn)固定時(shí)間定時(shí)任務(wù)的模塊可以理解就是要給定時(shí)任務(wù)管理器向定時(shí)任務(wù)管理器中添加?個(gè)任務(wù)任務(wù)將在固定時(shí)間后被執(zhí)?同時(shí)也可以通過(guò)刷新定時(shí)任務(wù)來(lái)延遲任務(wù)的執(zhí)?EventLoop模塊可以理解就是我們上邊所說(shuō)的Reactor模塊它是對(duì)Poller模塊TimerQueue模塊Socket模塊的?個(gè)整體封裝進(jìn)?所有描述符的事件監(jiān)控TcpServer模塊這個(gè)模塊是?個(gè)整體Tcp服務(wù)器模塊的封裝內(nèi)部封裝了Acceptor模塊EventLoopThreadPool模塊4.2 HTTP協(xié)議模塊HTTP協(xié)議模塊?于對(duì)?并發(fā)服務(wù)器模塊進(jìn)?協(xié)議?持基于提供的協(xié)議?持能夠更?便的完成指定協(xié)議服務(wù)器的搭建。?HTTP協(xié)議?持模塊的實(shí)現(xiàn)可以細(xì)分為以下?個(gè)模塊。Util模塊這個(gè)模塊是?個(gè)?具模塊主要提供HTTP協(xié)議模塊所?到的?些?具函數(shù)?如url編解碼?件讀寫(xiě)…等。HttpRequest模塊這個(gè)模塊是HTTP請(qǐng)求數(shù)據(jù)模塊?于保存HTTP請(qǐng)求數(shù)據(jù)被解析后的各項(xiàng)請(qǐng)求元素信息。HttpResponse模塊這個(gè)模塊是HTTP響應(yīng)數(shù)據(jù)模塊?于業(yè)務(wù)處理后設(shè)置并保存HTTP響應(yīng)數(shù)據(jù)的的各項(xiàng)元素信息最終會(huì)被按照HTTP協(xié)議響應(yīng)格式組織成為響應(yīng)信息發(fā)送給客?端。HttpContext模塊這個(gè)模塊是?個(gè)HTTP請(qǐng)求接收的上下?模塊主要是為了防?在?次接收的數(shù)據(jù)中不是?個(gè)完整的HttpServer模塊這個(gè)模塊是最終給組件使?者提供的HTTP服務(wù)器模塊了?于以簡(jiǎn)單的接?實(shí)現(xiàn)HTT服務(wù)器的搭建。在之后具體的模塊開(kāi)發(fā)時(shí)會(huì)對(duì)每個(gè)模塊的具體實(shí)現(xiàn)做出規(guī)劃并解釋功能與意義以便我們更好的梳理項(xiàng)目結(jié)構(gòu)。總結(jié)我們的項(xiàng)目前提就到這里了下期我們將學(xué)習(xí)一些項(xiàng)目中需要實(shí)現(xiàn)的小功能模塊正式開(kāi)啟我們的項(xiàng)目實(shí)現(xiàn)感謝您的收看讓我們下期再見(jiàn)。