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.其余字母可以字母,下