Netty入門教程——認識Netty

Netty

什么是Netty伤疙?

Netty 是一個利用 Java 的高級網(wǎng)絡(luò)的能力悼吱,隱藏其背后的復(fù)雜性而提供一個易于使用的 API 的客戶端/服務(wù)器框架慎框。
Netty 是一個廣泛使用的 Java 網(wǎng)絡(luò)編程框架(Netty 在 2011 年獲得了Duke's Choice Award,見https://www.java.net/dukeschoice/2011)舆绎。它活躍和成長于用戶社區(qū)鲤脏,像大型公司 Facebook 和 Instagram 以及流行 開源項目如 Infinispan, HornetQ, Vert.x, Apache Cassandra 和 Elasticsearch 等,都利用其強大的對于網(wǎng)絡(luò)抽象的核心代碼吕朵。

以上是摘自《Essential Netty In Action》這本書猎醇,本文的內(nèi)容也是本人讀了這本書之后的一些整理心得,如有不當之處歡迎大蝦們指正

Netty和Tomcat有什么區(qū)別努溃?

Netty和Tomcat最大的區(qū)別就在于通信協(xié)議硫嘶,Tomcat是基于Http協(xié)議的,他的實質(zhì)是一個基于http協(xié)議的web容器梧税,但是Netty不一樣沦疾,他能通過編程自定義各種協(xié)議,因為netty能夠通過codec自己來編碼/解碼字節(jié)流第队,完成類似redis訪問的功能哮塞,這就是netty和tomcat最大的不同。

有人說netty的性能就一定比tomcat性能高凳谦,其實不然忆畅,tomcat從6.x開始就支持了nio模式,并且后續(xù)還有APR模式——一種通過jni調(diào)用apache網(wǎng)絡(luò)庫的模式尸执,相比于舊的bio模式家凯,并發(fā)性能得到了很大提高,特別是APR模式如失,而netty是否比tomcat性能更高绊诲,則要取決于netty程序作者的技術(shù)實力了。

為什么Netty受歡迎褪贵?

如第一部分所述掂之,netty是一款收到大公司青睞的框架,在我看來,netty能夠受到青睞的原因有三:

  1. 并發(fā)高
  2. 傳輸快
  3. 封裝好

Netty為什么并發(fā)高

Netty是一款基于NIO(Nonblocking I/O板惑,非阻塞IO)開發(fā)的網(wǎng)絡(luò)通信框架橄镜,對比于BIO(Blocking I/O,阻塞IO)冯乘,他的并發(fā)性能得到了很大提高洽胶,兩張圖讓你了解BIO和NIO的區(qū)別:

阻塞IO的通信方式

非阻塞IO的通信方式

從這兩圖可以看出,NIO的單線程能處理連接的數(shù)量比BIO要高出很多裆馒,而為什么單線程能處理更多的連接呢姊氓?原因就是圖二中出現(xiàn)的Selector
當一個連接建立之后喷好,他有兩個步驟要做翔横,第一步是接收完客戶端發(fā)過來的全部數(shù)據(jù),第二步是服務(wù)端處理完請求業(yè)務(wù)之后返回response給客戶端梗搅。NIO和BIO的區(qū)別主要是在第一步禾唁。
在BIO中,等待客戶端發(fā)數(shù)據(jù)這個過程是阻塞的无切,這樣就造成了一個線程只能處理一個請求的情況荡短,而機器能支持的最大線程數(shù)是有限的,這就是為什么BIO不能支持高并發(fā)的原因哆键。
而NIO中掘托,當一個Socket建立好之后,Thread并不會阻塞去接受這個Socket籍嘹,而是將這個請求交給Selector闪盔,Selector會不斷的去遍歷所有的Socket,一旦有一個Socket建立完成辱士,他會通知Thread泪掀,然后Thread處理完數(shù)據(jù)再返回給客戶端——這個過程是不阻塞的,這樣就能讓一個Thread處理更多的請求了颂碘。
下面兩張圖是基于BIO的處理流程和netty的處理流程异赫,輔助你理解兩種方式的差別:
BIO的處理流程

NIO的處理流程

除了BIO和NIO之外,還有一些其他的IO模型凭涂,下面這張圖就表示了五種IO模型的處理流程:


五種常見的IO模型
  • BIO,同步阻塞IO贴妻,阻塞整個步驟切油,如果連接少,他的延遲是最低的名惩,因為一個線程只處理一個連接澎胡,適用于少連接且延遲低的場景,比如說數(shù)據(jù)庫連接。
  • NIO攻谁,同步非阻塞IO稚伍,阻塞業(yè)務(wù)處理但不阻塞數(shù)據(jù)接收,適用于高并發(fā)且處理簡單的場景戚宦,比如聊天軟件个曙。
  • 多路復(fù)用IO,他的兩個步驟處理是分開的受楼,也就是說垦搬,一個連接可能他的數(shù)據(jù)接收是線程a完成的,數(shù)據(jù)處理是線程b完成的艳汽,他比BIO能處理更多請求猴贰。
  • 信號驅(qū)動IO,這種IO模型主要用在嵌入式開發(fā)河狐,不參與討論米绕。
  • 異步IO,他的數(shù)據(jù)請求和數(shù)據(jù)處理都是異步的馋艺,數(shù)據(jù)請求一次返回一次栅干,適用于長連接的業(yè)務(wù)場景。

以上摘自Linux IO模式及 select丈钙、poll非驮、epoll詳解

Netty為什么傳輸快

Netty的傳輸快其實也是依賴了NIO的一個特性——零拷貝。我們知道雏赦,Java的內(nèi)存有堆內(nèi)存劫笙、棧內(nèi)存和字符串常量池等等,其中堆內(nèi)存是占用內(nèi)存空間最大的一塊星岗,也是Java對象存放的地方填大,一般我們的數(shù)據(jù)如果需要從IO讀取到堆內(nèi)存,中間需要經(jīng)過Socket緩沖區(qū)俏橘,也就是說一個數(shù)據(jù)會被拷貝兩次才能到達他的的終點允华,如果數(shù)據(jù)量大,就會造成不必要的資源浪費寥掐。
Netty針對這種情況靴寂,使用了NIO中的另一大特性——零拷貝,當他需要接收數(shù)據(jù)的時候召耘,他會在堆內(nèi)存之外開辟一塊內(nèi)存百炬,數(shù)據(jù)就直接從IO讀到了那塊內(nèi)存中去,在netty里面通過ByteBuf可以直接對這些數(shù)據(jù)進行直接操作污它,從而加快了傳輸速度剖踊。
下兩圖就介紹了兩種拷貝方式的區(qū)別庶弃,摘自Linux 中的零拷貝技術(shù),第 1 部分

傳統(tǒng)數(shù)據(jù)拷貝

零拷貝

上文介紹的ByteBuf是Netty的一個重要概念德澈,他是netty數(shù)據(jù)處理的容器歇攻,也是Netty封裝好的一個重要體現(xiàn),將在下一部分做詳細介紹梆造。

為什么說Netty封裝好缴守?

要說Netty為什么封裝好,這種用文字是說不清的澳窑,直接上代碼:

  • 阻塞I/O
public class PlainOioServer {

    public void serve(int port) throws IOException {
        final ServerSocket socket = new ServerSocket(port);     //1
        try {
            for (;;) {
                final Socket clientSocket = socket.accept();    //2
                System.out.println("Accepted connection from " + clientSocket);

                new Thread(new Runnable() {                        //3
                    @Override
                    public void run() {
                        OutputStream out;
                        try {
                            out = clientSocket.getOutputStream();
                            out.write("Hi!\r\n".getBytes(Charset.forName("UTF-8")));                            //4
                            out.flush();
                            clientSocket.close();                //5

                        } catch (IOException e) {
                            e.printStackTrace();
                            try {
                                clientSocket.close();
                            } catch (IOException ex) {
                                // ignore on close
                            }
                        }
                    }
                }).start();                                        //6
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 非阻塞IO
public class PlainNioServer {
    public void serve(int port) throws IOException {
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);
        ServerSocket ss = serverChannel.socket();
        InetSocketAddress address = new InetSocketAddress(port);
        ss.bind(address);                                            //1
        Selector selector = Selector.open();                        //2
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);    //3
        final ByteBuffer msg = ByteBuffer.wrap("Hi!\r\n".getBytes());
        for (;;) {
            try {
                selector.select();                                    //4
            } catch (IOException ex) {
                ex.printStackTrace();
                // handle exception
                break;
            }
            Set<SelectionKey> readyKeys = selector.selectedKeys();    //5
            Iterator<SelectionKey> iterator = readyKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();
                try {
                    if (key.isAcceptable()) {                //6
                        ServerSocketChannel server =
                                (ServerSocketChannel)key.channel();
                        SocketChannel client = server.accept();
                        client.configureBlocking(false);
                        client.register(selector, SelectionKey.OP_WRITE |
                                SelectionKey.OP_READ, msg.duplicate());    //7
                        System.out.println(
                                "Accepted connection from " + client);
                    }
                    if (key.isWritable()) {                //8
                        SocketChannel client =
                                (SocketChannel)key.channel();
                        ByteBuffer buffer =
                                (ByteBuffer)key.attachment();
                        while (buffer.hasRemaining()) {
                            if (client.write(buffer) == 0) {        //9
                                break;
                            }
                        }
                        client.close();                    //10
                    }
                } catch (IOException ex) {
                    key.cancel();
                    try {
                        key.channel().close();
                    } catch (IOException cex) {
                        // 在關(guān)閉時忽略
                    }
                }
            }
        }
    }
}
  • Netty
public class NettyOioServer {

    public void server(int port) throws Exception {
        final ByteBuf buf = Unpooled.unreleasableBuffer(
                Unpooled.copiedBuffer("Hi!\r\n", Charset.forName("UTF-8")));
        EventLoopGroup group = new OioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();        //1

            b.group(group)                                    //2
             .channel(OioServerSocketChannel.class)
             .localAddress(new InetSocketAddress(port))
             .childHandler(new ChannelInitializer<SocketChannel>() {//3
                 @Override
                 public void initChannel(SocketChannel ch) 
                     throws Exception {
                     ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {            //4
                         @Override
                         public void channelActive(ChannelHandlerContext ctx) throws Exception {
                             ctx.writeAndFlush(buf.duplicate()).addListener(ChannelFutureListener.CLOSE);//5
                         }
                     });
                 }
             });
            ChannelFuture f = b.bind().sync();  //6
            f.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully().sync();        //7
        }
    }
}

