一、什麼是Netty?
Netty是一個提供異步事件驅動(asynchronous event-driven)的網絡應用框架,是一個用於快速開發高性能、可擴展協議的服務器和客戶端。本質上,Netty就是一個NIO客戶端服務器框架,我們可以用它快速簡單地開發網絡應用程序。Netty大大簡化了網絡程序開發過程。
二、如何使用Netty?
(一)、建立一個Maven項目
建議使用JDK1.6以上版本,這個步驟我就不舉例了。假設我創建的項目名稱是Netty。
(二)、導入maven依賴
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.6.Final</version>
</dependency>
(三)、編寫我們的第一個Discard Server服務器
世上最簡單的協議不是’Hello, World!’ 而是 DISCARD(拋棄服務)。這個協議將會拋棄任何收到 的數據,而不響應。 爲了實現 DISCARD 協議,你只需忽略所有收到的數據。讓我們從 handler(處理器)的實現開始,handler 是由 Netty 生成用來處理 I/O 事件的。
代碼如下:
package com.netty;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
* Class DiscardServerHandler ...
*
* @author LiJun
* Created on 2018/8/15
* 處理服務端 channel
*/
public class DiscardServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// 丟棄收到的數據
((ByteBuf)msg).release();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
// 出現異常就關閉連接
cause.printStackTrace();
ctx.close();
}
}
步驟如下:
創建一個類繼承
ChannelInboundHandlerAdapter
,在這個例子上我們創建了一個DiscardServerHandler
繼承ChannelInboundHandlerAdapter
類,ChannelInboundHandlerAdapter
這個類實現了ChannelInboundHandler
接口(這個接口中提供了多種事件處理的接口方法)。我們覆蓋了
channelRead
方法,每當從客戶端接收到新的數據的時候,這個方法就會被調用。實現DISCARD協議,處理器需要忽略掉收到的信息。ByteBuf 是一個引用計數 對象,這個對象必須顯示地調用 release() 方法來釋放。
來一個
channelRead()
方法的模板吧:@Override public void channelRead(ChannelHandlerContext ctx, Object msg) { try { // Do something with msg } finally { ReferenceCountUtil.release(msg); } }
exceptionCaught()
事件處理方法是當出現Throwable
對象纔會被調用,即當 Netty 由於 IO 錯誤或者處理器在處理事件時拋出的異常時。在大部分情況下,捕獲的異常應該被記錄下來 並且把關聯的 channel 給關閉掉。然而這個方法的處理方式會在遇到不同異常的情況下有不 同的實現,比如你可能想在關閉連接之前發送一個錯誤碼的響應消息。
注:處理器的職責是釋放所有傳遞到處理器的引用計數對象。
(四)、啓動服務端的DiscardServerHandler
package com.netty;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* Class DiscardServer ...
*
* @author LiJun
* Created on 2018/8/15
*/
public class DiscardServer {
private int port;
public DiscardServer() {
}
public DiscardServer(int port) {
this.port = port;
}
public void run() throws Exception{
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) {
socketChannel.pipeline().addLast(new DiscardServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
// 綁定端口,並開始接收進來的連接
ChannelFuture channelFuture = b.bind(port).sync();
// 等待服務器,socket關閉
// 在這裏,不會發生什麼
channelFuture.channel().closeFuture().sync();
}finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int port;
if(args.length > 0){
port = Integer.parseInt(args[0]);
}else {
port = 8080;
}
new DiscardServer(port).run();
}
}
解釋:
NioEventLoopGroup
是用來處理I/O操作的多線程事件循環器,Netty 提供了許多不同的EventLoopGroup
的實現用來處理不同的傳輸。在這個例子中我們實現了一個服務端的應用, 因此會有2個NioEventLoopGroup
會被使用。第一個經常被叫做‘boss’
,用來接收進來的連 接。第二個經常被叫做‘worker’
,用來處理已經被接收的連接,一旦‘boss’接收到連接,就會 把連接信息註冊到‘worker’
上。如何知道多少個線程已經被使用,如何映射到已經創建的Channel
上都需要依賴於EventLoopGroup
的實現,並且可以通過構造函數來配置他們的關係。ServerBootstrap
是一個啓動 NIO 服務的輔助啓動類。你可以在這個服務中直接使用Channel
,但是這會是一個複雜的處理過程,在很多情況下你並不需要這樣做。這裏我們指定使用
NioServerSocketChannel
類來舉例說明一個新的Channel
如何接收進來的連接。這裏的事件處理類經常會被用來處理一個最近的已經接收的
Channel
。ChannelInitializer
是 一個特殊的處理類,他的目的是幫助使用者配置一個新的Channel
。也許你想通過增加一些 處理類比如DiscardServerHandler
來配置一個新的Channel
或者其對應的ChannelPipeline
來實現你的網絡程序。當你的程序變的複雜時,可能你會增加更多的處理類到pipeline
上,然後提取這些匿名類到最頂層的類上。你可以設置這裏指定的
Channel
實現的配置參數。我們正在寫一個TCP/IP 的服務端,因此 我們被允許設置socket
的參數選項比如tcpNoDelay
和keepAlive
。你關注過
option()
和childOption()
嗎?option()
是提供給NioServerSocketChannel
用來接 收進來的連接。childOption()
是提供給由父管道ServerChannel
接收到的連接,在這個例子 中也是NioServerSocketChannel
。我們繼續,剩下的就是綁定端口然後啓動服務。這裏我們在機器上綁定了機器所有網卡上的 8080 端口。當然現在你可以多次調用 bind() 方法(基於不同綁定地址) .
(五)、測試
先運行main方法哦
爲了我們便於觀察到結果,
我們將channelRead()
方法內容改一下
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf in = (ByteBuf)msg;
try{
while(in.isReadable()){
System.out.print((char) in.readByte());
System.out.flush();
}
}finally {
ReferenceCountUtil.release(msg);
}
}
1. 介紹下telnet
Telnet協議是TCP/IP協議族中的一員,是Internet遠程登陸服務的標準協議和主要方式。它爲用戶提供了在本地計算機上完成遠程主機工作的能力。在終端使用者的電腦上使用telnet程序,用它連接到服務器。終端使用者可以在telnet程序中輸入命令,這些命令會在服務器上運行,就像直接在服務器的控制檯上輸入一樣。可以在本地就能控制服務器。要開始一個telnet會話,必須輸入用戶名和密碼來登錄服務器。Telnet是常用的遠程控制Web服務器的方法。
2. win10如何開啓telnet
控制面板 -> 程序 -> 啓用或關閉Windows功能 -> 找到Telnet客戶端勾選上,然後點確定。
打開cmd,輸入Telnet
我們可以輸入?/help
查看幫助
3.連接
輸入 open localhost 8080
顯示正在連接localhost…就表示連接上了
當我們在telnet輸入內容的時候就能在idea的控制檯山看到響應的信息了。
三、利用Netty編寫一個Echo(應答)服務器
把channelRead()
方法內容改成如下:
ctx.writeAndFlush(msg);
這個表示把傳入的數據傳回去,並顯示。
writeAndFlush(msg);
等於 ctx.write(msg)+ctx.flush()
調用wirte
方法不會使消息寫入到通道上,它被緩衝在了內部,我們必須要調用ctx.flush()
方法把緩衝區的數據強行輸出。
四、利用Netty編寫一個Time(時間) 服務器
TimeServerHandler類
如下:
package com.netty;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
* Class TimeServerHandler ...
*
* @author LiJun
* Created on 2018/8/15
*/
public class TimeServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
final ByteBuf time = ctx.alloc().buffer(4);
time.writeInt((int)(System.currentTimeMillis()/1000L+220898800L));
final ChannelFuture f = ctx.writeAndFlush(time);
f.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
assert f == channelFuture;
ctx.close();
}
});
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
// 出現異常就關閉連接
cause.printStackTrace();
ctx.close();
}
}
解釋:
我們表示的是在連接被創建的時候發送一個消息,所以使用的是
channelActive()
方法channelActive()
方法將會在連接被建立並且準備進行通信時被調用。因此讓我們在這個 法裏完成一個代表當前時間的32位整數消息的構建工作。爲了發送一個新的消息,我們需要分配一個包含這個消息的新的緩衝。因爲我們需要寫入一 個32位的整數,因此我們需要一個至少有4個字節的 ByteBuf。通過
ChannelHandlerContext.alloc()
得到一個當前的ByteBufAllocator
,然後分配一個新的緩衝。在這我們不需要
flip()
方法了,ByteBuf中沒有這個方法,它含有兩個指針,一個對應讀操作,一個對應寫操作。當我們想ByteBuf中寫入數據的時候,寫指針的索引就會增加,讀指針不變,讀的時候讀指針變化,寫指針不變。所以讀指針索引和寫指針所以分別代表了消息的開始和消息和結束。注意:
ChannelHandlerContext.write()
和writeAndFlush()
方法會返回一 個ChannelFuture
對象,一個ChannelFuture
代表了一個還沒有發生的 I/O 操作。這意味着 任何一個請求操作都不會馬上被執行,因爲在 Netty 裏所有的操作都是異步的。所以我們需要在write方法
返回的ChannelFuture
完成後調用close方法
,然後當他的寫操 作已經完成他會通知他的監聽者。請注意,close()
方法也可能不會立馬關閉,他也會返回一個ChannelFuture
。當一個寫請求已經完成是如何通知到我們?這個只需要簡單地在返回的
ChannelFuture
上 增加一個ChannelFutureListener
。這裏我們構建了一個匿名的ChannelFutureListener
類用來 在操作完成時關閉Channel
。 或者,你可以使用簡單的預定義監聽器代碼:f.addListener(ChannelFutureListener.CLOSE);
爲了測試我們的time服務如我們期望的一樣工作,你可以使用 UNIX 的 rdate 命令
$ rdate -o <port> -p <host>
Port 是你在main()函數中指定的端口,host 使用 localhost 就可以了。
五、時間客戶端
在Netty中,編寫服務端和客戶端唯一也是最大的不同就是使用了不同的BootStrap
和Channel
的實現。
先看一下代碼吧:
TimeClient
“`java
package com.netty;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
/**
* Class TimeClient …
*
* @author LiJun
* Created on 2018/8/27
*/
public class TimeClient {
public static void main(String[] args) {
//String host = args[0];
//int port = Integer.parseInt(args[1]);
String host = "localhost";
int port = 8080;
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(workerGroup);
bootstrap.channel(NioSocketChannel.class);
bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new TimeDecoder(), new TimeClientHandler());
}
});
//啓動客戶端
ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
//等待關閉
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
workerGroup.shutdownGracefully();
}
}
}
“`
解釋下:
BootStrap
和ServerBootStrap
類似,但是它針對的是非服務端的channel
,例如客戶端或者無連接傳輸模式的channel
。- 指定了
EventLoopGroup
,那麼這既是一個boss group
,也是worker group
,儘管客戶端不需要boss worker
NioSocketChannel
這個類在客戶端channel
被創建時使用。- 不需要像使用
ServerBootStrap
一樣使用childOption()
方法,因爲客戶端的SocketChannel
沒有父親。 我們用
connect()
方法代替了bind()方法。最後我們需要一個
TimeClientHandler
類來處理信息,翻譯時間。
TimeClientHandler
“`java
package com.netty;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.util.Date;
/**
* Class TimeClientHandler …
*
* @author LiJun
* Created on 2018/8/27
*/
public class TimeClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf byteBuf = (ByteBuf)msg;
try {
long currentTimeMillis = (byteBuf.readUnsignedInt() - 2208988800L)*1000L;
System.out.println(new Date(currentTimeMillis));
ctx.close();
}finally {
byteBuf.release();
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
“`
解釋下:
在TCP/IP中,Netty會把讀到的數據放到ByteBuf這個數據結構中。
啓動時間服務器:
TimeServer
“`java
package com.netty;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* Class TimeServer …
*
* @author LiJun
* Created on 2018/8/15
*/
public class TimeServer{
private int port;
public TimeServer() {
}
public TimeServer(int port) {
this.port = port;
}
public void run() throws Exception{
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) {
socketChannel.pipeline().addLast(new DiscardServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
// 綁定端口,並開始接收進來的連接
ChannelFuture channelFuture = b.bind(port).sync();
// 等待服務器,socket關閉
// 在這裏,不會發生什麼
channelFuture.channel().closeFuture().sync();
}finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int port;
if(args.length > 0){
port = Integer.parseInt(args[0]);
}else {
port = 8080;
}
new TimeServer(port).run();
}
}
“`
六、處理一個基於流的傳輸
注意事項:基於流的傳輸(例如TCP/IP)接收到的數據是存在於socket接收的buffer中的,而且這個基於流的傳輸不是數據包隊列,而是字節隊列,所以不能保證遠程寫入數據的準確讀取。由於這個原因,接收部分需要將接收的數據進行碎片整理,整理成一個或多個可以被應用邏輯輕鬆識別的有意義框架。
例如:
- 遠程傳輸的數據爲 ABC DEF GHI
- 沒處理接收到的數據就可能爲 AB CDEFG H I 因爲是字節,數據錯亂了
- 我們需要將數據處理成 ABC DEF GHI 這種能被識別的數據。
由此:
(一)、解決方法一
以TIME客戶端爲例。32位整型是非常小的數據,可能會被拆分到不同的數據段內,並且,碎片化的可能性會隨着流量的增加而增加。所以我們可以構造一個內部的可積累的緩衝,知道4個字節全部接收到了內部緩衝區。
修改後的TimeClientHandler:
package com.netty;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.util.Date;
/**
* Class TimeClientHandler2 ...
*
* @author LiJun
* Created on 2018/8/27
*/
public class TimeClientHandler2 extends ChannelInboundHandlerAdapter {
private ByteBuf byteBuf;
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
byteBuf = ctx.alloc().buffer(4);
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
byteBuf.release();
byteBuf = null;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf m = (ByteBuf)msg;
byteBuf.writeBytes(m);
m.release();
if(byteBuf.readableBytes() >= 4){
long currentTimeMills = (byteBuf.readUnsignedInt() - 2208988800L)*1000L;
System.out.println(new Date(currentTimeMills));
ctx.close()
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
解釋下:
ChannelHandler
有兩個生命週期的監聽方法:handlerAdded()和handlerRemoved()
。我們可以完成任意初始化。- 所有接收的數據都被積累在了
byteBuf
變量中。 - 處理器需要檢查byteBuf照片那個是否有足夠數據,如果有,則進行邏輯處理,否則就重複調用
channelRead()
方法獲得更多數據,知道數據夠了爲止。
(二)、解決方法二
上面的方法維護起來麻煩。我們可以吧ChannelHandler
拆分成多個模塊以減少應用的複雜度。
我們可以把上面的TimeClientHandler
拆分成;兩個處理器:
TimeDecoder
處理數據拆分的問題TimeClientHandler
原始版本的實現。
這就要用到Netty提供的可擴展類了:ByteToMessageDecoder
TimeDecoder
package com.netty;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.util.List;
/**
* Class TimeDecoder ...
*
* @author LiJun
* Created on 2018/8/27
*/
public class TimeDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
if(byteBuf.readableBytes() < 4){
return;
}
list.add(byteBuf.readBytes(4));
}
}
解釋下:
- ByteToMessageDecoder是
ChannelInboundHandler
的· 一個實現可,可以吧處理數據拆分的問題變得簡單。 - 每當有新數據接收的時候,ByteToMessageDecoder都會調用
decode()
方法來處理內部的累積緩衝byteBuf
。 decode()
方法可以決定累積緩衝裏有沒有足夠數據可以往out對象裏放任意數據。當有更多的數據被接收了,ByteToMessageDecoder會再次調用decode()方法。- 如果在
decode()
方法裏增加了一個對象到 out 對象裏,這意味着解碼器解碼消息成功。 ByteToMessageDecoder 將會丟棄在累積緩衝裏已經被讀過的數據。我們不需要對多條消息調用 decode(),ByteToMessageDecoder 會持續調用 decode() 直到不放任何數據到 out 裏。
修改TImeClient的ChannelInitializer實現:
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new TimeDecoder(), new TimeClientHandler());
}
});
這還有一個解碼器:ReplayingDecoder,需要去了解API獲取更多信息。
package com.netty;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ReplayingDecoder;
import java.util.List;
/**
* Class TimeDecoder2 ...
*
* @author LiJun
* Created on 2018/8/27
*/
public class TimeDecoder2 extends ReplayingDecoder<Void> {
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
list.add(byteBuf.readBytes(4));
}
}
Netty還提供了不少開箱即用的解碼器
io.netty.example.factorial
基於二進制協議io.netty.example.telnet
基於文本協議
七、用POJO代替ByteBuf
在此之前我們都是使用ByteBu作爲協議消息的數據結構。我們接下來就要使用POJO代替ByteBuf。
這樣做的好處:
- 通過從ChannelHandler中提取出ByteBuf的代碼,使ChannelHandler的實現變得更加可維護和可重用。
我們改善我們的時間客戶端:
首先我們定義一個新的類:UnixTime
package com.netty;
import java.util.Date;
/**
* Class UnixTime ...
*
* @author LiJun
* Created on 2018/8/27
*/
public class UnixTime {
private final long value;
public UnixTime() {
this(System.currentTimeMillis()/1000L + 2208988800L);
}
public UnixTime(long value) {
this.value = value;
}
public long getValue() {
return value;
}
@Override
public String toString() {
return new Date((getValue() - 2208988800L)*1000L).toString();
}
}
修改TimeDecoder類,返回UnixTime:
package com.netty;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.util.List;
/**
* Class TimeDecoder3 ...
*
* @author LiJun
* Created on 2018/8/27
*/
public class TimeDecoder3 extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
if(byteBuf.readableBytes() < 4){
return;
}
list.add(new UnixTime(byteBuf.readUnsignedInt()));
}
}
修改TimeClientHandler繼承的channelRead()方法:
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
UnixTime unixTime = (UnixTime)msg;
System.out.println(unixTime);
ctx.close();
}
同樣我們服務端也可以改:
java
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ChannelFuture future = ctx.writeAndFlush(new UnixTime());
future.addListener(ChannelFutureListener.CLOSE);
}
現在我們就編寫一個編碼器實現ChannelOutboundHandler,, 用來將UnixTime對象轉化爲一個ByteBuf:
package com.netty;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundBuffer;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
/**
* Class TimeEncoder ...
*
* @author LiJun
* Created on 2018/8/27
*/
public class TimeEncoder extends ChannelOutboundHandlerAdapter {
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
UnixTime unixTime = (UnixTime)msg;
ByteBuf encoded = ctx.alloc().buffer(4);
encoded.writeInt((int)unixTime.getValue());
ctx.write(encoded, promise);
}
}
注意下:
- 通過ChannelPromise,當編碼後的數據被寫到通道上Netty可以通過這個對象標記成功還是失敗。
- 我們不需要調用ctx.flush(),因爲處理器單獨分離除了一個void flush()方法了。
如果要進一步簡化代碼的話:
package com.netty;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundBuffer;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.MessageToByteEncoder;
/**
* Class TimeEncoder ...
*
* @author LiJun
* Created on 2018/8/27
*/
public class TimeEncoder extends MessageToByteEncoder<UnixTime> {
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, UnixTime unixTime, ByteBuf byteBuf) throws Exception {
byteBuf.writeInt((int)unixTime.getValue());
}
}
最後,我們需要在TimeServerHandler之前把TimeEncoder插入到ChannelPipeline。
八、關閉我們的應用
關閉一個 Netty 應用往往只需要簡單地通過 shutdownGracefully() 方法來關閉你構建的所有的EventLoopGroup。當EventLoopGroup 被完全地終止,並且對應的所有 channel 都已經被關閉時,Netty 會返回一個Future對象來通知你。