為了應對高并發(fā)的服務器端開發(fā),微軟在2009年提出了一種更優(yōu)雅地實現(xiàn)異步編程的方式Reactive Programming即反應式編程。隨后其他技術緊隨其后,比如ES6通過引入類似的異步編程方式等。
在高性能的I/O設計中,有兩個比較著名的模式Reactor和Proactor模式,其中Reactor模式用于同步I/O,Proactor用于異步I/O操作。
Reactor模式稱之為響應器模式,通常用于NIO非阻塞IO的網絡通信框架中。
在這之前,需要弄明白幾個概念:
- 什么是阻塞和非阻塞?
阻塞和非阻塞是針對于進程在訪問數(shù)據時,根據IO操作的就緒狀態(tài)而采取的不同方式,簡單來說是一種讀取或寫入操作函數(shù)的實現(xiàn)方式,阻塞方式下讀取或寫入函數(shù)將一直等待。非阻塞方式下,讀取和寫入函數(shù)會立即返回一個狀態(tài)值。
- 什么是同步和異步?
同步和異步是針對應用程序和內核的交互而言的,同步是指用戶進程觸發(fā)IO操作并等待或輪詢的查看IO操作是否就緒,異步是指用戶進程觸發(fā)IO操作以后便開始做自己的事情,當IO操作完成時會得到通知,換句話說異步的特點就是通知。
- 什么是IO模型?
一般而言,IO模型可以分為四種:同步阻塞、同步非阻塞、異步阻塞、異步非阻塞
同步阻塞IO是指用戶進程在發(fā)起一個IO操作后必須等待IO操作完成,只有當真正完成了IO操作后用戶進程才能運行。
同步非阻塞IO是指用戶進程發(fā)起一個IO操作后立即返回,程序也就可以做其他事情。但是用戶進程需要不時的詢問IO操作是否就緒,這就要求用戶進程不停的去詢問,從而引入不必要的CPU資源浪費。
異步阻塞IO是指應用發(fā)起一個IO操作后不必等待內核IO操作的完成,內核完成IO操作后會通知應用程序。這其實是同步和異步最關鍵的區(qū)別,同步必須等待或主動詢問IO操作是否完成,那么為什么說是阻塞呢?因為此時是通過
select系統(tǒng)調用來完成的,而select函數(shù)本身的實現(xiàn)方式是阻塞的,采用select函數(shù)的好處在于可以同時監(jiān)聽多個文件句柄,從而提高系統(tǒng)的并發(fā)性。異步非阻塞IO是指用戶進程只需要發(fā)起一個IO操作后立即返回,等IO操作真正完成后,應用系統(tǒng)會得到IO操作完成的通知,此時用戶進程只需要對數(shù)據進行處理即可,不需要進行實際的IO讀寫操作,因為真正的IO讀寫操作已經由內核完成。
NIO非阻塞IO處理流程

-
Acceptor注冊Selector并監(jiān)聽accept事件 - 當客戶端連接后會觸發(fā)
accept事件 - 服務器構建對應的
Channel并在其上注冊Selector,用于監(jiān)聽讀寫事件。 - 當發(fā)生讀寫事件后進行相應的讀寫處理
NIO非阻塞IO的優(yōu)點在于性能瓶頸高,缺點在于模型復雜、編碼復雜、需要處理半包問題。簡單來說非阻塞IO不需要一個連接建立一個線程,它可以在一個線程中處理所有的連接。但是由于是非阻塞的,所以應用無法知道什么時候消息讀完了,也就會存在半包的問題。
什么是半包問題呢?
TCP/IP在發(fā)送消息時可能會拆包,拆包會導致接收端無法得知什么時候接收到的數(shù)據是一個完整的數(shù)據。在BIO阻塞性IO模型中,當讀取步到數(shù)據后會阻塞,而在NIO非阻塞IO中則不會,所以需要自行進行處理。比如以換行符作為判斷依據,或者是定長消息發(fā)送,或者是自定義協(xié)議等。
什么是Reactor模式?
Reactor模式是處理并發(fā)I/O常見的一種模式,用于同步I/O,其中心思想是將所有要處理的I/O事件注冊到一個中心I/O多路復用器上,同時主線程阻塞在多路復用器上,一旦有I/O事件到來或是準備就緒,多路復用器將返回并將相應I/O事件分發(fā)到對應的處理器中。
Reactor是一種事件驅動機制,和普通函數(shù)調用不同的是應用程序不是主動的調用某個API來完成處理,恰恰相反的是Reactor逆置了事件處理流程,應用程序需提供相應的接口并注冊到Reactor上,如果有相應的事件發(fā)生,Reactor將主動調用應用程序注冊的接口(回調函數(shù))。
The reactor design pattern is an event handling pattern for handling service requests delivered concurrently by one or more inputs. The service handler then demultiplexes the incoming requests and dispatches them synchronously to associated request handlers.

