Netty 入門
瘋狂創客圈 Java 分布式聊天室【 億級流量】實戰系列之 -入門【 博客園 總入口 】
@
前言:
問題: 我們需要高度優化的協議
現在我們使用通用應用程序或包進行通信。例如,我們經常使用HTTP客戶端庫從Web服務器檢索信息,並通過Web服務調用遠程過程調用。然而,通用協議或其實現有時不能很好地擴展。這就像我們不使用通用HTTP服務器來交換大量文件,電子郵件和近實時消息,如財務信息和多人游戲數據。
我們需要的是高度優化的協議實現,專門用於特殊目的。例如,您可能希望實現針對基於AJAX的聊天應用程序,媒體流或大型文件傳輸進行了優化的HTTP服務器。你甚至可以設計和實施一個全新的協議,這個協議是根據你的需要而定制的。另一個不可避免的情況是當您必須處理舊版專有協議以確保與舊系統的互操作性。在這種情況下重要的是我們能夠快速實現該協議,而不會犧牲最終應用程序的穩定性和性能。
方案
Netty項目是為了快速開發可維護的高性能高可擴展性協議服務器和客戶端而努力提供異步事件驅動的網絡應用程序框架和工具。換句話說,Netty是一個NIO客戶端服務器框架,可以快速輕松地開發諸如協議服務器和客戶端之類的網絡應用程序。它大大簡化了網絡編程流程,如TCP和UDP套接字服務器開發。
“快速和容易”並不意味着由此產生的應用程序將遭受可維護性或性能問題的困擾。Netty經過精心設計,實現了許多協議,如FTP,SMTP,HTTP以及各種基於二進制和基於文本的傳統協議。因此,Netty成功地找到了一種方法來實現輕松的開發,性能,穩定性和靈活性,而無需妥協。
有些用戶可能已經找到了聲稱具有相同優勢的其他網絡應用程序框架,您可能想問問Netty與他們的區別。答案是它建立的哲學。Netty旨在為您提供API和執行方面最舒適的體驗,從第一天開始。這不是有形的東西,但你會意識到,這個哲學將使你的生活更容易,當你閱讀本指南和玩Netty的時候。
好了,以上就是關於netty的一個官網的初步介紹。
下面進入搭建最簡單的服務器的環節,我這里會按照官網的思路走,不過不會完全一點不差。
好了,我們開始。
建立項目
首先我們需要建立項目,如下圖所示:
項目名稱是NettyDemo,官網建議使用JDK1.6以上,我這里使用的JDK1.8,然后加入使用maven導入Netty依賴:
<dependencies>
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.6.Final</version>
</dependency>
</dependencies>
那么現在我們可以正式開始我們的項目編寫了。
編寫一個Discard Handler 處理器
編寫一個Discard服務器(按我理解就是啥也不干的服務器,別着急反駁,往下看)
世界上最簡單的協議不是“hello world”,而是。。。。什么也不做的協議Discard,丟棄的意思,服務端丟棄,那就是啥也不做的協議唄(嘗試把協議理解為用戶自定義功能)。
想要實現一個Discard協議,那么我們唯一需要做的就是忽略所有接收到的數據。讓我們從處理器實現開始,它處理由netty生成的I/O事件。
首先我們創建一個java包:netty_beginner,然后在里面創建一個類DiscardServerHandler
類的內容如下:
package netty_beginner;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
- Created by moon on 2017/4/5.
*/
public class DiscardServerHandler extends ChannelInboundHandlerAdapter { // (1)
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // (2)
// super.channelRead(ctx, msg);
((ByteBuf) msg).release(); // (3)
ByteBuf in = (ByteBuf) msg;
try {
while (in.isReadable()) {
System.out.print((char) in.readByte());
System.out.flush();
}
} finally {
ReferenceCountUtil.release(msg);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception { // (5)
// super.exceptionCaught(ctx, cause);
cause.printStackTrace();
ctx.close();
}
}
DiscardServerHandler 繼承自ChannelInboundHandlerAdapter,它是 ChannelInboundHandler的實現。提供可以覆蓋的各種事件處理程序方法。現在,只需要擴展ChannelInboundHandlerAdapter即可,而不是自己實現處理程序接口。
在這里,我們重寫通道讀取channelRead()事件處理方法。每當從客戶端收到新數據時,都會使用接收到的消息調用此方法。
在這個例子中,接收到的消息的類型是ByteBuf。
為了實現DISCARD 丟棄的功能,處理程序必須丟棄掉收到的消息。
ByteBuf是一個引用計數對象,必須通過release()方法顯式釋放。請記住,處理程序有責任釋放傳遞給處理程序的引用計數對象。通常,channelRead()處理方法的實現方式如下:
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
try {
// Do something with msg
} finally {
ReferenceCountUtil.release(msg);
}
}
當由於I / O錯誤或由於在處理事件時拋出異常而使得Netty拋出異常時,exceptionCaught() 事件將會被Throwable拋出。
在大多數情況下,應該記錄捕獲到的異常,並在此關閉其關聯的通道,雖然這種方法的實現可以根據你想要處理的異常情況而有所不同。例如,您可能希望在關閉連接之前發送帶有錯誤代碼的響應消息。
編寫一個Discard 服務器
到目前位置一切順利。我們已經實現了DISCARD服務器的前半部分。現在剩下的是寫入使用DiscardServerHandler啟動服務器的main()方法。
我們創建另外一個類:DiscardServer,實現 Discard 服務的功能。
如下:
package netty_beginner;
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;
/**
- Created by moon on 2017/4/5.
*/
public class DiscardServer {
private int port;
public DiscardServer(int port) {
this.port = port;
}
public void run() throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap(); // (2)
b.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)
// Bind and start to accept incoming connections.
ChannelFuture f = b.bind(port).sync(); // (7)
// Wait until the server socket is closed.
// In this example, this does not happen, but you can do that to gracefully
// shut down your server.
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws InterruptedException {
int port;
if (args.length > 0) {
port = Integer.parseInt(args[0]);
} else {
port = 8080;
}
new DiscardServer(port).run();
}
}
線程組
NioEventLoopGroup 是一個處理I / O操作的多線程事件循環線程組。
Netty為不同類型的傳輸提供了各種EventLoopGroup實現。在這個例子中,使用兩個NioEventLoopGroup線程組。一般的服務器,都使用兩個以上的線程組。
-
第一個,通常稱為“Boss”,用於接受客戶端的連接。
-
第二個,通常稱為“Worker”,一旦“Boss” 接受客戶端的連接,並將接受的連接注冊給“Worker”。
“Boss”負責連接的監聽,“Worker”負責處理連接的輸入和輸出。
一個線程組,包含一條或者多條線程。
啟動幫助類
ServerBootstrap是一個幫助類,用於設置服務器。可以不用ServerBootstrap直幫助類,直接使用Channel設置服務器。但是,這是一個繁瑣的過程,在大多數情況下您不需要這樣做。
在這里,我們指定使用NioServerSocketChannel類來實例化一個新的Channel來接受傳入的連接。(可以這么理解,每個客戶端連接我們服務端,我們都會為他們創建一個channel,那么這個channel對於面向對象的我們來說就是一個類,我們同意對於我們接受到的連接都初始化為:NioServerSocketChannel。
這里指定的處理程序將始終由新接受的Channel進行評估。ChannelInitializer是一個特殊的處理程序,旨在幫助用戶配置新的Channel。很可能您想通過添加一些處理程序(如DiscardServerHandler)來配置新Channel的ChannelPipeline來實現您的網絡應用程序。隨着應用程序的復雜化,您可能會在管道中添加更多的處理程序,並將這個匿名類最終提取到頂級類中。(個人感覺說白了就是想自己實現包含自己處理邏輯的Channel,但是又需要包含一些通用的原有功能,咋辦,繼承唄,這就是為什么上面的DiscardServerHandler繼承netty的類)
您還可以設置特定於Channel實現的參數。我們正在編寫一個TCP / IP服務器,因此我們可以設置套接字選項,如tcpNoDelay和keepAlive。請參閱ChannelOption的apidocs和特定的ChannelConfig實現,以獲得有關支持的ChannelOptions的概述。
設置Channel 通道的選項
你有沒有注意到option()和childOption()?
option()用於配置 服務器連接監聽通道 ,也就是 NioServerSocketChannel。
childOption()用於配置每一個客戶端連接成功的通道 —— NioSocketChannel。
我們現在准備好了。剩下的是綁定到端口並啟動服務器。這里,我們綁定機器中端口 (比如 8080)。如果有多個地址,您現在可以根據需要調用 bind()方法多次(具有不同的綁定地址)。
恭喜,到了現在這個階段我們已經完成了。下面可以進行嘗試,那么在嘗試之前,我要說一句,這個例子非常好,就是一點比較費解,即使我開始測試。
步驟是:
- 首先啟動服務器的main方法。
- 然后,通過telnet ,本機8080端口發送內容,看看是否成功
測試:發送消息到Discard服務器
我們打開cmd,輸入 telnet,進入一個新的窗口:
我們使用 telnet 命令:open localhost 8080 ,開啟Discard服務器的telnet連接。
如下圖:
如果忘記了命令,可以使用幫助命令。 這個幫助命令為: ?/help
連接成功后,我們可以輸入任何內容,比如 hello,在idea控制台會挨個字符輸出:
寫在最后
至此為止,可以看到,Netty 的開發,其實是容易入門滴。
下一篇: Netty 的 Echo 服務器,比這個例子稍微復雜一點點。
瘋狂創客圈 Java 死磕系列
- Java (Netty) 聊天程序【 億級流量】實戰 開源項目實戰
- Netty 源碼、原理、JAVA NIO 原理
- Java 面試題 一網打盡
- 瘋狂創客圈 【 博客園 總入口 】