1. 程式人生 > >Netty4實戰第二章:第一個Netty應用

Netty4實戰第二章:第一個Netty應用

3.2、實現業務邏輯

  Netty使用上一章介紹的Future、回撥等技術,再加上其他設計,用來響應不同型別的事件。這些後面再討論,目前我們的焦點是收到資料後下一步怎麼做。想要處理收到的資料,必須編寫一個類繼承ChannelInboundHandlerAdapter類,並且重寫messageReceived方法。這個方法會在每次收到資料後呼叫,這個例子裡我們收到的是位元組陣列。EchoServer這裡要做的就是在這個方法裡將收到的內容重新發送給客戶端,請看下面的程式碼。

package com.nan.netty.one;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

import java.nio.charset.Charset;

//Sharable註解可以讓此Handler在不同Channel之間共享
@ChannelHandler.Sharable
public class EchoServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        //Netty的ByteBuf轉成字串
        ByteBuf byteBuf = (ByteBuf) msg;
        System.out.println("Server received: " + byteBuf.getCharSequence(0, byteBuf.readableBytes(), Charset.forName("UTF-8")));
        //收到資料並將資料傳送到客戶端,但是此時資料還沒有刷到對等連線點
        ctx.write(msg);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        //讀操作完成後,將所有寫入的資料(等待中的)重新整理到對等連線點,然後關閉此Channel
        ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        //出現異常關閉此Channel
        ctx.close();
    }
}

  現在我們先使用第一章寫的BIO的EchoClient連線上面的服務端,可以發現EchoServer的功能已經實現了。

  Netty使用的Handler有更大的解耦性,所以非常容易進行增加,修改或刪除業務邏輯的專案演進。Handler的作用是很明確的,裡面的每個方法都可以重寫以參與到收發的資料的生命週期,但是一般情況下只有channelRead方法是需要重寫的。

3.3、攔截異常

  除了重寫了channelRead方法之外,有一個exceptionCaught方法也被重寫了。這個方法是用來處理異常的,或者其他Throwable的子類。上面的例子中,記錄了異常並關閉掉到客戶端的連線,因為出現異常這個連結的狀態可能是不確定的。一般情況下可能你也會這麼幹,不過有一些場景,需要從錯誤中恢復過來,這就需要發揮你的指揮找到一個聰明的方法去實現。重要的就是你要有一個ChannelHandler實現exceptionCaught方法來處理各種各樣的錯誤。


  Netty提供的這個攔截異常的方法更容易處理不同執行緒發生的錯誤。通過統一的簡單、集中API將不同執行緒發生的錯誤儘可能放在一起處理。

  如果你需要用Netty寫一個實際生產的應用或者一個框架就要知道很多其他ChannelHandler的子類和實現,這些後面會討論。現在只需要記住ChannelHandler的實現會在發生不同的事件呼叫,你可以繼承或實現它們來參與事件的生命週期。

四、編寫EchoClient

  上面我們寫了很多版本的EchoServer,不過客戶端始終只有BIO版本的,現在我們就編寫一個基於Netty的客戶端。   客戶端的程式碼主要包含以下邏輯:
  • 連線服務端
  • 傳送資料到服務端
  • 等待服務端返回相同的資料
  • 關閉連線
  清楚了要做什麼,現在就來實現客戶端的邏輯。

4.1、客戶端啟動器

  客戶端的啟動器和服務端的啟動器很類似。有一點區別,伺服器啟動器一般只需要埠號,而客戶端需要服務端的IP地址和埠,因為它要去連線這個IP和埠。

package com.nan.netty.one;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

import java.net.InetSocketAddress;

public class EchoClient {
    private final String host;
    private final int port;

    public EchoClient(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public void start() throws Exception {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            //建立客戶端啟動器例項
            Bootstrap b = new Bootstrap();
            //使用NioEventLoopGroup接收事件,和服務端一樣,這裡使用NIO傳輸
            b.group(group)
                    .channel(NioSocketChannel.class)
                    .remoteAddress(new InetSocketAddress(host, port))
                    //指定ChannelHandler,一旦連線簡歷就會建立Channel
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            //指定EchoClientHandler處理業務邏輯
                            ch.pipeline().addLast(new EchoClientHandler());
                        }
                    });
            //同步連線服務端
            ChannelFuture f = b.connect().sync();
            //阻塞程式碼只到客戶端關閉
            f.channel().closeFuture().sync();
        } finally {
            //關閉啟動器並釋放資源
            group.shutdownGracefully().sync();
        }
    }

    public static void main(String[] args) throws Exception {
        new EchoClient("localhost", 9999).start();
    }
}
  這裡我們用的還是NIO傳輸。這裡先提一下,暫時也不用關係你用的什麼傳輸方式;客戶端和服務端也可以使用不同的傳輸方式。有些開發者就是在服務端使用NIO傳輸並在客戶端使用OIO傳輸。我們將會在第四章學習到底什麼是傳輸方式。

  我們列一下客戶端的重點:

  • 建立一個啟動器例項啟動客戶端
  • 使用NioEventLoopGroup處理事件,包括新連線、讀寫資料等
  • InetSocketAddress裡面的地址和埠就是要連線的服務端的地址和埠
  • 還註冊了一個Handler作為連線成功後的回撥,目前還沒寫這個Handler,所以程式碼會在IDE裡報錯
  • 上面的步驟完成之後,呼叫ServerBootstrap.connect()方法去連線服務端

4.2、客戶端業務邏輯

  這裡實現的客戶端邏輯儘量簡單,裡面用到的一些類的細節會在後面的章節詳解。

  客戶端的Handler首先繼承了SimpleChannelInboundHandler,然後重寫了下面三個方法,這三個方法對我們現在而言都是有用的。

  • channelActive()- 連線服務端成功後回撥
  • channelRead0()- 從服務端收到資料後回撥
  • exceptionCaught()- 出現異常時回撥
package com.nan.netty.one;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;

//不同Channel之間共享此Handler
@ChannelHandler.Sharable
public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        //連線成功後向服務端傳送資料
        ctx.writeAndFlush(Unpooled.copiedBuffer("Hello server 110", CharsetUtil.UTF_8));
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
        //列印從服務端收到的資料,然後關閉連線
        System.out.println("Received from server: " + msg.getCharSequence(0, msg.readableBytes(), CharsetUtil.UTF_8));
        ctx.close();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        //列印遇到的異常,然後關閉連線
        cause.printStackTrace();
        ctx.close();
    }
}
  這裡重寫的三個方法在一般應用中都是有必要重寫的。
  一旦連線成功就會回撥channelActive()。上面的例子很簡單,連線成功後向服務端傳送一串字元。傳送的內容目前不重要,目前這個不是重點。重寫這個方法就是保證儘快將資訊傳送給服務端。
  下一個重寫的方法是channelRead0()。一旦接收到資訊就會回撥這個方法。這裡要注意訊息內容可能會分片,比如服務端傳送5個位元組給客戶端但是客戶端並不是一下子把5個位元組全部接收了。例如,5個位元組,可能一次只能收3個,也就是說這個方法第一接收3個位元組,第二次接收2個位元組,所以這個方法會回撥2次。唯一能保證的就是接收順序與傳送順序是一致的,不過這也僅適用於TCP或其他可靠協議。
  第三個重寫的方法是exceptionCaught()。基本上和服務端的一樣。記錄異常並關閉連線。
  這裡大家可能會有個疑問,為什麼客戶端用的是SimpleChannelInboundHandler,而服務端用的是ChannelInboundHandlerAdapter。使用ChannelInboundHandlerAdapter的主要場景是處理完接收到的訊息後要手動釋放資源。比如用ByteBuf可以呼叫ByteBuf.release()釋放資源。而使用SimpleChannelInboundHandler會在channelRead0(...)方法執行完後自動釋放資源,這個從SimpleChannelInboundHandler的原始碼可以很容易看出來。Netty的訊息類都通過實現ReferenceCounted介面去釋放資源的。   但是為什麼服務端就不需要釋放資源呢?這是因為服務端需要將訊息重新發送給客戶端,所以寫操作完成之前不能釋放資源,而且寫還是非同步。Netty會在寫操作完成之後自動釋放資源。   剛寫完服務端的時候,我們使用的是BIO的客戶端驗證的,現在可以用基於Netty的客戶端去驗證。直接在IDE中執行main方法,可以發現EchoServer的功能正常實現了,如果你執行過程中遇到什麼問題,請仔細檢查下自己的程式碼,或者加我的微信wangjinn一起討論,謝謝。

五、總結

這一章我們使用Netty實現了一個簡單的服務端和客戶端,對於啟動,配置,讀寫資料以及捕獲異常都有介紹,現在對於Netty算是有一些基本的瞭解,這些知識對於後面的學習是個很好的基礎。



相關推薦

Netty4實戰第二一個Netty應用

3.2、實現業務邏輯   Netty使用上一章介紹的Future、回撥等技術,再加上其他設計,用來響應不同型別的事件。這些後面再討論,目前我們的焦點是收到資料後下一步怎麼做。想要處理收到的資料,必須編寫一個類繼承ChannelInboundHandlerAdapter類,並且重寫messageReceive

SpringBoot第一一個 SpringBoot 應用

springboot簡單介紹 概述 隨著動態語言的流行(Ruby、Groovy、Scala、Node.js),Java的開發顯得格外的笨重:繁多的配置、低下的開發效率、複雜的部署流程以及第三方技術整合難度大。 在上述環境下,Springboot應運而生。它使用”習慣優於配置”(專案中存在大量

SpringBoot | 第一一個SpringBoot應用

概述 隨著動態語言的流行(Ruby、Groovy、Scala、Node.js),Java的開發顯得格外的笨重:繁多的配置、低下的開發效率、複雜的部署流程以及第三方技術整合難度大。 在上述環境下,Springboot應運而生。它使用”習慣優於配置”(專案中存在大量的配置

SpringBoot | 第一一個 SpringBoot 應用

來源:oKong , my.oschina.net/xiedeshou/blog/1853320 springboot簡單介紹 概述 隨著動態語言的流行(Ruby、Groovy、Scala、Node.js),Java的開發顯得格外的笨重:繁多的配置、低

Node入門教程(4)一個 Nodejs 程序

tps con javascrip 第三章 body linux 一定的 ava UC 第一個 Nodejs 程序 本教程僅適合您已經有一定的JS編程的基礎或者是後端語言開發的基礎。如果您是零基礎,建議您先學一下老馬的前端免費視頻教程 第一步:創建項目文件夾 首先創建

OpenGL學習——第二一個OpenGL程式

第一個OpenGL程式一個簡單的OpenGL程式如下:(注意,如果需要編譯並執行,需要正確安裝GLUT,安裝方法如第一課) //  OpenGl.c  #include <GL/glut.h>void myDisplay(void){glClear(GL_COLOR_BUFFER_BIT);gl

Netty實戰 02」手把手教你實現自己的一個 Netty 應用!新手也能搞懂!

[大家好,我是 **「後端技術進階」** 作者,一個熱愛技術的少年。](https://www.yuque.com/docs/share/aaa50642-c892-4c41-8c0c-9d2fc2b0d93c?#%20%E3%80%8A%E8%B5%B0%E8%BF%9B%E5%90%8E%E7%AB%AF

子雨大資料之Spark入門教程---Spark2.1.0入門一個Spark應用程式WordCount 2.2

前面已經學習了Spark安裝,完成了實驗環境的搭建,並且學習了Spark執行架構和RDD設計原理,同時,我們還學習了Scala程式設計的基本語法,有了這些基礎知識作為鋪墊,現在我們可以沒有障礙地開始編寫一個簡單的Spark應用程式了——詞頻統計。 任務要求 任務:

第二自動化測試框架Cucumber,Ruby實戰----環境搭建

1.配置Ruby環境 1.安裝JDK,並配置環境變數 2.Jruby-9.1.2.0 下載地址:https://pan.baidu.com/s/1kiDe_pkeVzqmViKihwx91A 將壓縮包下載到本地,並解壓。 配置環境變數: PATH=c:\jruby-9.1.2

《Kotlin實戰》學習筆記之第二Kotlin基礎

一、基本要素:函式和變數 1、Hello,world fun main(args: Array<Stirng>) { println("Hello, world!") } 關鍵字

機器學習實戰---讀書筆記 10 利用K均值聚類演算法對未標註資料分組---1

#!/usr/bin/env python # encoding: utf-8 import os from matplotlib import pyplot as plt from numpy import * ''' 讀書筆記之--<<機器學習實戰>>--第10章_

Netty實戰-一個Netty

      本部分將簡單介紹Netty的核心概念,核心概念就是學習Netty是如何攔截和處理異常。1 設定開發環境   設定開發環境的步驟包括如下三個部分:安裝jdk下載netty包安裝Ecplise2 Netty客戶端和伺服器概述    下面將構建一個完整的Netty伺服器

第二如何將一個類變成物件

使用物件的步驟: 1.建立物件 類名 物件名 = new 類名(); Telphone phone = new Telphone();(例項化) 2.使用物件 引用物件的屬性:物件名.屬性 phone.screen = 5; //給scree屬性賦值5 引用物件的方法: 物件

《機器學習實戰第二k-近鄰演算法(3)手寫數字識別

這是k-近鄰演算法的最後一個例子——手寫數字識別! 怎樣?是不是聽起來很高大上? 呵呵。然而這跟影象識別沒有半毛錢的關係 因為每個資料樣本並不是手寫數字的圖片,而是有由0和1組成的文字檔案,就像這樣:         嗯,這個資料集中的每一個樣本用圖形軟體處理過,變成了寬高

《Spring實戰》學習筆記-構建Spring web應用

之前一直在看《Spring實戰》第三版,看到第五章時發現很多東西已經過時被廢棄了,於是現在開始讀《Spring實戰》第四版了,章節安排與之前不同了,裡面應用的應該是最新的技術。 本章中,將會接觸到Spring MVC基礎,以及如何編寫控制器來處理web請求,如何通明地繫

《機器學習實戰第二k-近鄰演算法(1)簡單KNN

收拾下心情,繼續上路。 最近開始看Peter Harrington的《Machine Learning in Action》... 的中文版《機器學習實戰》。準備在部落格裡面記錄些筆記。 這本書附帶的程式碼和資料及可以在這裡找到。 這本書裡程式碼基本是用python寫的

《機器學習實戰第二k-近鄰演算法(2)約會物件分類

這是KNN的一個新例子。 在一個約會網站裡,每個約會物件有三個特徵: (1)每年獲得的飛行常客里程數(額...這個用來判斷你是不是成功人士?) (2)玩視訊遊戲所耗時間百分比(額...這個用來判斷你是不是肥宅?) (3)每週消費的冰激凌公升數(額...這個是何用意我真不知道

機器學習 第二模型評估與選擇-總結

但是 交叉 roc曲線 掃描 com ram hidden 技術分享 preview 1、數據集包含1000個樣本,其中500個正例,500個反例,將其劃分為包含70%樣本的訓練集和30%樣本的測試集用於留出法評估,試估算共有多少種劃分方式。 留出法將數據集劃分為兩個互斥的

【機器學習筆記】第二模型評估與選擇

機器學習 ini ppi 第二章 err cap ner rate rac 2.1 經驗誤差與過擬合 1. error rate/accuracy 2. error: training error/empirical error, generalization error

第二數據類型和運算符

取反 可能 tin 中間 接口 double類型 變量名 不能 修飾 第二章:數據類型和運算符 計算機中的進制 **標識符 總的命名規則:見名知意。如果有多個單詞組成,首單詞小寫,其余單詞的首字母大寫(駝峰命名法)。1.首字母只能是字母,下劃線和$2.其余字母可以字母,下