netty快速入門

什麼是netty

Netty 是一個提供 asynchronous event-driven (異步事件驅動)的網絡應用框架,是一個用以快速開發高性能、高可靠性協議的服務器和客戶端。換句話說,Netty 是一個 NIO 客戶端服務器框架,使用它可以快速簡單地開發網絡應用程序,比如服務器和客戶端的協議。Netty 大大簡化了網絡程序的開發過程比如 TCP 和 UDP 的 socket 服務的開發。

"快速和簡單"並不意味著應用程序會有難維護和性能低的問題,Netty 是一個精心設計的框架,它從許多協議的實現中吸收了很多的經驗比如 FTP、SMTP、HTTP、許多二進制和基於文本的傳統協議.因此,Netty 已經成功地找到一個方式,在不失靈活性的前提下來實現開發的簡易性,高性能,穩定性。

讓我們開始吧

本章圍繞 Netty 的核心架構,通過簡單的示例帶你快速入門。當你讀完本章節,你馬上就可以用 Netty 寫出一個客戶端和服務器。

開始之前

在開始之前我們先說明下開發環境,我們使用netty-4.1.30這個版本,jdk使用1.8及以上版本。

<code><dependency>
<groupid>io.netty/<groupid>
<artifactid>netty-all/<artifactid>
<version>4.1.30.Final/<version>
/<dependency>/<code>

jdk請自行下載。

先來個丟棄服務

世上最簡單的協議不是'Hello, World!' 而是 DISCARD(丟棄)。這個協議將會丟掉任何收到的數據,而不響應。 為了實現 DISCARD 協議,你只需忽略所有收到的數據。讓我們從 handler (處理器)的實現開始,handler 是由Netty 生成用來處理 I/O 事件的。

先創建一個處理器

<code>package com.netty.first;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

/**
* 處理服務端 channel.
*/
public class DiscardServerHandler extends ChannelInboundHandlerAdapter { // (1)
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) { // (2) System.out.println(msg);
// 默默地丟棄收到的數據
((ByteBuf) msg).release(); // (3)
}

@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (4)
// 當出現異常就關閉連接cause.printStackTrace(); ctx.close();
}
/<code>

(1)ChannelInboundHandlerAdapter提供了許多事件處理的接口方法,然後你可以覆蓋這些方法。現在僅僅只需要繼承 類而不是你自己去實現接口方法。

(2)這裡我們蓋了chanelRead()覆 事件處理方法。每當從客戶端收到新的數據時,這個方法會在收到消息時被調用,這個ByteBuf例子中,收到的消息的類型是

(3)為了實現 DISCARD 協議,處理器不得不忽略所有接受到的消息。

ByteBuf是一個引用計數對象,這個對象必須顯示地調用 release() 方法來釋放。請記住處理器的職責是釋放所有channelRead()傳遞到處理器的引用計數對象。通常, 方法的實現就像下面的這段代碼:

<code>@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) { try {
// Do something with msg
} finally {
ReferenceCountUtil.release(msg);
}
}/<code>

(4)exceptiongcaught()事件處理方法是當出現throwable對象才會被調用,即當 Netty 由於 IO 錯誤或者處理器在處理事件時拋出的異常時。在大部分情況下,捕獲的異常應該被記錄下來並且把關聯的 channel 給關閉掉。然而這個方法的處理方式會在遇到不同異常的情況下有不同的實現,比如你可能想在關閉連接之前發送一個錯誤碼的響應消息。

編寫服務端代碼

目前為止一切都還不錯,我們已經實現了 DISCARD 服務器的一半功能,剩下的需要編寫一個 main() 方法來啟動DiscardServerHandler服務端的 。

<code>
package com.netty.first;

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;


/**
* 丟棄任何進入的數據
*/
public class DiscardServer {



private int port;


public DiscardServer(int port) { this.port = port;
}


public void run() throws Exception {
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)\t// (5)
.childOption(ChannelOption.SO_KEEPALIVE, true); // (6)

// 綁定端口,開始接收進來的連接
ChannelFuture f = b.bind(port).sync(); // (7)

// 等待服務器\tsocket 關 閉 。
// 在這個例子中,這不會發生,但你可以優雅地關閉你的服務器。
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();
}
}
/<socketchannel>/<code>

