所以 Netty 服務器都需要下面這些:
* 一個服務器 handler:這個組件實現了服務器的業務邏輯,決定了連接創建后和接收到信息后該如何處理
* Bootstrapping: 這個是配置服務器的啟動代碼。最少需要設置服務器綁定的端口,用來監聽連接請求。
### [](https://github.com/waylau/essential-netty-in-action/blob/master/GETTING%20STARTED/Writing%20the%20echo%20server.md#通過-channelhandler-來實現服務器的邏輯)通過 ChannelHandler 來實現服務器的邏輯
Echo Server 將會將接受到的數據的拷貝發送給客戶端。因此,我們需要實現 ChannelInboundHandler 接口,用來定義處理入站事件的方法。由于我們的應用很簡單,只需要繼承 ChannelInboundHandlerAdapter 就行了。這個類 提供了默認 ChannelInboundHandler 的實現,所以只需要覆蓋下面的方法:
* channelRead() - 每個信息入站都會調用
* channelReadComplete() - 通知處理器最后的 channelread() 是當前批處理中的最后一條消息時調用
* exceptionCaught()- 讀操作時捕獲到異常時調用
EchoServerHandler 代碼如下:
Listing 2.2 EchoServerHandler
~~~
@Sharable //1
public class EchoServerHandler extends
ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx,
Object msg) {
ByteBuf in = (ByteBuf) msg;
System.out.println("Server received: " + in.toString(CharsetUtil.UTF_8)); //2
ctx.write(in); //3
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)//4
.addListener(ChannelFutureListener.CLOSE);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx,
Throwable cause) {
cause.printStackTrace(); //5
ctx.close(); //6
}
}
~~~
1.`@Sharable`?標識這類的實例之間可以在 channel 里面共享
2.日志消息輸出到控制臺
3.將所接收的消息返回給發送者。注意,這還沒有沖刷數據
4.沖刷所有待審消息到遠程節點。關閉通道后,操作完成
5.打印異常堆棧跟蹤
6.關閉通道
這種使用 ChannelHandler 的方式體現了關注點分離的設計原則,并簡化業務邏輯的迭代開發的要求。處理程序很簡單;它的每一個方法可以覆蓋到“hook(鉤子)”在活動周期適當的點。很顯然,我們覆蓋 channelRead因為我們需要處理所有接收到的數據。
覆蓋 exceptionCaught 使我們能夠應對任何 Throwable 的子類型。在這種情況下我們記錄,并關閉所有可能處于未知狀態的連接。它通常是難以 從連接錯誤中恢復,所以干脆關閉遠程連接。當然,也有可能的情況是可以從錯誤中恢復的,所以可以用一個更復雜的措施來嘗試識別和處理 這樣的情況。
*如果異常沒有被捕獲,會發生什么?*
*每個 Channel 都有一個關聯的 ChannelPipeline,它代表了 ChannelHandler 實例的鏈。適配器處理的實現只是將一個處理方法調用轉發到鏈中的下一個處理器。因此,如果一個 Netty 應用程序不覆蓋exceptionCaught ,那么這些錯誤將最終到達 ChannelPipeline,并且結束警告將被記錄。出于這個原因,你應該提供至少一個 實現 exceptionCaught 的 ChannelHandler。*
關鍵點要牢記:
* ChannelHandler 是給不同類型的事件調用
* 應用程序實現或擴展 ChannelHandler 掛接到事件生命周期和 提供自定義應用邏輯。
### [](https://github.com/waylau/essential-netty-in-action/blob/master/GETTING%20STARTED/Writing%20the%20echo%20server.md#引導服務器)引導服務器
了解到業務核心處理邏輯 EchoServerHandler 后,下面要引導服務器自身了。
* 監聽和接收進來的連接請求
* 配置 Channel 來通知一個關于入站消息的 EchoServerHandler 實例
*Transport(傳輸)*
*在本節中,你會遇到“transport(傳輸)”一詞。在網絡的多層視圖協議里面,傳輸層提供了用于端至端或主機到主機的通信服務。互聯網通信的基礎是 TCP 傳輸。當我們使用術語“NIO transport”我們指的是一個傳輸的實現,它是大多等同于 TCP ,除了一些由 Java NIO 的實現提供了服務器端的性能增強。Transport 詳細在第4章中討論。*
Listing 2.3 EchoServer
~~~
public class EchoServer {
private final int port;
public EchoServer(int port) {
this.port = port;
}
public static void main(String[] args) throws Exception {
if (args.length != 1) {
System.err.println(
"Usage: " + EchoServer.class.getSimpleName() +
" <port>");
return;
}
int port = Integer.parseInt(args[0]); //1
new EchoServer(port).start(); //2
}
public void start() throws Exception {
NioEventLoopGroup group = new NioEventLoopGroup(); //3
try {
ServerBootstrap b = new ServerBootstrap();
b.group(group) //4
.channel(NioServerSocketChannel.class) //5
.localAddress(new InetSocketAddress(port)) //6
.childHandler(new ChannelInitializer<SocketChannel>() { //7
@Override
public void initChannel(SocketChannel ch)
throws Exception {
ch.pipeline().addLast(
new EchoServerHandler());
}
});
ChannelFuture f = b.bind().sync(); //8
System.out.println(EchoServer.class.getName() + " started and listen on " + f.channel().localAddress());
f.channel().closeFuture().sync(); //9
} finally {
group.shutdownGracefully().sync(); //10
}
}
}
~~~
1.設置端口值(拋出一個 NumberFormatException 如果該端口參數的格式不正確)
2.呼叫服務器的 start()方法
3.創建 EventLoopGroup
4.創建 ServerBootstrap
5.指定使用 NIO 的傳輸 Channel
6.設置 socket 地址使用所選的端口
7.添加 EchoServerHandler 到 Channel 的 ChannelPipeline
8.綁定的服務器;sync 等待服務器關閉
9.關閉 channel 和 塊,直到它被關閉
10.關機的 EventLoopGroup,釋放所有資源。
在這個例子中,代碼創建 ServerBootstrap 實例(步驟4)。由于我們使用在 NIO 傳輸,我們已指定 NioEventLoopGroup(3)接受和處理新連接,指定 NioServerSocketChannel(5)為信道類型。在此之后,我們設置本地地址是 InetSocketAddress 與所選擇的端口(6)如。服務器將綁定到此地址來監聽新的連接請求。
第七步是關鍵:在這里我們使用一個特殊的類,ChannelInitializer 。當一個新的連接被接受,一個新的子 Channel 將被創建, ChannelInitializer 會添加我們EchoServerHandler 的實例到 Channel 的 ChannelPipeline。正如我們如前所述,這個處理器將被通知如果有入站信息。
雖然 NIO 是可擴展性,但它的正確配置是不簡單的。特別是多線程,要正確處理也非易事。幸運的是,Netty 的設計封裝了大部分復雜性,尤其是通過抽象,例如 EventLoopGroup,SocketChannel 和 ChannelInitializer,其中每一個將在更詳細地在第3章中討論。
在步驟8,我們綁定的服務器,等待綁定完成。 (調用 sync() 的原因是當前線程阻塞)在第9步的應用程序將等待服務器 Channel 關閉(因為我們 在 Channel 的 CloseFuture 上調用 sync())。現在,我們可以關閉下 EventLoopGroup 并釋放所有資源,包括所有創建的線程(10)。
NIO 用于在本實施例,因為它是目前最廣泛使用的傳輸,歸功于它的可擴展性和徹底的不同步。但不同的傳輸的實現是也是可能的。例如,如果本實施例中使用的 OIO 傳輸,我們將指定 OioServerSocketChannel 和 OioEventLoopGroup。 Netty 的架構,包括更關于傳輸信息,將包含在第4章。在此期間,讓我們回顧下在服務器上執行,我們只研究重要步驟。
服務器的主代碼組件是
* EchoServerHandler 實現了的業務邏輯
* 在 main()方法,引導了服務器
執行后者所需的步驟是:
* 創建 ServerBootstrap 實例來引導服務器并隨后綁定
* 創建并分配一個 NioEventLoopGroup 實例來處理事件的處理,如接受新的連接和讀/寫數據。
* 指定本地 InetSocketAddress 給服務器綁定
* 通過 EchoServerHandler 實例給每一個新的 Channel 初始化
* 最后調用 ServerBootstrap.bind() 綁定服務器
這樣服務器初始化完成,可以被使用了。
- Introduction
- 開始
- Netty-異步和數據驅動
- Netty 介紹
- 構成部分
- 關于本書
- 第一個 Netty 應用
- 設置開發環境
- Netty 客戶端/服務端 總覽
- 寫一個 echo 服務器
- 寫一個 echo 客戶端
- 編譯和運行 Echo 服務器和客戶端
- 總結
- Netty 總覽
- Netty 快速入門
- Channel, Event 和 I/O
- 什么是 Bootstrapping 為什么要用
- ChannelHandler 和 ChannelPipeline
- 近距離觀察 ChannelHandler
- 總結
- 核心功能
- Transport(傳輸)
- 案例研究:Transport 的遷移
- Transport API
- 包含的 Transport
- Transport 使用情況
- 總結
- Buffer(緩沖)
- Buffer API
- ByteBuf - 字節數據的容器
- 字節級別的操作
- ByteBufHolder
- ByteBuf 分配
- 總結
- ChannelHandler 和 ChannelPipeline
- ChannelHandler 家族
- ChannelPipeline
- ChannelHandlerContext
- 總結
- Codec 框架
- 什么是 Codec
- Decoder(解碼器)
- Encoder(編碼器)
- 抽象 Codec(編解碼器)類
- 總結
- 提供了的 ChannelHandler 和 Codec
- 使用 SSL/TLS 加密 Netty 程序
- 構建 Netty HTTP/HTTPS 應用
- 空閑連接以及超時
- 解碼分隔符和基于長度的協議
- 編寫大型數據
- 序列化數據
- 總結
- Bootstrap 類型
- 引導客戶端和無連接協議
- 引導服務器
- 從 Channel 引導客戶端
- 在一個引導中添加多個 ChannelHandler
- 使用Netty 的 ChannelOption 和屬性
- 關閉之前已經引導的客戶端或服務器
- 總結
- 引導
- Bootstrap 類型
- 引導客戶端和無連接協議
- 引導服務器
- 從 Channel 引導客戶端
- 在一個引導中添加多個 ChannelHandler
- 使用Netty 的 ChannelOption 和屬性
- 關閉之前已經引導的客戶端或服務器
- 總結
- NETTY BY EXAMPLE
- 單元測試
- 總覽
- 測試 ChannelHandler
- 測試異常處理
- 總結
- WebSocket
- WebSocket 程序示例
- 添加 WebSocket 支持
- 測試程序
- 總結
- SPDY
- SPDY 背景
- 示例程序
- 實現
- 啟動 SpdyServer 并測試
- 總結
- 通過 UDP 廣播事件
- UDP 基礎
- UDP 廣播
- UDP 示例
- EventLog 的 POJO
- 寫廣播器
- 寫監視器
- 運行 LogEventBroadcaster 和 LogEventMonitor
- 總結
- 高級主題
- 實現自定義的編解碼器
- 編解碼器的范圍
- 實現 Memcached 編解碼器
- 了解 Memcached 二進制協議
- Netty 編碼器和解碼器
- 測試編解碼器
- EventLoop 和線程模型
- 線程模型的總覽
- EventLoop
- EventLoop
- I/O EventLoop/Thread 分配細節
- 總結
- 用例1:Droplr Firebase 和 Urban Airship
- 用例2:Facebook 和 Twitter