寫在開頭:Netty是個什麼玩意?這裏摘抄官網的一段話:Netty是由JBOSS提供的一個java開源框架。Netty提供異步的、事件驅動的網絡應用程序框架和工具,用以快速開發高性能、高可靠性的網絡服務器和客戶端程序。java
也就是說,Netty 是一個基於NIO的客戶、服務器端編程框架,使用Netty 能夠確保你快速和簡單的開發出一個網絡應用,例如實現了某種協議的客戶,服務端應用。Netty至關簡化和流線化了網絡應用的編程開發過程,例如,TCP和UDP的socket服務開發。linux
總結一句話:Netty是用於開發客戶端、服務端通訊系統的一套框架。數據庫
咱們學習Netty能用來幹什麼呢?或者說有哪些咱們經常使用的東西使用Netty開發的呢?舉兩個例子:Hadoop和dubbo,Hadoop底層使用netty作通訊架構的,dubbo底層也是用netty寫的。netty在中間件系統開發中用的特別多,可能你會認爲我用不到啊,我平時都是SofaMVC或者SpringMVC,Mybatis,Spring一類的,而後基本上都是和數據庫打交道,每天crud。對,說的沒錯,可是你別忘了,互聯網的技術本質其實就是通訊,你操做數據庫其實也是在不斷和數據庫在通訊,並且隨着系統規模增大,大到現有的框架已經不能知足要求了,那麼你就須要開發本身的通訊系統。編程
在沒有Netty以前咱們是怎麼開發通訊系統的,記得之前作過一個項目,是替有關部門作一個車輛監控系統,每一個車上面有個GPS設備(與衛星通訊的專業設備),他們但願可以監控車輛的運行狀態。當時在作服務端的時候用的是BIO框架,也就是阻塞IO,採用一端一線程來解決,在開發的時候遇到了不少坑,好比說客戶端(GPS模塊)與服務端連接超時,網絡閃斷,板報讀寫,網絡擁塞等,而且一臺機器的線程數有限,還好當時只監控500輛車,當時遇到的最噁心的是GPS這個模塊他們用了一個廠家作的嵌入式設備,由於我之前是開發單片機的,對着還比較瞭解,當時他們的協議棧有問題,雖然客戶端與服務器鏈接斷了,可是服務器這邊仍是現實鏈接着,後來發現是他們經過基站鏈接着服務器,總之,特別多的問題。基本上好多代碼都不是去解決業務自己並且在構建一個通訊框架,這固然不是我想要的。緩存
再後來,使用了NIO框架,好處不用多說,單機支撐上萬線程,採用linux的enpoll模型,多路io複用,可是在採用原生的NIO框架開發通訊系統的時候,依舊大量代碼用於構建通訊框架而不是業務自己。而且一直崩潰,不穩定,這其實也是NIO框架的問題所在,主要有如下幾點:服務器
1. NIO的類庫和API繁雜。網絡
2. 須要具有其餘的額外技能作鋪墊。架構
3. 可靠性能力不齊,工做了和難度大。例如:客戶端斷線重連,網絡閃斷,半包讀寫,失敗緩存,網絡擁塞,異常碼流等。框架
4. JDK的BUG,例如:epoll的bug會致使selector空輪詢,致使CPU 100%,該問題到JDK1.7版本仍是一直存在。異步
廢話很少說,先看一個簡單的採用原生NIO框架開發的服務器例子(簡單)。
package com.dlb.note.server.nio; import java.io.Closeable; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.*; import java.nio.charset.Charset; import java.util.Iterator; /** * <li> * <b>nio作服務端開發的問題:</b> * 1. NIO的類庫和API繁雜。 * 2. 須要具有其餘的額外技能作鋪墊。 * 3. 可靠性能力不齊,工做了和難度大。例如:客戶端斷線重連,網絡閃斷,半包讀寫,失敗緩存,網絡擁塞,異常碼流等。 * 4. JDK的BUG,例如:epoll的bug會致使selector空輪詢,致使CPU 100%,該問題到JDK1.7版本仍是一直存在。 * </li> * * 功能:nio服務端 * 版本:1.0 * 日期:2016/12/21 11:38 * 做者:馟蘇 */ public class NioServer { // 本地字符集 private static final String LocalCharsetName = "gbk"; // 緩衝區大小 private static final int Buffer_Size=1024; /** * 主函數 * @param args */ public static void main(String []args) throws Exception { Selector selector = null; ServerSocketChannel serverSocketChannel = null; try { selector = Selector.open(); // 開啓IO多路複用輪詢 serverSocketChannel = ServerSocketChannel.open(); // 打開服務器channel serverSocketChannel.configureBlocking(false); // 配置爲非阻塞 serverSocketChannel.socket().bind(new InetSocketAddress(8888)); // 綁定服務器本地地址和端口 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); // 在多路複用輪詢器上註冊操做 System.out.println("服務器在8888端口監聽"); while (selector.select() > 0) { // 若是沒有事件發生,那麼在這裏阻塞 Iterator<SelectionKey> it = selector.selectedKeys().iterator(); // 獲取鍵的迭代器 while (it.hasNext()) { SelectionKey key = it.next(); it.remove(); // 移除key,防止屢次遍歷 try { /** * 處理 */ handler(key); } catch (Exception e) { e.printStackTrace(); // 打印遠程客戶端的ip SocketChannel channel = (SocketChannel) key.channel(); System.out.println(channel.socket().getRemoteSocketAddress()); // 取消註冊,關閉客戶端的channel key.cancel(); closeConnects(key.channel()); } System.out.println("處理結束"); } } } catch (Exception e) { e.printStackTrace(); } finally { try { closeConnects(serverSocketChannel, selector); } catch (Exception e) { e.printStackTrace(); } } } /** * 處理 * @param key */ private static void handler(SelectionKey key) throws Exception { if(key.isAcceptable()) { // 客戶端鏈接到來 SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept(); clientChannel.configureBlocking(false); // 客戶端註冊爲讀事件而且設置與該鍵關聯的附加對象 clientChannel.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(Buffer_Size)); } else if(key.isReadable()) { // 收到客戶端消息 // 得到與客戶端通訊的信道 SocketChannel clientChannel = (SocketChannel) key.channel(); System.out.println(key.hashCode()); // 獲取此鍵的附加對象 ByteBuffer buffer = (ByteBuffer) key.attachment(); buffer.clear(); // 讀取信息得到讀取的字節數 long bytesRead = clientChannel.read(buffer); if(bytesRead == -1) { // 打印遠程客戶端的ip SocketChannel channel = (SocketChannel) key.channel(); System.out.println(channel.socket().getRemoteSocketAddress()); // 取消註冊,關閉客戶端的channel key.cancel(); closeConnects(clientChannel); } else { // 將緩衝區準備爲數據傳出狀態 buffer.flip(); // 將得到字節字符串(使用Charset進行解碼) String receivedString = Charset.forName(LocalCharsetName).newDecoder().decode(buffer).toString(); // 控制檯打印出來 System.out.println("接收到信息:" + receivedString); // 準備發送的文本 String sendString = "你好,客戶端. 已經收到你的信息" + receivedString; // 將要發送的字符串編碼(使用Charset進行編碼)後再進行包裝 buffer = ByteBuffer.wrap(sendString.getBytes(LocalCharsetName)); // 發送給客戶端 clientChannel.write(buffer); // 設置爲下一次讀取或是寫入作準備 // key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE); } } } /** * 關閉鏈接 * @param closeables */ public static void closeConnects(Closeable ...closeables) throws Exception { if (closeables == null) { return; } for (Closeable c : closeables) { c.close(); } } }
看完以後什麼感受,是否是特別噁心,對吧,寫個簡單的接受字符串程序寫了這麼多代碼,並且也沒有解決半包等問題,這豈不是要瘋的節奏,那麼如何解決這個問題?Netty這個猶如救世主的框架給咱們帶來了曙光!預知後事如何,待我下回分解。
參考書籍《netty權威指南》