NioEventLoopGroup 用來處理I/O操作的多線程事件循環器,Netty 提供了許多不同的EventLoopGroup 的NioEventLoopGroup實現用來處理不同的傳輸。在這個例子中我們實現了一個服務端的應用,因此會有2個會被使用。第一個經常被叫做'boss',用來接收進來的連接。第二個經常被叫做'worker',用來處理已經被接收的連接,一旦'boss'接收到連接,就會把連接信息註冊到'worker'上。如何知道多少個線程已經被使用,如何映射到已經創建 的 Channel上都需要依賴於 EventLoopGroup 的實現,並且可以通過構造函數來配置他們的關係。

2. ServerBootstrap 是一個啟動 NIO 服務的輔助啟動類。你可以在這個服務中直接使用 Channel,但是這會是一個複雜的處理過程,在很多情況下你並不需要這樣做。

3. 這裡我們指定使用 NioServerSocketChannel 類來舉例說明一個新的 Channel 如何接收進來的連接。

4. 這裡的事件處理類經常會被用來處理一個最近的已經接收的 Channel。ChannelInitializer 是一個特殊的處理類,他的目的是幫助使用者配置一個新的 Channel。也許你想通過增加一些處理類比如DiscardServerHandler 來配置一個新的 Channel 或者其對應的ChannelPipeline 來實現你的網絡程序。當你的程序變的複雜時,可能你會增加更多的處理類到 pipline 上,然後提取這些匿名類到最頂層的類上。

5. 你可以設置這裡指定的 Channel 實現的配置參數。我們正在寫一個TCP/IP 的服務端,因此我們被允許設置socket 的參數選項比如tcpNoDelay 和 keepAlive。請參考 ChannelOption 和詳細的 ChannelConfig 實現的接口文檔以此可以對ChannelOption 的有一個大概的認識。

6. 你關注過 option() 和 childOption() 嗎?option() 是提供給NioServerSocketChannel 用來接收進來的連接。childOption() 是提供給由父管道 ServerChannel 接收到的連接,在這個例子中也是 NioServerSocketChannel。

7. 我們繼續,剩下的就是綁定端口然後啟動服務。這裡我們在機器上綁定了機器所有網卡上的 8080 端口。當然現在你可以多次調用 bind() 方法(基於不同綁定地址)。

恭喜!你已經熟練地完成了第一個基於 Netty 的服務端程序。

查看收到的數據

telnet localhost 8080現在我們已經編寫出我們第一個服務端,我們需要測試一下他是否真的可以運行。最簡單的測試方法是用 telnet 命

令。例如,你可以在命令行上輸入 或者其他類型參數。

netty快速入門

netty快速入門

在telnet終端中輸入任意字符,服務端向控制檯輸出信息。證明服務端接收到客戶端發送的消息了。但是我們並不 能看到服務端接收到了什麼東西,我們可以把channelRead方法改成如下內容:

<code>@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) { // (2)
//System.out.println(msg); ByteBuf message = (ByteBuf) msg;
System.out.println(message.toString(CharsetUtil.US_ASCII));
// 默默地丟棄收到的數據
((ByteBuf) msg).release(); // (3)
}/<code>

這樣控制檯就可以看到客戶端發送的數據了。

netty快速入門

寫個應答服務器


到目前為止,我們雖然接收到了數據,但沒有做任何的響應。然而一個服務端通常會對一個請求作出響應。讓我們 學習怎樣在 ECHO 協議的實現下編寫一個響應消息給客戶端,這個協議針對任何接收的數據都會返回一個響應。

和 discard server 唯一不同的是把在此之前我們實現的 channelRead() 方法,返回所有的數據替代打印接收數據到控制檯上的邏輯。因此,需要把 channelRead() 方法修改如下:

<code>@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) { ctx.write(msg); // (1)
ctx.flush(); // (2)
}/<code>

(1)ChannelHandlerContext 對象提供了許多操作,使你能夠觸發各種各樣的 I/O 事件和操作。這裡我們調用了write(Object) 方法來逐字地把接受到的消息寫入。請注意不同於 DISCARD 的例子我們並沒有釋放接受到的消息, 這是因為當寫入的時候 Netty 已經幫我們釋放了。 (2)ctx.write(Object) 方法不會使消息寫入到通道上,他被緩衝在了內部,你需要調用 ctx.flush() 方法來把緩衝區中數據強行輸出。或者你可以用更簡潔的cxt.writeAndFlush(msg) 以達到同樣的目的。

如果你再一次運行 telnet 命令,你會看到服務端會發回一個你已經發送的消息。


分享到:


相關文章: