Netty就是這麼回事(一)

寫在開頭: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權威指南》

相關文章
相關標籤/搜索