<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                ## 1\. 前言 最近在學習 Java NIO 方面的知識,為了加深理解。特地去看了 Unix/Linux I/O 方面的知識,并寫了一些代碼進行驗證。在本文接下來的一章中,我將通過舉例的方式向大家介紹五種 I/O 模型。如果大家是第一次了解 I/O 模型方面的知識,理解起來會有一定的難度。所以在看文章的同時,我更建議大家動手去實現這些 I/O 模型,感覺會不一樣。好了,下面咱們一起進入正題吧。 ## 2\. I/O 模型 本章將向大家介紹五種 I/O 模型,包括阻塞 I/O、非阻塞 I/O、I/O 復用、信號驅動式 I/O 、異步 I/O 等。本文的內容參考了《UNIX網絡編程》,文中所用部分圖片也是來自于本書。關于《UNIX網絡編程》這本書,我想就不用多說了。很多寫網絡編程方面的文章一般都會參考該書,本文也不例外。如果大家想進深入學習網絡編程,建議去讀讀這本書。 ### [](http://www.tianxiaobo.com/2018/02/08/IO%E6%A8%A1%E5%9E%8B%E7%AE%80%E8%BF%B0/#21-阻塞-io-模型)2.1 阻塞 I/O 模型 阻塞 I/O 是最簡單的 I/O 模型,一般表現為進程或線程等待某個條件,如果條件不滿足,則一直等下去。條件滿足,則進行下一步操作。相關示意圖如下: [![](https://blog-pictures.oss-cn-shanghai.aliyuncs.com/15180090652103.jpg)](http://www.coolblog.xyz/) 上圖中,應用進程通過系統調用 recvfrom 接收數據,但由于內核還未準備好數據報,應用進程就阻塞住了。直到內核準備好數據報,recvfrom 完成數據報復制工作,應用進程才能結束阻塞狀態。 這里簡單解釋一下應用進程和內核的關系。內核即操作系統內核,用于控制計算機硬件。同時將用戶態的程序和底層硬件隔離開,以保障整個計算機系統的穩定運轉(如果用戶態的程序可以控制底層硬件,那么一些病毒就會針對硬件進行破壞,比如 CIH 病毒)。應用進程即用戶態進程,運行于操作系統之上,通過系統調用與操作系統進行交互。上圖中,內核指的是 TCP/IP 等協議及相關驅動程序。客戶端發送的請求,并不是直接送達給應用程序,而是要先經過內核。內核將請求數據緩存在內核空間,應用進程通過 recvfrom 調用,將數據從內核空間拷貝到自己的進程空間內。大致示意圖如下: [![](https://blog-pictures.oss-cn-shanghai.aliyuncs.com/15180125151397.jpg)](http://www.coolblog.xyz/) 阻塞 I/O 理解起來并不難,不過這里還是舉個例子類比一下。假設大家日常工作流程設這樣的(其實就是我日常工作的流程??),我們寫好代碼后,本地測試無誤,通過郵件的方式,告知運維同學發布服務。運維同學通過發布腳本打包代碼,重啟服務(心疼我司的人肉運維)。一般項目比較大時,重啟一次比較耗時。而運維同學又有點死腦筋,非要等這個服務重啟好,再去做其他事。結果一天等待的時間比真正工作的時間還要長,然后就被開了。運維同學用這個例子告訴我們,阻塞式 I/O 效率不太好。 ### [](http://www.tianxiaobo.com/2018/02/08/IO%E6%A8%A1%E5%9E%8B%E7%AE%80%E8%BF%B0/#22-非阻塞-io-模型)2.2 非阻塞 I/O 模型 與阻塞 I/O 模型相反,在非阻塞 I/O 模型下。應用進程與內核交互,目的未達到時,不再一味的等著,而是直接返回。然后通過輪詢的方式,不停的去問內核數據準備好沒。示意圖如下: [![](https://blog-pictures.oss-cn-shanghai.aliyuncs.com/15180140157823.jpg)](http://www.coolblog.xyz/) 上圖中,應用進程通過 recvfrom 系統調用不停的去和內核交互,直到內核準備好數據報。從上面的流程中可以看出,應用進程進入輪詢狀態時等同于阻塞,所以非阻塞的 I/O 似乎并沒有提高進程工作效率。 再用上面的例子進行類比。公司辭退了上一個怠工的運維同學后,又招了一個運維同學。這個運維同學每次重啟服務,隔一分鐘去看一下,然后進入發呆狀態。雖然真正的工作時間增加了,但是沒用啊,等待的時間還是太長了。被公司發現后,又被辭了。 ### [](http://www.tianxiaobo.com/2018/02/08/IO%E6%A8%A1%E5%9E%8B%E7%AE%80%E8%BF%B0/#23-io-復用模型)2.3 I/O 復用模型 Unix/Linux 環境下的 I/O 復用模型包含三組系統調用,分別是 select、poll 和 epoll(FreeBSD 中則為 kqueue)。select 出現的時間最早,在 BSD 4.2中被引入。poll 則是在 AT&T System V UNIX 版本中被引入(詳情請參考 UNIX man-page)。epoll 出現在 Linux kernel 2.5.44 版本中,與之對應的 kqueue 調用則出現在 FreeBSD 4.1,早于 epoll。select 和 poll 出現的時間比較早,在當時也是比較先進的 I/O 模型了,滿足了當時的需求。不過隨著因特網用戶的增長,C10K 問題出現。select 和 poll 已經不能滿足需求了,研發更加高效的 I/O 模型迫在眉睫。到了 2000 年,FreeBSD 率先發布了 select、poll 的改進版 kqueue。Linux 平臺則在 2002 年 2.5.44 中發布了 epoll。好了,關于三者的一些歷史就說到這里。本節接下來將以 select 函數為例,簡述該函數的使用過程。 select 有三個文件描述符集(readfds),分別是可讀文件描述符集(writefds)、可寫文件描述符集和異常文件描述符集(exceptfds)。應用程序可將某個 socket (文件描述符)設置到感興趣的文件描述符集中,并調用 select 等待所感興趣的事件發生。比如某個 socket 處于可讀狀態了,此時應用進程就可調用 recvfrom 函數把數據從內核空間拷貝到進程空間內,無需再等待內核準備數據了。示意圖如下: [![](https://blog-pictures.oss-cn-shanghai.aliyuncs.com/15180556060991.jpg)](http://www.coolblog.xyz/) 一般情況下,應用進程會將多個 socket 設置到感興趣的文件描述符集中,并調用 select 等待所關注的事件(比如可讀、可寫)處于就緒狀態。當某些 socket 處于就緒狀態后,select 返回處于就緒狀態的 sockct 數量。注意這里返回的是 socket 的數量,并不是具體的 socket。應用程序需要自己去確定哪些 socket 處于就緒狀態了,確定之后即可進行后續操作。 I/O 復用本身不是很好理解,所以這里還是舉例說明吧。話說公司的運維部連續辭退兩個運維同學后,運維部的 leader 覺得需要親自監督一下大家工作。于是 leader 在周會上和大家說,從下周開始,所有的發布郵件都由他接收,并由他轉發給相關運維同學,同時也由他重啟服務。各位運維同學需要告訴 leader 各自所負責監控的項目,服務重啟好后,leader 會通過內部溝通工具通知相關運維同學。至于服務重啟的結果(成功或失敗),leader 不關心,需要運維同學自己去看。運維同學看好后,需要把結果回復給開發同學。 上面的流程可能有點啰嗦,所以還是看圖吧。 [![](https://blog-pictures.oss-cn-shanghai.aliyuncs.com/15180681282120.jpg)](http://www.coolblog.xyz/) 把上面的流程進行分步,如下: 1. 開發同學將發布郵件發送給運維 leader,并指明這個郵件應該轉發給誰 2. 運維告訴 leader,如果有發給我的郵件,請發送給我 3. leader 把郵件轉發給相關的運維同學,并著手重啟服務 4. 運維同學看完郵件,告訴 leader 某某服務重啟好后,請告訴我 5. 服務重啟好,leader 通知運維同學xx服務啟動好了 6. 運維同學查看服務啟動情況,并返回信息給開發同學 這種方式為什么可以提高工作效率呢?原因在于運維同學一股腦把他所負責的幾十個項目都告訴了 leader,由 leader 重啟服務,并通知運維同學。運維同學這個時候等待 leader 的通知,只要其中一個或幾個服務重啟好了,運維同學就回接到通知,然后就可去干活了。而不是像以前一樣,非要等某個服務重啟好再進行后面的工作。 說一下上面例子的角色扮演。開發同學是客戶端,leader 是內核。開發同學發的郵件相當于網絡請求,leader 接收郵件,并重啟服務,相當于內核準備數據。運維同學是服務端應用進程,告訴 leader 自己感興趣的事情,并在最后將事情的處理結果返回給開發同學。 不知道大家有沒有理解上面的例子,I/O 復用本身可能就不太好理解,所以看不懂也不要氣餒。另外,上面的例子只是為了說明情況,現實中并不會是這樣干,不然 leader 要累死了。如果大家覺得上面的例子不太好,我建議大家去看看權威資料《UNIX網絡編程》。同時,如果能用 select 寫個簡單的 tcp 服務器,有助于加深對 I/O 復用的理解。如果不會寫,也可以參考我寫的代碼[select\_server.c](https://github.com/coolblog-xyz/toyhttpd/blob/master/select_server.c)。 ### [](http://www.tianxiaobo.com/2018/02/08/IO%E6%A8%A1%E5%9E%8B%E7%AE%80%E8%BF%B0/#24-信號驅動式-io-模型)2.4 信號驅動式 I/O 模型 信號驅動式 I/O 模型是指,應用進程告訴內核,如果某個 socket 的某個事件發生時,請向我發一個信號。在收到信號后,信號對應的處理函數會進行后續處理。示意圖如下: [![](https://blog-pictures.oss-cn-shanghai.aliyuncs.com/15180719994697.jpg)](http://www.coolblog.xyz/) 再用之前的例子進行說明。某個運維同學比較聰明,他寫了一個監控系統。重啟服務的過程由監控系統來做,做好后,監控系統會給他發個通知。在此之前,運維同學可以去做其他的事情,不用一直發呆等著了。運維同學收到通知后,首先去檢查服務重啟情況,接著再給開發同學回復郵件就行了。 相比之前的工作方式,是不是感覺這種方式更合理。從流程上來說,這種方式確實更合理。進程在信號到來之前,可以去做其他事情,而不用忙等。但現實中,這種 I/O 模型用的并不多。 ### [](http://www.tianxiaobo.com/2018/02/08/IO%E6%A8%A1%E5%9E%8B%E7%AE%80%E8%BF%B0/#25-異步-io-模型)2.5 異步 I/O 模型 異步 I/O 是指應用進程把文件描述符傳給內核后,啥都不管了,完全由內核去操作這個文件描述符。內核完成相關操作后,會發信號告訴應用進程,某某 I/O 操作我完成了,你現在可以進行后續操作了。示意圖如下: [![](https://blog-pictures.oss-cn-shanghai.aliyuncs.com/15180089800822.jpg)](http://www.coolblog.xyz/) 上圖通過 aio\_read 把文件描述符、數據緩存空間,以及信號告訴內核,當文件描述符處于可讀狀態時,內核會親自將數據從內核空間拷貝到應用進程指定的緩存空間呢。拷貝完在告訴進程 I/O 操作結束,你可以直接使用數據了。 接著上一節的例子進行類比,運維小哥升級了他的監控系統。此時,監控系統不光可以監控服務重啟狀態,還能把重啟結果整理好,發送給開發小哥。而運維小哥要做的事情就更簡單了,收收郵件,點點監控系統上的發布按鈕。然后就可以悠哉悠哉的繼續睡覺了,一天一天的就這么過去了。 ### [](http://www.tianxiaobo.com/2018/02/08/IO%E6%A8%A1%E5%9E%8B%E7%AE%80%E8%BF%B0/#26-總結)2.6 總結 上面介紹了5種 I/O 模型,也通過舉例的形式對每種模型進行了補充說明,不知道大家看懂沒。拋開上面的 I/O 模型不談,如果某種 I/O 模型能讓進程的工作的時間大于等待的時間,那么這種模型就是高效的模型。在服務端請求量變大時,通過 I/O 復用模型可以讓進程進入繁忙的工作狀態中,減少忙等,進而提高了效率。 I/O 復用模型結果數次改進,目前性能已經很好了,也得到了廣泛應用。像 Nginx,lighttd 等服務器軟件都選用該模型。好了,關于 I/O 模型就說到這里。 最后附一張幾種 I/O 模型的對比圖: [![](https://blog-pictures.oss-cn-shanghai.aliyuncs.com/15180794109268.jpg)](http://www.coolblog.xyz/)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看