Reactor模式稱為反應器模式或應答者模式,是基于事件驅動的設計模式,擁有一個或多個并發(fā)輸入源,有一個服務處理器和多個請求處理器,服務處理器會同步的將輸入的請求事件以多路復用的方式分發(fā)給相應的請求處理器。
Reactor設計模式是一種為處理并發(fā)服務請求,并將請求提交到一個或多個服務處理程序的事件設計模式。當客戶端請求抵達后,服務處理程序使用多路分配策略,由一個非阻塞的線程來接收所有請求,然后將請求派發(fā)到相關的工作線程并進行處理的過程。
在事件驅動的應用中,將一個或多個客戶端的請求分離和調度給應用程序,同步有序地接收并處理多個服務請求。對于高并發(fā)系統(tǒng)經常會使用到Reactor模式,用來替代常用的多線程處理方式以節(jié)省系統(tǒng)資源并提高系統(tǒng)的吞吐量。
基礎
什么是C/S架構?
-
C表示Client客戶端 -
S表示Server服務器,服務器管理著某種資源Resource,通過操作這種資源為客戶端提供服務。

C/S架構的工作流程
- 客戶端進程向服務器進程發(fā)送請求
- 服務器進程接收并處理請求
- 服務器進程向客戶端進程發(fā)送響應
- 客戶端進程處理響應
什么是套接字Socket?
- Socket原意為插口,所表達的意思是插口與插槽之間的關系。
- Socket是對TCP/IP編程的抽象,簡單來說,是
send socket插入到receive socket中以建立連接進行通信。

C/S架構中Socket之間是如何建立連接并通信的呢?
- 服務端Socket綁定
bind到指定的端口上后監(jiān)聽listen客戶端的插入 - 客戶端Socket連接到
connect到服務端 - 當服務端
accept到客戶端連接后 - 客戶端與服務端之間收發(fā)信息開發(fā)通信
- 通信完成后客戶端與服務器關閉
close掉Socket
演化
當前分布式計算Web服務盛行天下,網絡服務的底層都離不開對Socket的操作,而它們都具有一個共同的結構。
不同于傳統(tǒng)IO的串行調度方式,NIO非阻塞IO操作會將整個服務請求劃分為五個階段。
在網絡服務和分布式中對于網絡中請求的處理,處理流程大致可劃分為五個階段。
-
read接收請求讀取數(shù)據 -
decode數(shù)據解碼 -
compute業(yè)務邏輯處理(計算處理) -
encode編碼回復 -
send發(fā)送回復
在這五個階段中,以read和send階段IO操作最為頻繁。

