Netty In Action中文版 - 第二章:第一個Netty程式

weixin_34127717發表於2018-03-13

Netty In Action中文版 - 第二章第一個Netty程式

本章介紹

  • 獲取Netty4最新版本
  • 設定執行環境來構建和執行netty程式
  • 建立一個基於Netty的伺服器和客戶端
  • 攔截和處理異常
  • 編寫和執行Netty伺服器和客戶端

本章將簡單介紹Netty的核心概念這個狠心概念就是學習Netty是如何攔截和處理異常對於剛開始學習netty的讀者利用netty的異常攔截機制來除錯程式問題很有幫助。本章還會介紹其他一些核心概念如伺服器和客戶端的啟動以及分離通道的處理程式。本章學習一些基礎以便後面章節的深入學習。本章中將編寫一個基於netty的伺服器和客戶端來互相通訊我們首先來設定netty的開發環境。

2.1 設定開發環境

        設定開發環境的步驟如下
  • 安裝JDK7下載地址http://www.oracle.com/technetwork/java/javase/archive-139210.html
  • 下載netty包下載地址http://netty.io/
  • 安裝Eclipse

《Netty In Action》中描述的比較多沒啥用這裡就不多說了。本系列部落格將使用Netty4需要JDK1.7+

2.2 Netty客戶端和伺服器概述

        本節將引導你構建一個完整的Netty伺服器和客戶端。一般情況下你可能只關心編寫伺服器如一個http伺服器的客戶端是瀏覽器。然後在這個例子中你若同時實現了伺服器和客戶端你將會對他們的原理更加清晰。
        一個Netty程式的工作圖如下
  1. 客戶端連線到伺服器
  2. 建立連線後傳送或接收資料
  3. 伺服器處理所有的客戶端連線

從上圖中可以看出伺服器會寫資料到客戶端並且處理多個客戶端的併發連線。從理論上來說限制程式效能的因素只有系統資源和JVM。為了方便理解這裡舉了個生活例子在山谷或高山上大聲喊你會聽見回聲回聲是山返回的在這個例子中你是客戶端山是伺服器。喊的行為就類似於一個Netty客戶端將資料傳送到伺服器聽到回聲就類似於伺服器將相同的資料返回給你你離開山谷就斷開了連線但是你可以返回進行重連伺服器並且可以傳送更多的資料。

雖然將相同的資料返回給客戶端不是一個典型的例子但是客戶端和伺服器之間資料的來來回回的傳輸和這個例子是一樣的。本章的例子會證明這一點它們會越來越複雜。
接下來的幾節將帶著你完成基於Netty的客戶端和伺服器的應答程式。

2.3 編寫一個應答伺服器

寫一個Netty伺服器主要由兩部分組成

  • 配置伺服器功能如執行緒、埠
  • 實現伺服器處理程式它包含業務邏輯決定當有一個請求連線或接收資料時該做什麼

2.3.1 啟動伺服器

通過建立ServerBootstrap物件來啟動伺服器然後配置這個物件的相關選項如埠、執行緒模式、事件迴圈並且新增邏輯處理程式用來處理業務邏輯(下面是個簡單的應答伺服器例子)

[java] view plain copy

  1. package netty.example;
  2. import io.netty.bootstrap.ServerBootstrap;
  3. import io.netty.channel.Channel;
  4. import io.netty.channel.ChannelFuture;
  5. import io.netty.channel.ChannelInitializer;
  6. import io.netty.channel.EventLoopGroup;
  7. import io.netty.channel.nio.NioEventLoopGroup;
  8. import io.netty.channel.socket.nio.NioServerSocketChannel;
  9. public class EchoServer {
  10.     private final int port;
  11.     public EchoServer(int port) {
  12.         this.port = port;
  13.     }
  14.     public void start() throws Exception {
  15.         EventLoopGroup group = new NioEventLoopGroup();
  16.         try {
  17.             //create ServerBootstrap instance
  18.             ServerBootstrap b = new ServerBootstrap();
  19.             //Specifies NIO transport, local socket address
  20.             //Adds handler to channel pipeline
  21.             b.group(group).channel(NioServerSocketChannel.class).localAddress(port)
  22.                     .childHandler(new ChannelInitializer<Channel>() {
  23.                         @Override
  24.                         protected void initChannel(Channel ch) throws Exception {
  25.                             ch.pipeline().addLast(new EchoServerHandler());
  26.                         }
  27.                     });
  28.             //Binds server, waits for server to close, and releases resources
  29.             ChannelFuture f = b.bind().sync();
  30.             System.out.println(EchoServer.class.getName() + "started and listen on " + f.channel().localAddress());
  31.             f.channel().closeFuture().sync();
  32.         } finally {
  33.             group.shutdownGracefully().sync();
  34.         }
  35.     }
  36.     public static void main(String[] args) throws Exception {
  37.         new EchoServer(65535).start();
  38.     }
  39. }

