Java NIO 教程(十) 非阻塞式服務(wù)器

參考:http://ifeve.com/non-blocking-server/
原文地址

目錄

即使你知道Java NIO非阻塞的工作特性(如Selector,Channel,Buffer等組件)景殷,但是想要設(shè)計一個非阻塞的服務(wù)器仍然是一件很困難的事涯竟。非阻塞式服務(wù)器相較于阻塞式來說要多上許多挑戰(zhàn)。本文將會討論非阻塞式服務(wù)器的主要幾個難題端姚,并針對這些難題給出一些可能的解決方案。

查找關(guān)于非阻塞式服務(wù)器設(shè)計方面的資料實(shí)在不太容易杯缺,所以本文提供的解決方案都是基于本人工作和想法上的燕垃。如果各位有其他的替代方案或者更好的想法,我會很樂意聽取這些方案和想法净捅!你可以在文章下方留下你的評論,或者發(fā)郵件給我(郵箱為:info@jenkov.com )辩块。

本文的設(shè)計思路想法都是基于Java NIO的蛔六。但是我相信如果某些語言中也有像Selector之類的組件的話,文中的想法也能用于該語言废亭。據(jù)我所知国章,類似的組件底層操作系統(tǒng)會提供,所以對你來說也可以根據(jù)其中的思想運(yùn)用在其他語言上豆村。

非阻塞式服務(wù)器– GitHub 倉庫

我已經(jīng)創(chuàng)建了一些簡單的這些思想的概念驗(yàn)證呈現(xiàn)在這篇教程中液兽,并且為了讓你可以看到,我把源碼放到了github資源庫上了掌动。這里是GitHub資源庫地址:

https://github.com/jjenkov/java-nio-server

非阻塞式IO管道(Pipelines)

一個非阻塞式IO管道是由各個處理非阻塞式IO組件組成的鏈四啰。其中包括讀/寫IO。下圖就是一個簡單的非阻塞式IO管道組成:

非阻塞式IO管道組成

一個組件使用Selector監(jiān)控Channel什么時候有可讀數(shù)據(jù)粗恢。然后這個組件讀取輸入并且根據(jù)輸入生成相應(yīng)的輸出柑晒。最后輸出將會再次寫入到一個Channel中。

一個非阻塞式IO管道不需要將讀數(shù)據(jù)和寫數(shù)據(jù)都包含眷射,有一些管道可能只會讀數(shù)據(jù)匙赞,另一些可能只會寫數(shù)據(jù)。

上圖僅顯示了一個單一的組件妖碉。一個非阻塞式IO管道可能擁有超過一個以上的組件去處理輸入數(shù)據(jù)罚屋。一個非阻塞式管道的長度是由他的所要完成的任務(wù)決定。

一個非阻塞IO管道可能同時讀取多個Channel里的數(shù)據(jù)嗅绸。舉個例子:從多個SocketChannel管道讀取數(shù)據(jù)。

其實(shí)上圖的控制流程還是太簡單了撕彤。這里是組件從Selector開始從Channel中讀取數(shù)據(jù)鱼鸠,而不是Channel將數(shù)據(jù)推送給Selector進(jìn)入組件中,即便上圖畫的就是這樣羹铅。

非阻塞式 vs 阻塞式管道

非阻塞和阻塞IO管道兩者之間最大的區(qū)別在于他們?nèi)绾螐牡讓?code>Channel(Socket或者file)讀取數(shù)據(jù)蚀狰。

IO管道通常從流中讀取數(shù)據(jù)(來自socket或者file)并且將這些數(shù)據(jù)拆分為一系列連貫的消息。這和使用tokenizer(這里估計是解析器之類的意思)將數(shù)據(jù)流解析為token(這里應(yīng)該是數(shù)據(jù)包的意思)類似职员。相反你只是將數(shù)據(jù)流分解為更大的消息體麻蹋。我將拆分?jǐn)?shù)據(jù)流成消息這一組件稱為“消息讀取器”(Message Reader)下面是Message Reader拆分流為消息的示意圖:

Message Reader拆分流為消息

一個阻塞IO管道可以使用類似InputStream的接口每次一個字節(jié)地從底層Channel讀取數(shù)據(jù),并且這個接口阻塞直到有數(shù)據(jù)可以讀取焊切。這就是阻塞式Message Reader的實(shí)現(xiàn)過程扮授。

使用阻塞式IO接口簡化了Message Reader的實(shí)現(xiàn)芳室。阻塞式Message Reader從不用處理在流沒有數(shù)據(jù)可讀的情況,或者它只讀取流中的部分?jǐn)?shù)據(jù)并且對于消息的恢復(fù)也要延遲處理的情況刹勃。

同樣堪侯,阻塞式Message Writer(一個將數(shù)據(jù)寫入流中組件)也從不用處理只有部分?jǐn)?shù)據(jù)被寫入和寫入消息要延遲恢復(fù)的情況。

阻塞式IO管道的缺陷

雖然阻塞式Message Reader容易實(shí)現(xiàn)荔仁,但是也有一個不幸的缺點(diǎn):每一個要分解成消息的流都需要一個獨(dú)立的線程伍宦。必須要這樣做的理由是每一個流的IO接口會阻塞,直到它有數(shù)據(jù)讀取乏梁。這就意味著一個單獨(dú)的線程是無法嘗試從一個沒有數(shù)據(jù)的流中讀取數(shù)據(jù)轉(zhuǎn)去讀另一個流次洼。一旦一個線程嘗試從一個流中讀取數(shù)據(jù),那么這個線程將會阻塞直到有數(shù)據(jù)可以讀取遇骑。

如果IO管道是必須要處理大量并發(fā)鏈接服務(wù)器的一部分的話卖毁,那么服務(wù)器就需要為每一個鏈接維護(hù)一個線程。對于任何時間都只有幾百條并發(fā)鏈接的服務(wù)器這確實(shí)不是什么問題质蕉。但是如果服務(wù)器擁有百萬級別的并發(fā)鏈接量势篡,這種設(shè)計方式就沒有良好收放。每個線程都會占用棧32bit-64bit的內(nèi)存模暗。所以一百萬個線程占用的內(nèi)存將會達(dá)到1TB禁悠!不過在此之前服務(wù)器將會把所有的內(nèi)存用以處理傳過來的消息(例如:分配給消息處理期間使用對象的內(nèi)存)

為了將線程數(shù)量降下來,許多服務(wù)器使用了服務(wù)器維持線程池(例如:常用線程為100)的設(shè)計兑宇,從而一次一個地從入站鏈接(inbound connections)地讀取碍侦。入站鏈接保存在一個隊列中,線程按照進(jìn)入隊列的順序處理入站鏈接隶糕。這一設(shè)計如下圖所示:(譯者注:Tomcat就是這樣的)

然而瓷产,這一設(shè)計需要入站鏈接合理地發(fā)送數(shù)據(jù)。如果入站鏈接長時間不活躍枚驻,那么大量的不活躍鏈接實(shí)際上就造成了線程池中所有線程阻塞濒旦。這意味著服務(wù)器響應(yīng)變慢甚至是沒有反應(yīng)。

一些服務(wù)器嘗試通過彈性控制線程池的核心線程數(shù)量這一設(shè)計減輕這一問題再登。例如尔邓,如果線程池線程不足時,線程池可能開啟更多的線程處理請求锉矢。這一方案意味著需要大量的長時鏈接才能使服務(wù)器不響應(yīng)梯嗽。但是記住,對于并發(fā)線程數(shù)任然是有一個上限的沽损。因此灯节,這一方案仍然無法很好地解決一百萬個長時鏈接。

基礎(chǔ)非阻塞式IO管道設(shè)計

一個非阻塞式IO管道可以使用一個單獨(dú)的線程向多個流讀取數(shù)據(jù)。這需要流可以被切換到非阻塞模式炎疆。在非阻塞模式下卡骂,當(dāng)你讀取流信息時可能會返回0個字節(jié)或更多字節(jié)的信息。如果流中沒有數(shù)據(jù)可讀就返回0字節(jié)磷雇,如果流中有數(shù)據(jù)可讀就返回1+字節(jié)偿警。

為了避免檢查沒有可讀數(shù)據(jù)的流我們可以使用 Java NIO Selector. 一個或多個SelectableChannel實(shí)例可以同時被一個Selector注冊.。當(dāng)你調(diào)用Selectorselect()或者selectNow()方法它只會返回有數(shù)據(jù)讀取的SelectableChannel的實(shí)例. 下圖是該設(shè)計的示意圖:

讀取部分消息

當(dāng)我們從一個SelectableChannel讀取一個數(shù)據(jù)包時唯笙,我們不知道這個數(shù)據(jù)包相比于源文件是否有丟失或者重復(fù)數(shù)據(jù)(原文是:When we read a block of data from a SelectableChannel we do not know if that data block contains less or more than a message)螟蒸。一個數(shù)據(jù)包可能的情況有:缺失數(shù)據(jù)(比原有消息的數(shù)據(jù)少)、與原有一致崩掘、比原來的消息的數(shù)據(jù)更多(例如:是原來的1.5或者2.5倍)七嫌。數(shù)據(jù)包可能出現(xiàn)的情況如下圖所示:

在處理類似上面這樣部分信息時,有兩個問題:

  1. 判斷你是否能在數(shù)據(jù)包中獲取完整的消息苞慢。
  2. 在其余消息到達(dá)之前如何處理已到達(dá)的部分消息诵原。

判斷消息的完整性需要消息讀取器(Message Reader)在數(shù)據(jù)包中尋找是否存在至少一個完整消息體的數(shù)據(jù)。如果一個數(shù)據(jù)包包含一個或多個完整消息體挽放,這些消息就能夠被發(fā)送到管道進(jìn)行處理绍赛。尋找完整消息體這一處理可能會重復(fù)多次,因此這一操作應(yīng)該盡可能的快辑畦。

判斷消息完整性和存儲部分消息都是消息讀取器(Message Reader)的責(zé)任吗蚌。為了避免混合來自不同Channel的消息,我們將對每一個Channel使用一個Message Reader纯出。設(shè)計如下圖所示:

在從Selector得到可從中讀取數(shù)據(jù)的Channel實(shí)例之后,與該Channel相關(guān)聯(lián)的Message Reader讀取數(shù)據(jù)并嘗試將他們分解為消息蚯妇。這樣讀出的任何完整消息可以被傳到讀取通道(read pipeline)任何需要處理這些消息的組件中。

一個Message Reader一定滿足特定的協(xié)議暂筝。Message Reader需要知道它嘗試讀取的消息的消息格式箩言。如果我們的服務(wù)器可以通過協(xié)議來復(fù)用,那它需要有能夠插入Message Reader實(shí)現(xiàn)的功能 – 可能通過接收一個Message Reader工廠作為配置參數(shù)焕襟。

存儲部分消息

現(xiàn)在我們已經(jīng)確定Message Reader有責(zé)任存儲部分消息陨收,直到收到完整的消息,我們需要弄清楚這些部分消息的存儲應(yīng)該如何實(shí)現(xiàn)鸵赖。

有兩個設(shè)計因素我們要考慮:

  1. 我們想盡可能少地復(fù)制消息數(shù)據(jù)务漩。復(fù)制越多,性能越低卫漫。
  2. 我們希望將完整的消息存儲在連續(xù)的字節(jié)序列中,使解析消息更容易肾砂。

每個Message Reader的緩沖區(qū)

很顯然部分消息需要存儲某些緩沖區(qū)中列赎。簡單的實(shí)現(xiàn)方式可以是每一個Message Reader內(nèi)部簡單地有一個緩沖區(qū)。但是這個緩沖區(qū)應(yīng)該多大?它要大到足夠儲存最大允許儲存消息包吝。因此饼煞,如果最大允許儲存消息是1MB,那么Message Reader內(nèi)部緩沖區(qū)將至少需要1MB诗越。

當(dāng)我們的鏈接達(dá)到百萬數(shù)量級砖瞧,每個鏈接都使用1MB并沒有什么作用。1,000,000 * 1MB仍然是1TB的內(nèi)存嚷狞!那如果最大的消息是16MB甚至是128MB呢块促?

大小可調(diào)的緩沖區(qū)

另一個選擇是在Message Reader內(nèi)部實(shí)現(xiàn)一個大小可調(diào)的緩沖區(qū)。大小可調(diào)的緩沖區(qū)開始的時候很小床未,如果它獲取的消息過大竭翠,那緩沖區(qū)會擴(kuò)大。這樣每一條鏈接就不一定需要如1MB的緩沖區(qū)薇搁。每條鏈接的緩沖區(qū)只要需要足夠儲存下一條消息的內(nèi)存就行了斋扰。

有幾個可實(shí)現(xiàn)可調(diào)大小緩沖區(qū)的方法。它們都各自有自己的優(yōu)缺點(diǎn)啃洋,所以接下來的部分我將逐個討論传货。

通過復(fù)制調(diào)整大小

實(shí)現(xiàn)可調(diào)大小緩沖區(qū)的第一種方式是從一個大小(例如:4KB)的緩沖區(qū)開始。如果4KB的緩沖區(qū)裝不下一個消息宏娄,則會分配一個更大的緩沖區(qū)(如:8KB),并將大小為4KB的緩沖區(qū)數(shù)據(jù)復(fù)制到這個更大的緩沖區(qū)中去问裕。

通過復(fù)制實(shí)現(xiàn)大小可調(diào)緩沖區(qū)的優(yōu)點(diǎn)在于消息的所有數(shù)據(jù)被保存在一個連續(xù)的字節(jié)數(shù)組中,這就使得消息的解析更加容易绝编。它的缺點(diǎn)就是在復(fù)制更大消息的時候會導(dǎo)致大量的數(shù)據(jù)僻澎。

為了減少消息的復(fù)制,你可以分析流進(jìn)你系統(tǒng)的消息的大小十饥,并找出盡量減少復(fù)制量的緩沖區(qū)的大小窟勃。例如,你可能看到大多數(shù)消息都小于4KB逗堵,這是因?yàn)樗鼈兌純H包含很小的request/responses秉氧。這意味著緩沖區(qū)的初始值應(yīng)該設(shè)為4KB。

然后你可能有一個消息大于4KB蜒秤,這通常是因?yàn)樗锩姘粋€文件汁咏。你可能注意到大多數(shù)流進(jìn)系統(tǒng)的文件都是小于128KB的。這樣第二個緩沖區(qū)的大小設(shè)置為128KB就較為合理作媚。

最后你可能會發(fā)現(xiàn)一旦消息超過128KB之后攘滩,消息的大小就沒有什么固定的模式,因此緩沖區(qū)最終的大小可能就是最大消息的大小纸泡。

根據(jù)流經(jīng)系統(tǒng)的消息大小漂问,上面三種緩沖區(qū)大小可以減少數(shù)據(jù)的復(fù)制。小于4KB的消息將不會復(fù)制。對于一百萬個并發(fā)鏈接其結(jié)果是:1,000,000 * 4KB = 4GB蚤假,對于目前大多數(shù)服務(wù)器還是有可能的栏饮。介于4KB – 128KB的消息將只會復(fù)制一次,并且只有4KB的數(shù)據(jù)復(fù)制進(jìn)128KB的緩沖區(qū)中磷仰。介于128KB至最大消息大小的消息將會復(fù)制兩次袍嬉。第一次復(fù)制4KB,第二次復(fù)制128KB灶平,所以最大的消息總共復(fù)制了132KB伺通。假設(shè)沒有那么多超過128KB大小的消息那還是可以接受的。

一旦消息處理完畢民逼,那么分配的內(nèi)存將會被清空呼巴。這樣在同一鏈接接收到的下一條消息將會再次從最小緩沖區(qū)大小開始算坟岔。這樣做的必要性是確保了不同連接間內(nèi)存的有效共享系吩。所有的連接很有可能在同一時間并不需要打的緩沖區(qū)吐辙。

我有一篇介紹如何實(shí)現(xiàn)這樣支持可調(diào)整大小的數(shù)組的內(nèi)存緩沖區(qū)的完整文章:

Resizable Arrays

文章包含一個GitHub倉庫連接,其中的代碼演示了是如何實(shí)現(xiàn)的疮鲫。

通過追加調(diào)整大小

調(diào)整緩沖區(qū)大小的另一種方法是使緩沖區(qū)由多個數(shù)組組成吆你。當(dāng)你需要調(diào)整緩沖區(qū)大小時,你只需要另一個字節(jié)數(shù)組并將數(shù)據(jù)寫進(jìn)去就行了俊犯。

這里有兩種方法擴(kuò)張一個緩沖區(qū)妇多。一個方法是分配單獨(dú)的字節(jié)數(shù)組,并將這些數(shù)組保存在一個列表中燕侠。另一個方法是分配較大的共享字節(jié)數(shù)組的片段者祖,然后保留分配給緩沖區(qū)的片段的列表。就個人而言绢彤,我覺得片段的方式會好些七问,但是差別不大。

通過追加單獨(dú)的數(shù)組或片段來擴(kuò)展緩沖區(qū)的優(yōu)點(diǎn)在于寫入過程中不需要復(fù)制數(shù)據(jù)茫舶。所有的數(shù)據(jù)可以直接從socket (Channel)復(fù)制到一個數(shù)組或片段中械巡。

以這種方式擴(kuò)展緩沖區(qū)的缺點(diǎn)是在于數(shù)據(jù)不是存儲在單獨(dú)且連續(xù)的數(shù)組中。這將使得消息的解析更困難饶氏,因?yàn)榻馕銎餍枰瑫r查找每個單獨(dú)數(shù)組的結(jié)尾處和所有數(shù)組的結(jié)尾處讥耗。由于你需要在寫入的數(shù)據(jù)中查找消息的結(jié)尾,所以該模型并不容易使用疹启。

TLV編碼消息

一些協(xié)議消息格式是使用TLV格式(類型(Type)古程、長度(Length)、值(Value))編碼喊崖。這意味著當(dāng)消息到達(dá)時挣磨,消息的總長度被存儲在消息的開頭菲宴。這一方式你可以立即知道應(yīng)該對整個消息分配多大的內(nèi)存。

TLV編碼使得內(nèi)存管理變得更加容易趋急。你可以立即知道要分配多大的內(nèi)存給這個消息。只有部分在結(jié)束時使用的緩沖區(qū)才會使得內(nèi)存浪費(fèi)势誊。

TLV編碼的一個缺點(diǎn)是你要在消息的所有數(shù)據(jù)到達(dá)之前就分配好這個消息需要的所有內(nèi)存呜达。一些慢連接可能因此分配完你所有可用內(nèi)存,從而使得你的服務(wù)器無法響應(yīng)粟耻。

此問題的解決方法是使用包含多個TLV字段的消息格式查近。因此,服務(wù)器是為每個字段分配內(nèi)存而不是為整個消息分配內(nèi)存挤忙,并且是字段到達(dá)之后再分配內(nèi)存霜威。然而,一個大消息中的一個大字段在你的內(nèi)存管理有同樣的影響册烈。

另外一個方案就是對于還未到達(dá)的信息設(shè)置超時時間戈泼,例如10-15秒。當(dāng)恰好有許多大消息到達(dá)服務(wù)器時赏僧,這個方案能夠使得你的服務(wù)器可以恢復(fù)大猛,但是仍然會造成服務(wù)器一段時間無法響應(yīng)。另外淀零,惡意的DoS(Denial of Service拒絕服務(wù))攻擊仍然可以分配完你服務(wù)器的所有內(nèi)存挽绩。

TLV編碼存在許多不同的形式。實(shí)際使用的字節(jié)數(shù)驾中、自定字段的類型和長度都依賴于每一個TLV編碼唉堪。TLV編碼首先放置字段的長度、然后是類型肩民、然后是值(一個LTV編碼)唠亚。 雖然字段的順序不同,但它仍然是TLV的一種此改。

TLV編碼使內(nèi)存管理更容易這一事實(shí)趾撵,其實(shí)是HTTP 1.1是如此可怕的協(xié)議的原因之一。 這是他們試圖在HTTP 2.0中修復(fù)數(shù)據(jù)的問題之一共啃,數(shù)據(jù)在LTV編碼幀中傳輸占调。 這也是為什么我們使用TLV編碼的VStack.co project 設(shè)計了我們自己的網(wǎng)絡(luò)協(xié)議。

寫部分?jǐn)?shù)據(jù)

在非阻塞IO管道中寫數(shù)據(jù)仍然是一個挑戰(zhàn)移剪。當(dāng)你調(diào)用一個處于非阻塞式Channel對象的write(ByteBuffer)方法時究珊,ByteBuffer寫入多少數(shù)據(jù)是無法保證的。write(ByteBuffer)方法會返回寫入的字節(jié)數(shù)纵苛,因此可以跟蹤寫入的字節(jié)數(shù)剿涮。這就是挑戰(zhàn):跟蹤部分寫入的消息言津,以便最終可以發(fā)送一條消息的所有字節(jié)。

