Netty實戰一 | Java BIO, NIO 及Netty簡介
標簽: JAVA網絡協議編程 網絡 編程語言 java netty
免責聲明:本人最近在研讀《Netty實戰》書籍,對于里面內容頗感興趣,本文旨在于技術學習交流,不存在盈利性目的。
Java 網絡編程
BIO:block input output
早期的網絡編程開發人員,需要花費大量的時間去學習復雜的 C 語言套接字庫,去處理它們在不同的操作系統上出現的古怪問題。早期的 Java API(java.net)只支持由本地系統套接字庫提供的所謂的阻塞函數。Java創建一個復雜的客戶端/服務器協議需要大量的樣板代碼(以及相當多的底層研究才能使它整個流暢地運行起來)。早期Java阻塞 I/O 示例代碼:
ServerSocket serverSocket = new ServerSocket(portNumber);
Socket clientSocket = serverSocket.accept();
BufferedReader in = new BufferedReader(
new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out =
new PrintWriter(clientSocket.getOutputStream(), true);
String request, response;
while ((request = in.readLine()) != null) {
if ("Done".equals(request)) {
break;
}
response = processRequest(request);
out.println(response);
}
這段代碼片段將只能同時處理一個連接,要管理多個并發客戶端,需要為每個新的客戶端Socket 創建一個新的 Thread,對于這種模式,主要存在以下幾個問題:
第一,在任何時候都可能有大量的線程處于休眠狀態,只是等待輸入或者輸出數據就緒,這可能算是一種資源浪費。
第二,需要為每個線程的調用棧都分配內存,其默認值大小區間為 64 KB 到 1 MB,具體取決于操作系統。
第三,即使 Java 虛擬機(JVM)在物理上可以支持非常大數量的線程,但是遠在到達該極限之前,上下文切換所帶來的開銷就會帶來麻煩,例如,在達到 10 000 個連接的時候。雖然這種并發方案對于支撐中小數量的客戶端來說還算可以接受,但是為了支撐 100 000 或者更多的并發連接所需要的資源使得它很不理想。
這就是JAVA中典型的BIO模型,模型結構圖如下所示:

BIO 就是傳統的 java.io 包,它是基于流模型實現的,交互的方式是同步、阻塞方式,也就是說在讀入輸入流或者輸出流時,在讀寫動作完成之前,線程會一直阻塞在那里,它們之間的調用時可靠的線性順序。它的有點就是代碼比較簡單、直觀;缺點就是 IO 的效率和擴展性很低,容易成為應用性能瓶頸。
NIO:Non-blocking input output
NIO 是 Java 1.4 引入的 java.nio 包,提供了 Channel、Selector、Buffer 等新的抽象,可以構建多路復用的、同步非阻塞 IO 程序,同時提供了更接近操作系統底層高性能的數據操作方式。
新的還是非阻塞的
NIO 最開始是新的輸入/輸出(New Input/Output)的英文縮寫,但是,該Java API 已經出現足夠長的時間了,不再是“新的”了,因此,如今大多數的用戶認為NIO 代表非阻塞 I/O(Non-blocking I/O),而阻塞I/O(blocking I/O)是舊的輸入/輸出(old input/output,OIO)。你也可能遇到它被稱為普通I/O(plain I/O)的時候。

Java NIO
的核心組件 包括:
- 通道(
Channel
) - 緩沖區(
Buffer
) - 選擇器(
Selectors
)

NIO具體實現代碼示例:
基于通道 & 緩沖數據
// 1. 獲取數據源 和 目標傳輸地的輸入輸出流(此處以數據源 = 文件為例)
FileInputStream fin = new FileInputStream(infile);
FileOutputStream fout = new FileOutputStream(outfile);
// 2. 獲取數據源的輸入輸出通道
FileChannel fcin = fin.getChannel();
FileChannel fcout = fout.getChannel();
// 3. 創建 緩沖區 對象:Buffer(共有2種方法)
// 方法1:使用allocate()靜態方法
ByteBuffer buff = ByteBuffer.allocate(256);
// 上述方法創建1個容量為256字節的ByteBuffer
// 注:若發現創建的緩沖區容量太小,則重新創建一個大小合適的緩沖區
// 方法2:通過包裝一個已有的數組來創建
// 注:通過包裝的方法創建的緩沖區保留了被包裝數組內保存的數據
ByteBuffer buff = ByteBuffer.wrap(byteArray);
// 額外:若需將1個字符串存入ByteBuffer,則如下
String sendString="你好,服務器. ";
ByteBuffer sendBuff = ByteBuffer.wrap(sendString.getBytes("UTF-16"));
// 4. 從通道讀取數據 & 寫入到緩沖區
// 注:若 以讀取到該通道數據的末尾,則返回-1
fcin.read(buff);
// 5. 傳出數據準備:將緩存區的寫模式 轉換->> 讀模式
buff.flip();
// 6. 從 Buffer 中讀取數據 & 傳出數據到通道
fcout.write(buff);
// 7. 重置緩沖區
// 目的:重用現在的緩沖區,即 不必為了每次讀寫都創建新的緩沖區,在再次讀取之前要重置緩沖區
// 注:不會改變緩沖區的數據,只是重置緩沖區的主要索引值
buff.clear();
基于選擇器(Selecter)
// 1. 創建Selector對象
Selector sel = Selector.open();
// 2. 向Selector對象綁定通道
// a. 創建可選擇通道,并配置為非阻塞模式
ServerSocketChannel server = ServerSocketChannel.open();
server.configureBlocking(false);
// b. 綁定通道到指定端口
ServerSocket socket = server.socket();
InetSocketAddress address = new InetSocketAddress(port);
socket.bind(address);
// c. 向Selector中注冊感興趣的事件
server.register(sel, SelectionKey.OP_ACCEPT);
return sel;
// 3. 處理事件
try {
while(true) {
// 該調用會阻塞,直到至少有一個事件就緒、準備發生
selector.select();
// 一旦上述方法返回,線程就可以處理這些事件
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iter = keys.iterator();
while (iter.hasNext()) {
SelectionKey key = (SelectionKey) iter.next();
iter.remove();
process(key);
}
}
} catch (IOException e) {
e.printStackTrace();
}
class java.nio.channels.Selector 是Java 的非阻塞 I/O 實現的關鍵。它使用了事件通知 API以確定在一組非阻塞套接字中有哪些已經就緒能夠進行 I/O 相關的操作。因為可以在任何的時間檢查任意的讀操作或者寫操作的完成狀態,個單一的線程便可以處理多個并發的接。
總體來看,與阻塞 I/O 模型相比,這種模型提供了更好的資源管理:
- 使用較少的線程便可以處理許多連接,因此也減少了內存管理和上下文切換所帶來開銷;
- 當沒有 I/O 操作需要處理的時候,線程也可以被用于其他任務。

盡管已經有許多直接使用 Java NIO API 的應用程序被構建了,但是要做到如此正確和安全并不容易。特別是,在高負載下可靠和高效地處理和調度 I/O 操作是一項繁瑣而且容易出錯的任務,最好留給高性能的網絡編程專家——Netty。
Netty 簡介
Netty 是一款用于創建高性能網絡應用程序的高級框架。是一款異步的事件驅動的網絡應用程序框架,支持快速地開發可維護的高性能的面向協議的服務器和客戶端。在網絡編程領域,Netty是Java的卓越框架。它駕馭了Java高級API的能力,并將其隱藏在一個易于使用的API之后。Netty使你可以專注于自己真正感興趣的——你的應用程序的獨一無二的價值。

異步和事件驅動
異步也就是非同步,本質上,一個既是異步的又是事件驅動的系統會表現出一種特殊的、對我們來說極具價值的行為:它可以以任意的順序響應在任意的時間點產生的事件。這種能力對于實現最高級別的可伸縮性至關重要,定義為:“一種系統、網絡或者進程在需要處理的工作不斷增長時,可以通過某種可行的方式或者擴大它的處理能力來適應這種增長的能力。”
同步交互:指發送一個請求,需要等待返回,然后才能夠發送下一個請求,有個等待過程;
異步交互:指發送一個請求,不需要等待返回,隨時可以再發送下一個請求,即不需要等待。 區別:一個需要等待,一個不需要等待,在部分情況下,我們的項目開發中都會優先選擇不需要等待的異步交互方式。
netty 是事件驅動的,這里面有兩個含義,一是 netty 接收到 socket 數據后,會產生事件,事件在 pipeline 上傳播,二是事件由特定的線程池處理。
異步和可伸縮性之間的聯系又是什么呢?
- 非阻塞網絡調用使得我們可以不必等待一個操作的完成。完全異步的 I/O 正是基于這個特性構建的,并且更進一步:異步方法會立即返回,并且在它完成時,會直接或者在稍后的某個時間點通知用戶。
- 選擇器使得我們能夠通過較少的線程便可監視許多連接上的事件。
將這些元素結合在一起,與使用阻塞 I/O 來處理大量事件相比,使用非阻塞 I/O 來處理更快速、更經濟。從網絡編程的角度來看,這是構建我們理想系統的關鍵,這也是Netty 的設計底蘊的關鍵。
智能推薦
freemarker + ItextRender 根據模板生成PDF文件
1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...
電腦空間不夠了?教你一個小秒招快速清理 Docker 占用的磁盤空間!
Docker 很占用空間,每當我們運行容器、拉取鏡像、部署應用、構建自己的鏡像時,我們的磁盤空間會被大量占用。 如果你也被這個問題所困擾,咱們就一起看一下 Docker 是如何使用磁盤空間的,以及如何回收。 docker 占用的空間可以通過下面的命令查看: TYPE 列出了docker 使用磁盤的 4 種類型: Images:所有鏡像占用的空間,包括拉取下來的鏡像,和本地構建的。 Con...
requests實現全自動PPT模板
http://www.1ppt.com/moban/ 可以免費的下載PPT模板,當然如果要人工一個個下,還是挺麻煩的,我們可以利用requests輕松下載 訪問這個主頁,我們可以看到下面的樣式 點每一個PPT模板的圖片,我們可以進入到詳細的信息頁面,翻到下面,我們可以看到對應的下載地址 點擊這個下載的按鈕,我們便可以下載對應的PPT壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...
Linux C系統編程-線程互斥鎖(四)
互斥鎖 互斥鎖也是屬于線程之間處理同步互斥方式,有上鎖/解鎖兩種狀態。 互斥鎖函數接口 1)初始化互斥鎖 pthread_mutex_init() man 3 pthread_mutex_init (找不到的情況下首先 sudo apt-get install glibc-doc sudo apt-get install manpages-posix-dev) 動態初始化 int pthread_...
猜你喜歡
統計學習方法 - 樸素貝葉斯
引入問題:一機器在良好狀態生產合格產品幾率是 90%,在故障狀態生產合格產品幾率是 30%,機器良好的概率是 75%。若一日第一件產品是合格品,那么此日機器良好的概率是多少。 貝葉斯模型 生成模型與判別模型 判別模型,即要判斷這個東西到底是哪一類,也就是要求y,那就用給定的x去預測。 生成模型,是要生成一個模型,那就是誰根據什么生成了模型,誰就是類別y,根據的內容就是x 以上述例子,判斷一個生產出...
styled-components —— React 中的 CSS 最佳實踐
https://zhuanlan.zhihu.com/p/29344146 Styled-components 是目前 React 樣式方案中最受關注的一種,它既具備了 css-in-js 的模塊化與參數化優點,又完全使用CSS的書寫習慣,不會引起額外的學習成本。本文是 styled-components 作者之一 Max Stoiber 所寫,首先總結了前端組件化樣式中的最佳實踐原則,然后在此基...
19.vue中封裝echarts組件
19.vue中封裝echarts組件 1.效果圖 2.echarts組件 3.使用組件 按照組件格式整理好數據格式 傳入組件 home.vue 4.接口返回數據格式...