從上面這個簡單的伺服器例子可以看出啟動伺服器應先建立一個ServerBootstrap物件因為使用NIO所以指定NioEventLoopGroup來接受和處理新連線指定通道型別為NioServerSocketChannel設定InetSocketAddress讓伺服器監聽某個埠已等待客戶端連線。
接下來呼叫childHandler放來指定連線後呼叫的ChannelHandler這個方法傳ChannelInitializer型別的引數ChannelInitializer是個抽象類所以需要實現initChannel方法這個方法就是用來設定ChannelHandler。
最後繫結伺服器等待直到繫結完成呼叫sync()方法會阻塞直到伺服器完成繫結然後伺服器等待通道關閉因為使用sync()所以關閉操作也會被阻塞。現在你可以關閉EventLoopGroup和釋放所有資源包括建立的執行緒。
這個例子中使用NIO因為它是目前最常用的傳輸方式你可能會使用NIO很長時間但是你可以選擇不同的傳輸實現。例如這個例子使用OIO方式傳輸你需要指定OioServerSocketChannel。Netty框架中實現了多重傳輸方式將再後面講述。
本小節重點內容

  • 建立ServerBootstrap例項來引導繫結和啟動伺服器
  • 建立NioEventLoopGroup物件來處理事件如接受新連線、接收資料、寫資料等等
  • 指定InetSocketAddress伺服器監聽此埠
  • 設定childHandler執行所有的連線請求
  • 都設定完畢了最後呼叫ServerBootstrap.bind() 方法來繫結伺服器

2.3.2 實現伺服器業務邏輯

Netty使用futures和回撥概念它的設計允許你處理不同的事件型別更詳細的介紹將再後面章節講述但是我們可以接收資料。你的channel handler必須繼承ChannelInboundHandlerAdapter並且重寫channelRead方法這個方法在任何時候都會被呼叫來接收資料在這個例子中接收的是位元組。
下面是handler的實現其實現的功能是將客戶端發給伺服器的資料返回給客戶端

[java] view plain copy

  1. package netty.example;
  2. import io.netty.buffer.Unpooled;
  3. import io.netty.channel.ChannelFutureListener;
  4. import io.netty.channel.ChannelHandlerContext;
  5. import io.netty.channel.ChannelInboundHandlerAdapter;
  6. public class EchoServerHandler extends ChannelInboundHandlerAdapter {
  7.     @Override
  8.     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
  9.         System.out.println("Server received: " + msg);
  10.         ctx.write(msg);
  11.     }
  12.     @Override
  13.     public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
  14.         ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
  15.     }
  16.     @Override
  17.     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
  18.         cause.printStackTrace();
  19.         ctx.close();
  20.     }
  21. }

Netty使用多個Channel Handler來達到對事件處理的分離因為可以很容的新增、更新、刪除業務邏輯處理handler。Handler很簡單它的每個方法都可以被重寫它的所有的方法中只有channelRead方法是必須要重寫的。

2.3.3 捕獲異常

        重寫ChannelHandler的exceptionCaught方法可以捕獲伺服器的異常比如客戶端連線伺服器後強制關閉伺服器會丟擲"客戶端主機強制關閉錯誤"通過重寫exceptionCaught方法就可以處理異常比如發生異常後關閉ChannelHandlerContext。

2.4 編寫應答程式的客戶端

        伺服器寫好了現在來寫一個客戶端連線伺服器。應答程式的客戶端包括以下幾步
  • 連線伺服器
  • 寫資料到伺服器
  • 等待接受伺服器返回相同的資料
  • 關閉連線

2.4.1 引導客戶端

        引導客戶端啟動和引導伺服器很類似客戶端需同時指定host和port來告訴客戶端連線哪個伺服器。看下面程式碼