在處理網絡請求時,通常具有兩種體系結構。
- 基于線程
thread-based architecture
基于線程的體系結構會使用多線程來處理客戶端的請求,每當接收一個請求便開啟一個獨立的線程來處理。這種方式雖然簡單直觀,但僅適用于并發(fā)訪問不大的場景。因為線程是需要占用一定的內存資源,而且操作系統(tǒng)在線程之間的切換也需要一定的開銷。當線程過多時顯然會降低網絡服務器的性能。另外,當線程在處理IO操作時,在等待輸出的這段時間內線程是處于空閑狀態(tài),造成CPU資源浪費。
- 事件驅動
event-driver architecture
事件驅動體系結構是目前廣泛使用的一種方式,這種方式定義了一系列的事件處理程序來響應事件的發(fā)生,而且將服務端接收連接和事件處理分離,事件本身只是一種狀態(tài)的改變。在事件驅動的應用中,會將一個或多個客戶端的服務請求分離demultiplex和調度dispatch給應用程序。
Reactor設計模式是event-driven architecture的一種實現(xiàn)方式,用于處理多個客戶端并發(fā)的向服務器請求服務的場景。每種服務在服務器上可能由多個方法組成。Reactor會解耦并發(fā)請求的服務并分發(fā)給對應的時間處理器來處理。
從結構上看,Reactor類似于生產消費模式,也就是一個或多個生產者會將事件放入一個隊列中,一個或多個消費者主動從隊列中poll拉取事件進行處理。Reactor并沒有使用隊列來做緩沖,每當一個事件輸入到服務處理程序之后,服務處理程序會主動根據不同的事件類型將其分發(fā)給對應的請求處理程序進行處理。
Reactor模式和生產者和消費者之間最大的區(qū)別在于
生產者消費者模式是基于隊列
queue的實現(xiàn),能夠解決生產端和消費端處理速度不同步的問題,隊列可以采用先有的MQ產品來實現(xiàn)。Reactor模式是基于事件驅動模型,當接收到請求后會將請求封裝成事件,并將事件分發(fā)給相應處理事件的
handler,handler處理完成后將時間狀態(tài)修改為下一個狀態(tài),再由Reactor將事件分發(fā)給能夠處理下一個狀態(tài)的handle進行處理。
Reactor模式與Observer觀察者模式在某些方面極為相似,當一個主體發(fā)生改變時,所有依屬體都將得到通知。不過觀察者模式與單個事件源關聯(lián),而反應器模式則于多個事件源關聯(lián)。
Reactor模式的優(yōu)點很明顯:解耦、提升復用性、模塊化、可移植性、事件驅動、細粒度的開發(fā)控制等。Reactor模式的缺點也很明顯:模型復雜,涉及到內部回調、多線程處理、不容易調試、需要操作系統(tǒng)底層支持,因此導致不同操作系統(tǒng)可能會產生不一樣的結果。總來而言,如果并發(fā)要求不是很高,可使用傳統(tǒng)的阻塞線程池足夠了。如果使用場景是產生瞬間大并發(fā)可使用Reactor模式來實現(xiàn)。
傳統(tǒng)服務模型
最原始的網絡編程思路是服務器使用一個while循環(huán)并不斷監(jiān)聽端口是否有新的socket套接字連接,如果有就會去調用一個處理函數(shù)。
while(true)
{
socket = accept();
handle(socket);
}
這種方式最大的問題是無法并發(fā)且效率太低,如果當前請求沒有處理完畢后續(xù)請求只能被阻塞,因此服務器的吞吐量太低。
導致服務器阻塞的原因是什么呢?
- 服務器
socket的accept方法將阻塞等待客戶端連接,直到客戶端連接成功。 - 線程從
socket inputstream套接字輸入流讀取數(shù)據并進入阻塞狀態(tài),直到全部數(shù)據讀取完畢 - 線程向
socket outputstream套接字輸出流寫入數(shù)據并進入阻塞狀態(tài),直到全部數(shù)據寫入完畢。
由于IO在阻塞時會處于等待狀態(tài),因此在用戶負載增加時,性能下降的非??臁?/p>
多線程
改進的方式是使用多線程,也就是經典的connection per thread,每一個連接擁有一個線程處理。
while(true)
{
socket = accept();
new thread(socket);
}
對于傳統(tǒng)的服務設計,每個抵達的請求系統(tǒng)會分配一個線程去處理,Tomcat服務器早期版本是這樣實現(xiàn)的。
當系統(tǒng)請求量瞬間暴增時(高并發(fā)情況下),會直接把系統(tǒng)拖垮,因為系統(tǒng)能夠創(chuàng)建線程的數(shù)量是有限的。
多線程并發(fā)模式采用一個連接一個線程的方式,優(yōu)點是確實一定程度上提高了服務器的吞吐量,因為之前的請求在read讀阻塞后不會影響到后續(xù)的請求,由于它們在不同的線程中,而且一個線程只能對應一個套接字socket,每一個套接字socket都是阻塞的,所以一個線程中只能處理一個套接字。就算accept多個socket,如果前一個socket被阻塞其后的socket是無法被執(zhí)行到的。

