天天看點

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

本章介紹

擷取Netty4最新版本

設定運作環境來建構和運作netty程式

建立一個基于Netty的伺服器和用戶端

攔截和處理異常

編寫和運作Netty伺服器和用戶端

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

        設定開發環境的步驟如下

安裝JDK7下載下傳位址http://www.oracle.com/technetwork/java/javase/archive-139210.html

下載下傳netty包下載下傳位址http://netty.io/

安裝Eclipse

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

        本節将引導你建構一個完整的Netty伺服器和用戶端。一般情況下你可能隻關心編寫伺服器如一個http伺服器的用戶端是浏覽器。然後在這個例子中你若同時實作了伺服器和用戶端你将會對他們的原理更加清晰。

        一個Netty程式的工作圖如下

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

用戶端連接配接到伺服器

建立連接配接後發送或接收資料

伺服器處理所有的用戶端連接配接

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

雖然将相同的資料傳回給用戶端不是一個典型的例子但是用戶端和伺服器之間資料的來來回回的傳輸和這個例子是一樣的。本章的例子會證明這一點它們會越來越複雜。

接下來的幾節将帶着你完成基于Netty的用戶端和伺服器的應答程式。

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

配置伺服器功能如線程、端口

實作伺服器處理程式它包含業務邏輯決定當有一個請求連接配接或接收資料時該做什麼

通過建立ServerBootstrap對象來啟動伺服器然後配置這個對象的相關選項如端口、線程模式、事件循環并且添加邏輯處理程式用來處理業務邏輯(下面是個簡單的應答伺服器例子)

<b>[java]</b> view plain copy

package netty.example;

import io.netty.bootstrap.ServerBootstrap;

import io.netty.channel.Channel;

import io.netty.channel.ChannelFuture;

import io.netty.channel.ChannelInitializer;

import io.netty.channel.EventLoopGroup;

import io.netty.channel.nio.NioEventLoopGroup;

import io.netty.channel.socket.nio.NioServerSocketChannel;

public class EchoServer {

    private final int port;

    public EchoServer(int port) {

        this.port = port;

    }

    public void start() throws Exception {

        EventLoopGroup group = new NioEventLoopGroup();

        try {

            //create ServerBootstrap instance

            ServerBootstrap b = new ServerBootstrap();

            //Specifies NIO transport, local socket address

            //Adds handler to channel pipeline

            b.group(group).channel(NioServerSocketChannel.class).localAddress(port)

                    .childHandler(new ChannelInitializer&lt;Channel&gt;() {

                        @Override

                        protected void initChannel(Channel ch) throws Exception {

                            ch.pipeline().addLast(new EchoServerHandler());

                        }

                    });

            //Binds server, waits for server to close, and releases resources

            ChannelFuture f = b.bind().sync();

            System.out.println(EchoServer.class.getName() + "started and listen on " + f.channel().localAddress());

            f.channel().closeFuture().sync();

        } finally {

            group.shutdownGracefully().sync();

        }

    public static void main(String[] args) throws Exception {

        new EchoServer(65535).start();

}

從上面這個簡單的伺服器例子可以看出啟動伺服器應先建立一個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() 方法來綁定伺服器

Netty使用futures和回調概念它的設計允許你處理不同的事件類型更詳細的介紹将再後面章節講述但是我們可以接收資料。你的channel handler必須繼承ChannelInboundHandlerAdapter并且重寫channelRead方法這個方法在任何時候都會被調用來接收資料在這個例子中接收的是位元組。

下面是handler的實作其實作的功能是将用戶端發給伺服器的資料傳回給用戶端

import io.netty.buffer.Unpooled;

import io.netty.channel.ChannelFutureListener;

import io.netty.channel.ChannelHandlerContext;

import io.netty.channel.ChannelInboundHandlerAdapter;

public class EchoServerHandler extends ChannelInboundHandlerAdapter {

    @Override

    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

        System.out.println("Server received: " + msg);

        ctx.write(msg);

    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {

        ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);

    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {

        cause.printStackTrace();

        ctx.close();

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

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

        伺服器寫好了現在來寫一個用戶端連接配接伺服器。應答程式的用戶端包括以下幾步

連接配接伺服器

寫資料到伺服器

等待接受伺服器傳回相同的資料

關閉連接配接

        引導用戶端啟動和引導伺服器很類似用戶端需同時指定host和port來告訴用戶端連接配接哪個伺服器。看下面代碼

import io.netty.bootstrap.Bootstrap;

import io.netty.channel.socket.SocketChannel;

import io.netty.channel.socket.nio.NioSocketChannel;

import io.netty.example.echo.EchoClientHandler;

import java.net.InetSocketAddress;

public class EchoClient {

    private final String host;

    public EchoClient(String host, int port) {

        this.host = host;

            Bootstrap b = new Bootstrap();

            b.group(group).channel(NioSocketChannel.class).remoteAddress(new InetSocketAddress(host, port))

                    .handler(new ChannelInitializer&lt;SocketChannel&gt;() {

                        protected void initChannel(SocketChannel ch) throws Exception {

                            ch.pipeline().addLast(new EchoClientHandler());

            ChannelFuture f = b.connect().sync();

        new EchoClient("localhost", 20000).start();

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

建立Bootstrap對象用來引導啟動用戶端

建立EventLoopGroup對象并設定到Bootstrap中EventLoopGroup可以了解為是一個線程池這個線程池用來處理連接配接、接受資料、發送資料

建立InetSocketAddress并設定到Bootstrap中InetSocketAddress是指定連接配接的伺服器位址

添加一個ChannelHandler用戶端成功連接配接伺服器後就會被執行

調用Bootstrap.connect()來連接配接伺服器

最後關閉EventLoopGroup來釋放資源

        用戶端的業務邏輯的實作依然很簡單更複雜的用法将在後面章節詳細介紹。和編寫伺服器的ChannelHandler一樣在這裡将自定義一個繼承SimpleChannelInboundHandler的ChannelHandler來處理業務通過重寫父類的三個方法來處理感興趣的事件

channelActive()用戶端連接配接伺服器後被調用

channelRead0()從伺服器接收到資料後調用

exceptionCaught()發生異常時被調用

實作代碼如下

import io.netty.buffer.ByteBuf;

import io.netty.buffer.ByteBufUtil;

import io.netty.channel.SimpleChannelInboundHandler;

import io.netty.util.CharsetUtil;

public class EchoClientHandler extends SimpleChannelInboundHandler&lt;ByteBuf&gt; {

    public void channelActive(ChannelHandlerContext ctx) throws Exception {

        ctx.write(Unpooled.copiedBuffer("Netty rocks!",CharsetUtil.UTF_8));

    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {

        System.out.println("Client received: " + ByteBufUtil.hexDump(msg.readBytes(msg.readableBytes())));

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

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

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

注意netty4需要jdk1.7+。

本人測試可以正常運作。

本章介紹了如何編寫一個簡單的基于Netty的伺服器和用戶端并進行通信發送資料。介紹了如何建立伺服器和用戶端以及Netty的異常處理機制。

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