從代碼量上來看斧散,Netty就已經(jīng)秒殺傳統(tǒng)Socket編程了,但是這一部分博大精深摊聋,僅僅貼幾個代碼豈能說明問題鸡捐,在這里給大家介紹一下Netty的一些重要概念,讓大家更理解Netty麻裁。

  • Channel
    數(shù)據(jù)傳輸流箍镜,與channel相關(guān)的概念有以下四個,上一張圖讓你了解netty里面的Channel煎源。


    Channel一覽
    • Channel色迂,表示一個連接,可以理解為每一個請求手销,就是一個Channel歇僧。
    • ChannelHandler,核心處理業(yè)務(wù)就在這里锋拖,用于處理業(yè)務(wù)請求诈悍。
    • ChannelHandlerContext,用于傳輸業(yè)務(wù)數(shù)據(jù)兽埃。
    • ChannelPipeline侥钳,用于保存處理過程需要用到的ChannelHandler和ChannelHandlerContext。
  • ByteBuf
    ByteBuf是一個存儲字節(jié)的容器柄错,最大特點就是使用方便舷夺,它既有自己的讀索引和寫索引,方便你對整段字節(jié)緩存進行讀寫售貌,也支持get/set给猾,方便你對其中每一個字節(jié)進行讀寫,他的數(shù)據(jù)結(jié)構(gòu)如下圖所示:
ByteBuf數(shù)據(jù)結(jié)構(gòu)

他有三種使用模式:

  1. Heap Buffer 堆緩沖區(qū)
    堆緩沖區(qū)是ByteBuf最常用的模式颂跨,他將數(shù)據(jù)存儲在堆空間敢伸。
  2. Direct Buffer 直接緩沖區(qū)
    直接緩沖區(qū)是ByteBuf的另外一種常用模式,他的內(nèi)存分配都不發(fā)生在堆毫捣,jdk1.4引入的nio的ByteBuffer類允許jvm通過本地方法調(diào)用分配內(nèi)存详拙,這樣做有兩個好處
    • 通過免去中間交換的內(nèi)存拷貝, 提升IO處理速度; 直接緩沖區(qū)的內(nèi)容可以駐留在垃圾回收掃描的堆區(qū)以外。
    • DirectBuffer 在 -XX:MaxDirectMemorySize=xxM大小限制下, 使用 Heap 之外的內(nèi)存, GC對此”無能為力”,也就意味著規(guī)避了在高負載下頻繁的GC過程對應(yīng)用線程的中斷影響.
  3. Composite Buffer 復(fù)合緩沖區(qū)
    復(fù)合緩沖區(qū)相當于多個不同ByteBuf的視圖蔓同,這是netty提供的饶辙,jdk不提供這樣的功能。