多線程并發(fā)模式的缺點在于資源要求太高,系統(tǒng)中創(chuàng)建線程是需要消耗系統(tǒng)資源的,如果連接數(shù)過高系統(tǒng)將無法承受。另外,線程反復被創(chuàng)建和銷毀也是需要代價的。
線程池
雖然利用線程池可以緩解線程創(chuàng)建和銷毀的代價,不過還是存在一些問題,線程的粒度太大。每一個線程會將一次交互操作全部處理完成,包括讀取和返回甚至是連接。表面上似乎連接不在線程里面,但是如果線程不夠,新連接將無法得到處理。所以線程的任務可以簡化為做三件事:連接、讀取、寫入。
顯然傳統(tǒng)一對一的線程處理無法滿足需求的變化,對此考慮使用線程池使得線程可以被復用,大大降低創(chuàng)建線程和銷毀線程的時間。然而,線程池并不能很好滿足高并發(fā)線程的需求。當海量請求抵達時線程池中的工作線程達到飽和狀態(tài),此時可能就導致請求被拋棄,無法完成客戶端的請求。對此,考慮到將一次完整的請求切分為幾個小的任務,每個小任務都是非阻塞的,對于讀寫操作使用NIO非阻塞IO對其進行讀寫,不同的任務將被分配到與之關聯(lián)的處理程序上進行處理,每個處理器通過異步回調機制來實現(xiàn)。這樣可以大大提高系統(tǒng)吞吐量,減少響應時間。
由于線程同步的粒度太大限制了吞吐量,所以應該將一次連接操作拆分為更細的粒度或過程,這些更細的粒度則是更小的線程。這樣做之后,整個線程池中線程的數(shù)量將會翻倍增加,但線程更加簡單且任務更為單一。這也是Reactor出現(xiàn)的原因。
Reactor
在Reactor中這些被拆分的小線程或子過程對應的處理程序,每一種處理程序會去處理一種事件。Reactor中存在一個全局管理者Selector,開發(fā)者需要將Channel注冊到感興趣的事件上,Selector會不斷在Channel上檢測是否有該類型的事件發(fā)生,如果沒有主線程會被阻塞,否則會調用相應的事件處理函數(shù)來處理。
由于典型的事件包括連接、讀取、寫入,因此需要為這些事件分別提供對應的處理程序,每個處理程序可以采用線程的方式實現(xiàn)。一旦連接來了,而且顯示被讀取線程或處理程序處理了,則會再執(zhí)行寫入。那么之前的讀取就可以被后面的請求復用,因此吞吐量就提高了。
傳統(tǒng)的thread per connection中線程在真正處理請求之間是需要從socket中讀取網絡請求,由于讀取完成之前線程本身是被阻塞的不能做任何事情,這就導致線程資源被占用,而線程資源本身很珍貴的,尤其是在處理高并發(fā)請求時。Rector模式指出在等待IO時,線程可以先退出,這樣就會因為有線程等待IO而占用資源。但是這樣原先的執(zhí)行流程就沒法還原了。因此可以利用事件驅動的方式,要求線程在退出之前向event loop事件循環(huán)中注冊回調函數(shù),這樣IO完成時event loop事件循環(huán)就可以調用回調函數(shù)完成剩下的操作。所以Reactor模式通過減少服務器的資源消耗提供并發(fā)能力。

細分
Reactor從線程池和Reactor的選擇上可細分為:Reactor單線程模型、Reactor多線程模型,Reactor主從模型
單線程Reactor模型
單線程的Reactor模式對于客戶端的所有請求使用一個專門的線程去處理,這個線程無限循環(huán)地監(jiān)聽是否有客戶端的請求抵達,一旦收到客戶端的請求,就將其分發(fā)給響應處理程序進行處理。

