一 初遇Netty
Netty是什麼?
Netty 是一個提供 asynchronous event-driven (非同步事件驅動)的網路應用框架,是一個用以快速開發高效能、可擴充套件協議的伺服器和客戶端。
Netty能做什麼?
Netty 是一個 NIO 客戶端伺服器框架,使用它可以快速簡單地開發網路應用程式,比如伺服器(HTTP伺服器,FTP伺服器,WebSocket伺服器,Redis的Proxy伺服器等等)和客戶端的協議。Netty 大大簡化了網路程式的開發過程比如 TCP 和 UDP 的 socket 服務的開發。
Netty為什麼好?
Netty是建立在NIO基礎之上,Netty在NIO之上又提供了更高層次的抽象,使用它你可以更容易利用Java NIO提高服務端和客戶端的效能。
Netty的特性:
1. 設計
1.1 統一的API,適用於不同的協議(阻塞和非阻塞)
1.2 基於可擴充套件和靈活的事件驅動模型
1.3高度可定製的執行緒模型 - 單執行緒,一個或多個執行緒池,如SEDA
1.4真正的無連線資料包套接字支援(自3.1以來)
複製程式碼
2. 效能
2.1更好的吞吐量,低延遲
2.2更省資源
2.3儘量減少不必要的記憶體拷貝
複製程式碼
3. 安全
完整的SSL / TLS和StartTLS協議的支援
複製程式碼
4. 易用性
4.1 官方有詳細的使用指南
4.2 對環境要求很低
複製程式碼
NIO和IO的區別是什麼?
1. 一個面向位元組一個面向緩衝;
IO面向流意味著每次從流中讀一個或多個位元組,直至讀取所有位元組,它們沒有被快取在任何地方。此外,它不能前後移動流中的資料。如果需要前後移動從流中讀取的資料,需要先將它快取到一個緩衝區。 Java NIO的緩衝導向方法略有不同。資料讀取到一個它稍後處理的緩衝區,需要時可在緩衝區中前後移動。這就增加了處理過程中的靈活性。但是,還需要檢查是否該緩衝區中包含所有您需要處理的資料。而且,需確保當更多的資料讀入緩衝區時,不要覆蓋緩衝區裡尚未處理的資料。
2. NIO是非阻塞IO,IO是阻塞IO
阻塞意味著當一個執行緒呼叫read() 或 write()時,該執行緒被阻塞,直到有一些資料被讀取,或資料完全寫入,該執行緒在此期間不能再幹任何事情了。而非阻塞不會這樣。
二 Netty使用
環境要求:
- JDK 7+
- Maven 3.2.x
- Netty 4.x
Maven依賴:
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.0.32.Final</version>
</dependency>
複製程式碼
以下Netty examples來源: 官方文件
2.1 寫個拋棄伺服器
DiscardServerHandler.java
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
* handler 是由 Netty 生成用來處理 I/O 事件的。
*/
public class DiscardServerHandler extends ChannelInboundHandlerAdapter { // (1)
/**
* 這裡我們覆蓋了 chanelRead() 事件處理方法。
* 每當從客戶端收到新的資料時,這個方法會在收到訊息時被呼叫。
*((ByteBuf) msg).release():丟棄資料
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) { // (2)
// 默默地丟棄收到的資料
((ByteBuf) msg).release(); // (3)
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (4)
// 當出現異常就關閉連線
cause.printStackTrace();
ctx.close();
}
}
複製程式碼
目前我們已經實現了 DISCARD 伺服器的一半功能,剩下的需要編寫一個 main() 方法來啟動服務端的 DiscardServerHandler。
DiscardServer.java
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;
/**
* 啟動服務端的 DiscardServerHandler
*/
public class DiscardServer {
private int port;
public DiscardServer(int port) {
this.port = port;
}
public void run() throws Exception {
//在這個例子中我們實現了一個服務端的應用,因此會有2個 NioEventLoopGroup 會被使用。
//第一個經常被叫做‘boss’,用來接收進來的連線。
//第二個經常被叫做‘worker’,用來處理已經被接收的連線,一旦‘boss’接收到連線,就會把連線資訊註冊到‘worker’上。
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//啟動 NIO 服務的輔助啟動類
ServerBootstrap serverBootstrap = new ServerBootstrap();
//用於處理ServerChannel和Channel的所有事件和IO。
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class) // (3)
.childHandler(new ChannelInitializer<SocketChannel>() { // (4)
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new DiscardServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128) // (5)
.childOption(ChannelOption.SO_KEEPALIVE, true); // (6)
// 繫結埠,開始接收進來的連線
ChannelFuture f = serverBootstrap.bind(port).sync(); // (7)
// 等待伺服器 socket 關閉 。
// 在這個例子中,這不會發生,但你可以優雅地關閉你的伺服器。
f.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();
}
}
複製程式碼
2.2 檢視收到的資料
我們剛剛已經編寫出我們第一個服務端,我們需要測試一下他是否真的可以執行。最簡單的測試方法是用 telnet 命令。例如,你可以在命令列上輸入telnet localhost 8080 或者其他型別引數。
然而我們能說這個服務端是正常執行了嗎?事實上我們也不知道,因為他是一個 discard 服務,你根本不可能得到任何的響應。為了證明他仍然是在正常工作的,讓我們修改服務端的程式來列印出他到底接收到了什麼。
我們已經知道 channelRead() 方法是在資料被接收的時候呼叫。讓我們放一些程式碼到 DiscardServerHandler 類的 channelRead() 方法。
修改DiscardServerHandler類的channelRead(ChannelHandlerContext ctx, Object msg)方法如下:
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf in = (ByteBuf) msg;
try {
while (in.isReadable()) { // (1)
System.out.print((char) in.readByte());
System.out.flush();
}
} finally {
ReferenceCountUtil.release(msg); // (2)
}
}
複製程式碼
再次驗證,cmd下輸入:telnet localhost 8080。你將會看到服務端列印出了他所接收到的訊息。
如下:
你在dos介面輸入的訊息會被顯示出來
2.3 寫個應答伺服器
到目前為止,我們雖然接收到了資料,但沒有做任何的響應。然而一個服務端通常會對一個請求作出響應。讓我們學習怎樣在 ECHO 協議的實現下編寫一個響應訊息給客戶端,這個協議針對任何接收的資料都會返回一個響應。
和 discard server 唯一不同的是把在此之前我們實現的 channelRead() 方法,返回所有的資料替代列印接收資料到控制檯上的邏輯。因此,需要把 channelRead() 方法修改如下:
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ctx.write(msg);
ctx.flush();
}
複製程式碼
再次驗證,cmd下輸入:telnet localhost 8080。你會看到服務端會發回一個你已經傳送的訊息。 如下:
下一篇我們會學習如何用Netty實現聊天功能。
歡迎關注我的微信公眾號(分享各種Java學習資源,面試題,以及企業級Java實戰專案回覆關鍵字免費領取):