[java] view plain copy

  1. package netty.example;
  2. import io.netty.bootstrap.Bootstrap;
  3. import io.netty.channel.ChannelFuture;
  4. import io.netty.channel.ChannelInitializer;
  5. import io.netty.channel.EventLoopGroup;
  6. import io.netty.channel.nio.NioEventLoopGroup;
  7. import io.netty.channel.socket.SocketChannel;
  8. import io.netty.channel.socket.nio.NioSocketChannel;
  9. import io.netty.example.echo.EchoClientHandler;
  10. import java.net.InetSocketAddress;
  11. public class EchoClient {
  12.     private final String host;
  13.     private final int port;
  14.     public EchoClient(String host, int port) {
  15.         this.host = host;
  16.         this.port = port;
  17.     }
  18.     public void start() throws Exception {
  19.         EventLoopGroup group = new NioEventLoopGroup();
  20.         try {
  21.             Bootstrap b = new Bootstrap();
  22.             b.group(group).channel(NioSocketChannel.class).remoteAddress(new InetSocketAddress(host, port))
  23.                     .handler(new ChannelInitializer<SocketChannel>() {
  24.                         @Override
  25.                         protected void initChannel(SocketChannel ch) throws Exception {
  26.                             ch.pipeline().addLast(new EchoClientHandler());
  27.                         }
  28.                     });
  29.             ChannelFuture f = b.connect().sync();
  30.             f.channel().closeFuture().sync();
  31.         } finally {
  32.             group.shutdownGracefully().sync();
  33.         }
  34.     }
  35.     public static void main(String[] args) throws Exception {
  36.         new EchoClient("localhost"20000).start();
  37.     }
  38. }

建立啟動一個客戶端包含下面幾步

  • 建立Bootstrap物件用來引導啟動客戶端
  • 建立EventLoopGroup物件並設定到Bootstrap中EventLoopGroup可以理解為是一個執行緒池這個執行緒池用來處理連線、接受資料、傳送資料
  • 建立InetSocketAddress並設定到Bootstrap中InetSocketAddress是指定連線的伺服器地址
  • 新增一個ChannelHandler客戶端成功連線伺服器後就會被執行
  • 呼叫Bootstrap.connect()來連線伺服器
  • 最後關閉EventLoopGroup來釋放資源

2.4.2 實現客戶端的業務邏輯

        客戶端的業務邏輯的實現依然很簡單更復雜的用法將在後面章節詳細介紹。和編寫伺服器的ChannelHandler一樣在這裡將自定義一個繼承SimpleChannelInboundHandler的ChannelHandler來處理業務通過重寫父類的三個方法來處理感興趣的事件
  • channelActive()客戶端連線伺服器後被呼叫
  • channelRead0()從伺服器接收到資料後呼叫
  • exceptionCaught()發生異常時被呼叫

實現程式碼如下

[java] view plain copy

  1. package netty.example;
  2. import io.netty.buffer.ByteBuf;
  3. import io.netty.buffer.ByteBufUtil;
  4. import io.netty.buffer.Unpooled;
  5. import io.netty.channel.ChannelHandlerContext;
  6. import io.netty.channel.SimpleChannelInboundHandler;
  7. import io.netty.util.CharsetUtil;
  8. public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
  9.     @Override
  10.     public void channelActive(ChannelHandlerContext ctx) throws Exception {
  11.         ctx.write(Unpooled.copiedBuffer("Netty rocks!",CharsetUtil.UTF_8));
  12.     }
  13.     @Override
  14.     protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
  15.         System.out.println("Client received: " + ByteBufUtil.hexDump(msg.readBytes(msg.readableBytes())));
  16.     }
  17.     @Override
  18.     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
  19.         cause.printStackTrace();
  20.         ctx.close();
  21.     }
  22. }

可能你會問為什麼在這裡使用的是SimpleChannelInboundHandler而不使用ChannelInboundHandlerAdapter主要原因是ChannelInboundHandlerAdapter在處理完訊息後需要負責釋放資源。在這裡將呼叫ByteBuf.release()來釋放資源。SimpleChannelInboundHandler會在完成channelRead0後釋放訊息這是通過Netty處理所有訊息的ChannelHandler實現了ReferenceCounted介面達到的。

        為什麼在伺服器中不使用SimpleChannelInboundHandler呢因為伺服器要返回相同的訊息給客戶端在伺服器執行完成寫操作之前不能釋放呼叫讀取到的訊息因為寫操作是非同步的一旦寫操作完成後Netty中會自動釋放訊息。

        客戶端的編寫完了下面讓我們來測試一下

2.5 編譯和執行echo(應答)程式客戶端和伺服器

注意netty4需要jdk1.7+。

本人測試可以正常執行。

2.6 總結

本章介紹瞭如何編寫一個簡單的基於Netty的伺服器和客戶端並進行通訊傳送資料。介紹瞭如何建立伺服器和客戶端以及Netty的異常處理機制。

原文地址http://www.bieryun.com/2148.html

相關文章