采用基于事件驅動的設計,當有事件觸發(fā)時才會調用處理器進行數(shù)據處理。使用Reactor模式可以對線程的數(shù)量進行控制,可以使用一個線程去處理大量的事件。
-Reactor 負責響應IO事件,當檢測到一個新的事件會將其發(fā)送給相應的處理程序去處理。
-
Handler負責處理非阻塞的行為,標識系統(tǒng)管理的資源,同時將處理程序與事件綁定。

Reactor是單個線程,需要處理accept連接,同時發(fā)送請求到處理器中。由于只是單個線程,所以處理器中的業(yè)務需要能夠快速處理完畢。

單線程的Reactor與NIO流程類似,只是將消息相關處理獨立到Handler中。雖然NIO中一個線程可以支持所有的IO處理,但瓶頸也是顯而易見的。如果某個客戶端多次進行請求時在Handler中的處理速度較慢,那么后續(xù)的客戶端請求都會被積壓,導致響應變慢。所以需要引入Reactor多線程模型。
單線程的Reactor的特點是只有一個Reactor線程,也就是說只有一個Selector事件通知器,因此字節(jié)的讀取I/O和后續(xù)的業(yè)務處理process()均由Reactor線程來做,很顯然業(yè)務的處理影響后續(xù)事件的分發(fā),所以引出多線程版本進行優(yōu)化。
從性能角度來看,單線程的Reactor沒有過多的提升空間,因為IO和CPU的速度嚴重不匹配。
單線程的Reactor模式并沒有解決IO和CPU處理速度不匹配問題,所以多線程的Reactor模式引入了線程池的概念,將耗時的IO操作交由線程池處理,處理完畢后再同步到selectionkey中。
多線程Reactor模型
考慮到工作線程的復用,可以將工作線程設計線程池。將處理器的執(zhí)行放入線程池,并使用多線程處理業(yè)務邏輯,Reactor仍然是單個線程。

Reactor讀線程模型是將Handler中的IO操作和非IO操作分開,操作IO的線程稱為IO線程,非IO操作的線程稱為工作線程??蛻舳说恼埱髸恢苯觼G到線程池中,因此不會發(fā)生堵塞。

多線程的Reactor的特點是一個Reactor線程和多個處理線程,將業(yè)務處理即process交給線程池進行了分離,Reactor線程只關注事件分發(fā)和字節(jié)的發(fā)送和讀取。需要注意的是,實際的發(fā)送和讀取還是由Reactor來處理。當在高并發(fā)環(huán)境下,有可能會出現(xiàn)連接來不及接收。
當用戶進一步增加時Reactor也會出現(xiàn)瓶頸,因為Reactor既要處理IO操作請求也要響應連接請求。為了分擔Reactor的負擔,可以引入主從Reactor模型。
主從Reactor模型

對于多個CPU的機器,為了充分利用系統(tǒng)資源會將Reactor拆分為兩部分。
- Main Reactor 負責監(jiān)聽連接,將
accept連接交給Sub Reactor處理,主Reactor用于響應連接請求。 - Sub Reactor 處理
accept連接,從Reactor用于處理IO操作請求。