除此之外斑粱,他還提供一大堆api方便你使用弃揽,在這里我就不一一列出了,具體參見ByteBuf字節(jié)緩存则北。

  • Codec
    Netty中的編碼/解碼器矿微,通過他你能完成字節(jié)與pojo、pojo與pojo的相互轉(zhuǎn)換尚揣,從而達到自定義協(xié)議的目的涌矢。
    在Netty里面最有名的就是HttpRequestDecoder和HttpResponseEncoder了。

Netty入門教程2——動手搭建HttpServer
Netty入門教程3——Decoder和Encoder
Netty入門教程4——如何實現(xiàn)長連接

以上就是我對《Netty實戰(zhàn)》這本書的一些心得和書外的一些相關(guān)知識整理快骗,如果有不同的見解娜庇,歡迎討論!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末方篮,一起剝皮案震驚了整個濱河市名秀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌藕溅,老刑警劉巖匕得,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異巾表,居然都是意外死亡汁掠,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門攒发,熙熙樓的掌柜王于貴愁眉苦臉地迎上來调塌,“玉大人,你說我怎么就攤上這事惠猿「崂” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵偶妖,是天一觀的道長姜凄。 經(jīng)常有香客問我,道長趾访,這世上最難降的妖魔是什么态秧? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮扼鞋,結(jié)果婚禮上申鱼,老公的妹妹穿的比我還像新娘愤诱。我一直安慰自己,他們只是感情好捐友,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布淫半。 她就那樣靜靜地躺著,像睡著了一般匣砖。 火紅的嫁衣襯著肌膚如雪科吭。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天猴鲫,我揣著相機與錄音对人,去河邊找鬼。 笑死拂共,一個胖子當著我的面吹牛牺弄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播宜狐,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼猖闪,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了肌厨?” 一聲冷哼從身側(cè)響起培慌,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎柑爸,沒想到半個月后吵护,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡表鳍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年馅而,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片譬圣。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡瓮恭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出厘熟,到底是詐尸還是另有隱情屯蹦,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布绳姨,位于F島的核電站登澜,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏飘庄。R本人自食惡果不足惜脑蠕,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望跪削。 院中可真熱鬧谴仙,春花似錦迂求、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至哼审,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間孕豹,已是汗流浹背涩盾。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留励背,地道東北人春霍。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像叶眉,于是被迫代替她去往敵國和親址儒。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 作者:李林鋒 原文:http://www.infoq.com/cn/articles/netty-high-per...
    楊鑫科閱讀 3,976評論 0 64
  • 前言 本篇文章主要分析Netty的系統(tǒng)結(jié)構(gòu)以及其如何實現(xiàn)其對外宣稱的特色衅疙,如果還未了解Netty的基礎(chǔ)知識莲趣,最好先...
    簡xiaoyao閱讀 4,898評論 0 13
  • 一陣電閃雷鳴之后 大雨噼里啪啦落下 沖洗著大地 空氣中多了一份清涼 雨水在歡歌 沖刷的聲音漸漸輕柔了 撲滅了人們心...
    泡泡裟閱讀 494評論 0 4
  • 一個朋友問我: 想了解一下做一個 APP 要多少成本喧伞? ?單單就這一句話,包含的信息很少绩郎,這點信息潘鲫,估不出成本,想...
    yuzebin閱讀 1,361評論 0 0
  • 2019年6月23日 親子日記第十七篇 丫丫小時候是姥姥一手帶大的肋杖,所以丫丫一直感覺姥姥做的飯菜最符合她的口...
    壹壹Love媽媽閱讀 392評論 0 6