Netty是什麼?java
Netty 是一個提供 asynchronous event-driven (異步事件驅動)的網絡應用框架,是一個用以快速開發高性能、可擴展協議的服務器和客戶端。面試
Netty能作什麼?bootstrap
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()時,該線程被阻塞,直到有一些數據被讀取,或數據徹底寫入,該線程在此期間不能再幹任何事情了。而非阻塞不會這樣。
環境要求:
Maven依賴:
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.0.32.Final</version>
</dependency>
複製代碼
如下Netty examples來源: 官方文檔
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();
}
}
複製代碼
咱們剛剛已經編寫出咱們第一個服務端,咱們須要測試一下他是否真的能夠運行。最簡單的測試方法是用 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界面輸入的消息會被顯示出來
到目前爲止,咱們雖然接收到了數據,但沒有作任何的響應。然而一個服務端一般會對一個請求做出響應。讓咱們學習怎樣在 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實戰項目回覆關鍵字免費領取):