主從Reactor的特點是使用 一個Selector池,通常有一個主Reactor用于處理接收連接事件,多個從Reactor處理實際的IO。整體來看,分工合作,分而治之,非常高效。
為什么需要單獨拆分一個Reactor來處理監(jiān)聽呢?
因為像TCP這樣需要經過3次握手才能建立連接,這個建立的過程也是需要消耗時間和資源的,單獨拆分一個Reactor來處理,可以提高性能。
優(yōu)缺點
Reactor模式的核心是解決多請求問題,如果有特別多的請求同時發(fā)生,不會因為線程池被短時間占滿而拒絕服務。一般實現(xiàn)多請求的模塊,會采用線程池的實現(xiàn)方案,這種方案對于并發(fā)量不是特別大的場景是足夠用的,比如單機TPS 1000以下都是夠用的。
線程池方案的最大缺點是:如果瞬間有大并發(fā),則會一下子耗滿線程,整個服務將會陷入阻塞中,后續(xù)請求無法介入?;?code>Reactor模式實現(xiàn)的方案,會有一個Dispatcher先接收事件event,然后快速分發(fā)給相應的耗時eventHandler處理器去處理,這樣就不會阻塞請求的接收。
Reactor模式的優(yōu)點是什么呢?
- 響應快,不為單個同步時間所阻塞,雖然Reactor自身依然是同步的。
- 編程相對簡單,可以最大程度的避免復雜的多線程以及同步問題和多線程以及多進程的切換開銷。
- 可擴展性,可以方便的通過增加Reactor實例個數(shù)來充分利用CPU資源。
- 可復用性, Reactor框架本身與具體事件處理邏輯無關,具有很高的復用性。
Reactor模式的缺點是什么呢?
- 相比傳統(tǒng)的模型,Reactor增加了一定的復雜性,因而具有一定的門檻,并且不易于調試。
- Reactor模式需要底層的
Synchronous Event Demultiplexer支持,比如Java中的Selector支持,操作系統(tǒng)的select系統(tǒng)調用支持。 - Reactor模式在IO讀寫數(shù)據時會在同一線程中實現(xiàn),即使使用多個Reactor機制的情況下,那些共享一個Reactor的Channel如果出現(xiàn)一個長時間的數(shù)據讀寫,會影響這個Reactor中其他Channel的相應時間。例如在大文件傳輸時,IO操作會影響其他客戶端的時間,因而對于這種操作,使用傳統(tǒng)的
Thread-Per-Connection或許是一個更好的選擇,或者采用Proactor模式。
結構
Reactor中的核心組件有哪些呢?
- Reactor
IO事件的派發(fā)者,相當于有分發(fā)功能的Selector。 - Acceptor
接收客戶端連接并建立對應客戶端的Handler,向Reactor注冊此Handler。相當于NIO中建立連接的那個判斷分支。 - Handler
和一個客戶端通訊的實體,一般在基礎的Handler上會有更進一步的層次劃分,用來抽象諸如decode、process、encode這些過程。相當于消息讀寫處理等操作類。
在Reactor模式中有五個關鍵的參與者:描述符handle、同步事件分離器demultiplexer、事件處理器接口event handler、具體的事件處理器、Reactor管理器
Reactor的結構

Reactor模式要求主線程(I/O處理單元)只負責監(jiān)聽文件描述符上是否有事件發(fā)生,如果有的話立即將該事件通知給工作線程(邏輯單元)。除此之外,主線程不做任何其它實質性的工作。讀寫數(shù)據、接收新連接、處理客戶端請求均在工作線程中完成。
- Handle 文件描述符
Handle在Linux中一般稱為文件描述符,在Windows中稱為句柄,兩者含義一樣。Handle是事件的發(fā)源地。比如網絡socket、磁盤文件等。發(fā)生在Handle上的事件可以有connection、ready for read、ready for write等。
Handle是操作系統(tǒng)的句柄,是對資源在操作系統(tǒng)上的一種抽象,它可以是打開的文件、一個Socket連接、Timer定時器等。由于Rector模式一般使用在網絡編程中,因而這里一般指的是Socket Handle,也就是一個網絡連接(connection/channel)。這個channel注冊到同步事件分離器中,以監(jiān)聽Handle中發(fā)生的事件,對ServerSocketChannel可以是CONNECT事件,對SocketChannel可以是read、write、close事件等。
- Synchronous Event Demultiplexer 同步(多路)事件分離器
同步事件分離器本質上是系統(tǒng)調用,比如Linux中的select、poll、epoll等。比如select()方法會一致阻塞直到文件描述符handle上有事件發(fā)生時才會返回。
無限循環(huán)等待新請求的到來,一旦發(fā)現(xiàn)有新的事件到來就會通知初始事件分發(fā)器去調取特定的時間處理器。
- Event Handler 事件處理器
事件處理器,定義一些回調方法或稱為鉤子函數(shù),當handle文件描述符上有事件發(fā)生時,回調方法便會執(zhí)行。供初始事件分發(fā)器回調使用。
- Concrete Event Handler 具體的事件處理器
具體的事件處理器,實現(xiàn)了Event Handler,在回調方法中實現(xiàn)具體的業(yè)務邏輯。
- Initiation Dispatcher 初始事件分發(fā)器
初始事件分發(fā)器,提供了注冊、刪除、轉發(fā)Event Handler的方法。當Synchronous Event Demultiplexer檢測到handler上有事件發(fā)生時,便會通知initiation dispatcher調用特定的event handler的回調方法。
初始事件分發(fā)器用于管理Event Handler,定義注冊、移除EventHandler等。它還作為Rector模式的入口調用Synchronous Event Demultiplexer同步多路事件分離器的select方法以阻塞等待事件返回,當阻塞等待返回時,根據事件發(fā)生的Handle將其分發(fā)給對應的Event Handle事件處理器進行處理,也就是回調EventHandler中的handle_event方法。

