網絡 I/O 模型優化
? ?網絡通信中,最底層的就是內核中的網絡 I/O 模型了。隨著技術的發展,操作系統內核的網絡模型衍生出了五種 I/O 模型,《UNIX 網絡編程》一書將這五種 I/O 模型分為阻塞式 I/O、非阻塞式 I/O、I/O 復用、信號驅動式 I/O 和異步 I/O。每一種 I/O 模型的出現,都 是基于前一種 I/O 模型的優化升級。
? ?最開始的阻塞式 I/O,它在每一個連接創建時,都需要一個用戶線程來處理,并且在 I/O 操 作沒有就緒或結束時,線程會被掛起,進入阻塞等待狀態,阻塞式 I/O 就成為了導致性能 瓶頸的根本原因。
那阻塞到底發生在套接字(socket)通信的哪些環節呢?
? ?在《Unix 網絡編程》中,套接字通信可以分為流式套接字(TCP)和數據報套接字 (UDP)。其中 TCP 連接是我們最常用的,一起來了解下 TCP 服務端的工作流程(由于 TCP 的數據傳輸比較復雜,存在拆包和裝包的可能,這里我只假設一次最簡單的 TCP 數據
傳輸):
? ? 首先,應用程序通過系統調用 socket 創建一個套接字,它是系統分配給應用程序的一個 文件描述符; 其次,應用程序會通過系統調用 bind,綁定地址和端口號,給套接字命名一個名稱; 然后,系統會調用 listen 創建一個隊列用于存放客戶端進來的連接;最后,應用服務會通過系統調用 accept 來監聽客戶端的連接請求。 當有一個客戶端連接到服務端之后,服務端就會調用 fork 創建一個子進程,通過系統調用 read 監聽客戶端發來的消息,再通過 write 向客戶端返回信息。
非阻塞式 I/O
使用 fcntl 可以把以上三種操作都設置為非阻塞操作。如果沒有數據返回,就會直接返回一 個 EWOULDBLOCK 或 EAGAIN 錯誤,此時進程就不會一直被阻塞。
當我們把以上操作設置為了非阻塞狀態,我們需要設置一個線程對該操作進行輪詢檢查,這也是最傳統的非阻塞 I/O 模型。
零拷貝
? ?在 I/O 復用模型中,執行讀寫 I/O 操作依然是阻塞的,在執行讀寫 I/O 操作時,存在著多 次內存拷貝和上下文切換,給系統增加了性能開銷。
零拷貝是一種避免多次內存復制的技術,用來優化讀寫 I/O 操作。
? ? 在網絡編程中,通常由 read、write 來完成一次 I/O 讀寫操作。每一次 I/O 讀寫操作都需 要完成四次內存拷貝,路徑是 I/O 設備 -> 內核空間 -> 用戶空間 -> 內核空間 -> 其它 I/O 設備
? ? Linux 內核中的 mmap 函數可以代替 read、write 的 I/O 讀寫操作,實現用戶空間和內核 空間共享一個緩存數據。mmap 將用戶空間的一塊地址和內核空間的一塊地址同時映射到相同的一塊物理內存地址,不管是用戶空間還是內核空間都是虛擬地址,最終要通過地址映 射映射到物理內存地址。這種方式避免了內核空間與用戶空間的數據交換。I/O 復用中的 epoll 函數中就是使用了 mmap 減少了內存拷貝。
在 Java 的 NIO 編程中,則是使用到了 Direct Buffer 來實現內存的零拷貝。Java 直接在 JVM 內存空間之外開辟了一個物理內存空間,這樣內核和用戶進程都能共享一份緩存數 據。這是在 08 講中已經詳細講解過的內容,你可以再去回顧下。
推薦閱讀
技術總體方案設計思路
Introduction to UML
業務冪等性技術架構體系