為了管理部分消息寫入Channel取试,我們將創(chuàng)建一個消息寫入器(Message Writer)悬槽。就像Message Reader一樣,每一個要寫入消息的Channel我們都需要一個Message Writer瞬浓。在每個Message Writer中初婆,我們跟蹤正在寫入的消息的字節(jié)數(shù)。

如果達(dá)到的消息量超過Message Writer可直接寫入Channel的消息量猿棉,消息就需要在Message Writer排隊磅叛。然后Message Writer盡快地將消息寫入到Channel中。

下圖是部分消息如何寫入的設(shè)計圖:

為了使Message Writer能夠盡快發(fā)送數(shù)據(jù)萨赁,Message Writer需要能夠不時被調(diào)用弊琴,這樣就能發(fā)送更多的消息。

如果你有大量的連接那你將需要大量的Message Writer實(shí)例杖爽。檢查Message Writer實(shí)例(如:一百萬個)看寫任何數(shù)據(jù)時是否緩慢敲董。 首先,許多Message Writer實(shí)例都沒有任何消息要發(fā)送慰安,我們并不想檢查那些Message Writer實(shí)例臣缀。其次,并不是所有的Channel實(shí)例都可以準(zhǔn)備好寫入數(shù)據(jù)泻帮。 我們不想浪費(fèi)時間嘗試將數(shù)據(jù)寫入無法接受任何數(shù)據(jù)的Channel精置。

為了檢查Channel是否準(zhǔn)備好進(jìn)行寫入,您可以使用Selector注冊Channel锣杂。然而我們并不想將所有的Channel實(shí)例注冊到Selector中去脂倦。想象一下,如果你有1,000,000個連接且其中大多是空閑的元莫,并且所有的連接已經(jīng)與Selector注冊赖阻。然后當(dāng)你調(diào)用select()時,這些Channel實(shí)例的大部分將被寫入就緒(它們大都是空閑的踱蠢,記得嗎火欧?)然后你必須檢查所有這些連接的Message Writer,以查看他們是否有任何數(shù)據(jù)要寫入茎截。

為了避免檢查所有消息的Message Writer實(shí)例和所有不可能被寫入任何信息的Channel實(shí)例苇侵,我們使用這兩步的方法:

  1. 當(dāng)一個消息被寫入Message Writer,Message Writer向Selector注冊其相關(guān)Channel(如果尚未注冊)企锌。
  2. 當(dāng)你的服務(wù)器有時間時榆浓,它檢查Selector以查看哪些注冊的Channel實(shí)例已準(zhǔn)備好進(jìn)行寫入。 對于每個寫就緒Channel撕攒,請求其關(guān)聯(lián)的Message Writer將數(shù)據(jù)寫入Channel陡鹃。 如果Message Writer將其所有消息寫入其Channel烘浦,則Channel將再次從Selector注冊。

這兩個小步驟確保了有消息寫入的Channel實(shí)際上已經(jīng)被Selector注冊了萍鲸。

匯總

正如你所見闷叉,一個非阻塞式服務(wù)器需要時不時檢查輸入的消息來判斷是否有任何的新的完整的消息發(fā)送過來。服務(wù)器可能會在一個或多個完整消息發(fā)來之前就檢查了多次脊阴。檢查一次是不夠的片习。

同樣,一個非阻塞式服務(wù)器需要時不時檢查是否有任何數(shù)據(jù)需要寫入蹬叭。如果有,服務(wù)器需要檢查是否有任何相應(yīng)的連接準(zhǔn)備好將該數(shù)據(jù)寫入它們状知。只有在第一次排隊消息時才檢查是不夠的秽五,因?yàn)橄⒖赡鼙徊糠謱懭搿?/p>

所有這些非阻塞服務(wù)器最終都需要定期執(zhí)行的三個“管道”(pipelines)::

  1. 讀取管道(The read pipeline),用于檢查是否有新數(shù)據(jù)從開放連接進(jìn)來的饥悴。
  2. 處理管道(The process pipeline)坦喘,用于所有任何完整消息。
  3. 寫入管道(The write pipeline)西设,用于檢查是否可以將任何傳出的消息寫入任何打開的連接瓣铣。