事件多路分解器
現(xiàn)代操作系統(tǒng)大多提供了一種本機機制,該機制通過一種有效的方式處理并發(fā)和非阻塞資源,這種機制稱為同步事件多路分解器或事件通知接口。
Reactor啟動流程
- 創(chuàng)建Reactor
- 注冊事件處理器
- 調用事件多路分發(fā)器進入無限事件循環(huán)
- 當操作系統(tǒng)通知某描述符狀態(tài)就緒時,事件分發(fā)器找出并調用此描述注冊的事件處理器。
使用同步IO模型(以epoll_wait為例)實現(xiàn)的Reactor模式的工作流程
- 主線程向
epoll內核事件表中注冊socket上的讀就緒事件 - 主線程調用
epoll_wait等待socket上有數(shù)據可讀 - 當
socket上有數(shù)據可讀時,epoll_wait通知主線程,主線程將socket可讀事件放入請求隊列。 - 休眠在請求隊列上的某個工作線程被喚醒,從
socket中讀取數(shù)據并處理客戶端請求,然后向epoll內核事件表中注冊該socket上的寫就緒事件。 - 主線程調用
epoll_wait等待socket可寫 - 當
socket可寫時epoll_wait通知主線程,主線程將socket可寫事件放入請求隊列。 - 休眠在請求隊列上的某個工作線程被喚醒,向
socket上寫入服務器處理客戶請求的結果。

案例
例如:使用Reactor實現(xiàn)的日志服務器
日志服務器中的Reactor模式實現(xiàn)分為兩部分
- 客戶端連接到日志服務器

- 客戶端向日志服務器寫入日志

例如:需要建立一個提供分布式日志服務的事件驅動服務器,客戶端向服務器發(fā)送請求記錄自己的狀態(tài)信息,信息包括錯誤通知、調試信息、表現(xiàn)診斷等。日志服務器對于收到的信息進行分類和分發(fā),具體包括顯示屏顯示、打印機打印、數(shù)據庫存儲等。

為了保證數(shù)據可靠性,客戶端和服務器之間的通信協(xié)議通常選用TCP等面向連接的協(xié)議,通過IP和端口的四元組來確認客戶端和服務器。日志服務器被多個客戶端同時使用,為此日志服務器需要保證多用戶連接請求和日志記錄的并發(fā)性。
為了保證并發(fā)性,可采用多線程的方式去實現(xiàn)該服務器,即每個線程專門針對一個連接。然而使用多線程的方式實現(xiàn)服務器存在著以下問題:
- 效率
多線程導致的上下文切換、同步、數(shù)據移動等可能帶來效率的下降。
- 編程簡單性
多線程需要考慮復雜的并發(fā)設計,包括線程安全等諸多因素。
- 可移植性
多線程在不同的操作系統(tǒng)下是不同的,因此會影響到可移植性。
由于以上問題,多線程設計往往既不是最高效也不是最易于實現(xiàn)的方案,因此需要其他方案來實現(xiàn)可以并行請求的服務器。
未完待續(xù)...