這三條管道在循環(huán)中重復(fù)執(zhí)行。你可能可以稍微優(yōu)化執(zhí)行贷揽。例如棠笑,如果沒有排隊的消息可以跳過寫入管道。 或者禽绪,如果我們沒有收到新的蓖救,完整的消息,也許您可以跳過處理管道印屁。

以下是說明完整服務(wù)器循環(huán)的圖:

如果仍然發(fā)現(xiàn)這有點(diǎn)復(fù)雜循捺,請記住查看GitHub資料庫:
https://github.com/jjenkov/java-nio-server

也許看到正在執(zhí)行的代碼可能會幫助你了解如何實(shí)現(xiàn)這一點(diǎn)。

服務(wù)器線程模型

GitHub資源庫里面的非阻塞式服務(wù)器實(shí)現(xiàn)使用了兩個線程的線程模式雄人。第一個線程用來接收來自ServerSocketChannel的傳入連接。第二個線程處理接受的連接础钠,意思是讀取消息旗吁,處理消息并將響應(yīng)寫回連接牺勾。這兩個線程模型的圖解如下:

上一節(jié)中說到的服務(wù)器循環(huán)處理是由處理線程(Processor Thread)執(zhí)行。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末驻民,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子回还,更是在濱河造成了極大的恐慌裆泳,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件柠硕,死亡現(xiàn)場離奇詭異,居然都是意外死亡闻葵,警方通過查閱死者的電腦和手機(jī)癣丧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來厢钧,“玉大人嬉橙,你說我怎么就攤上這事∠佳铮” “怎么了枫振?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長级及。 經(jīng)常有香客問我额衙,道長,這世上最難降的妖魔是什么县踢? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任伟件,我火速辦了婚禮硼啤,結(jié)果婚禮上斧账,老公的妹妹穿的比我還像新娘。我一直安慰自己嗓袱,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布蝙昙。 她就那樣靜靜地躺著奇颠,像睡著了一般放航。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天搜锰,我揣著相機(jī)與錄音耿战,去河邊找鬼。 笑死狈涮,一個胖子當(dāng)著我的面吹牛鸭栖,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播松却,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼溅话,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了砚哆?” 一聲冷哼從身側(cè)響起屑墨,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎关炼,沒想到半個月后匣吊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡社痛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年蒜哀,在試婚紗的時候發(fā)現(xiàn)自己被綠了吏砂。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,127評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡淀歇,死狀恐怖匈织,靈堂內(nèi)的尸體忽然破棺而出缀匕,到底是詐尸還是另有隱情,我是刑警寧澤乡小,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布满钟,位于F島的核電站,受9級特大地震影響苗分,放射性物質(zhì)發(fā)生泄漏牵辣。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一择浊、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧投剥,春花似錦担孔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽墩崩。三九已至,卻和暖如春鹦筹,著一層夾襖步出監(jiān)牢的瞬間盛龄,已是汗流浹背芳誓。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留匿值,地道東北人赂摆。 一個月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓烟号,卻偏偏與公主長得像,于是被迫代替她去往敵國和親达传。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Java NIO(New IO)是從Java 1.4版本開始引入的一個新的IO API宗弯,可以替代標(biāo)準(zhǔn)的Java I...
    JackChen1024閱讀 7,555評論 1 143
  • 轉(zhuǎn)自 http://www.ibm.com/developerworks/cn/education/java/j-...
    抓兔子的貓閱讀 2,310評論 0 22
  • 從三月份找實(shí)習(xí)到現(xiàn)在蒙保,面了一些公司邓厕,掛了不少岛蚤,但最終還是拿到小米、百度涤妒、阿里、京東硅堆、新浪贿讹、CVTE、樂視家的研發(fā)崗...
    時芥藍(lán)閱讀 42,255評論 11 349
  • 前言: 之前的文章《Java文件IO常用歸納》主要寫了Java 標(biāo)準(zhǔn)IO要注意的細(xì)節(jié)和技巧茄菊,由于網(wǎng)上各種學(xué)習(xí)途徑面殖,...
    androidjp閱讀 2,907評論 0 22
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理哭廉,服務(wù)發(fā)現(xiàn),斷路器辽幌,智...
    卡卡羅2017閱讀 134,669評論 18 139