第二章:第一個Netty程序

本章介紹java

獲取Netty4最新版本bootstrap

設置運行環境來構建和運行netty程序服務器

建立一個基於Netty的服務器和客戶端異步

攔截和處理異常socket

編寫和運行Netty服務器和客戶端ide

----學習Netty是如何攔截和處理異常,服務器和客戶端的啓動以及分離通道的處理程序----oop

1.NETTY介紹學習

一個Netty程序的工做圖以下:this

1.客戶端鏈接到服務器 .net

2.創建鏈接後,發送或接收數據 

3.服務器處理全部的客戶端鏈接 

2. 1編寫一個應答服務器

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

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

實現服務器處理程序,它包含業務邏輯,決定當有一個請求鏈接或接收數據時該作什麼

2.1.1啓動服務器

經過建立ServerBootstrap對象來啓動服務器,而後配置這個對象的相關選項,如端口、線程模式、事件循環,而且添加邏輯處理程序用來處理業務邏輯。

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<Channel>() {  
                        @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();  
    }  
}

a.啓動服務器應先建立一個ServerBootstrap對象,由於使用NIO,因此指定NioEventLoopGroup來接受和處理新鏈接,指定通道類型爲NioServerSocketChannel,設置InetSocketAddress讓服務器監聽某個端口已等待客戶端鏈接。

b. 接下來,調用childHandler用來指定鏈接後調用的ChannelHandler,這個方法傳ChannelInitializer類型的參數,ChannelInitializer是個抽象類,因此須要實現initChannel方法,這個方法就是用來設置ChannelHandler。

c.最後綁定服務器 等待直到綁定完成,調用sync()方法會阻塞直到服務器完成綁定,而後服務器等待通道關閉,由於使用sync(),因此關閉操做也會被阻塞。如今你能夠關閉EventLoopGroup和釋放全部資源,包括建立的線程。

若是這個例子使用OIO方式傳輸,你須要指定OioServerSocketChannel

 本小節重點內容:

建立ServerBootstrap實例來引導綁定和啓動服務器

建立NioEventLoopGroup對象來處理事件,如接受新鏈接、接收數據、寫數據等等

指定InetSocketAddress,服務器監聽此端口

設置childHandler執行全部的鏈接請求

都設置完畢了,最後調用ServerBootstrap.bind() 方法來綁定服務器

2.1.2 實現服務器業務邏輯

Netty使用futures和回調概念,它的設計容許你處理不一樣的事件類型。

你的channelHandler必須繼承ChannelInboundHandlerAdapter而且重寫channelRead方法,這個方法在任什麼時候候都會被調用來接收數據,在這個例子中接收的是字節。

下面是handler的實現,其實現的功能是將客戶端發給服務器的數據返回給客戶端:

package netty.example;  
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);  
    }  
    @Override  
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {  
        ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);  
    }  
    @Override  
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {  
        cause.printStackTrace();  
        ctx.close();  
    }  
}

Netty使用多個ChannelHandler來達到對事件處理的分離,由於能夠很容易的添加、更新、刪除業務邏輯處理handler。Handler很簡單,它的每一個方法均可以被重寫,它的全部的方法中只有channelRead方法是必需要重寫的。

2.1.3 捕獲異常

 重寫ChannelHandler的exceptionCaught方法能夠捕獲服務器的異常,好比客戶端鏈接服務器後強制關閉,服務器會拋出"客戶端主機強制關閉錯誤",經過重寫exceptionCaught方法就能夠處理異常,好比發生異常後關閉ChannelHandlerContext。

2.2 編寫應答程序的客戶端

服務器寫好了,如今來寫一個客戶端鏈接服務器。應答程序的客戶端包括如下幾步:

鏈接服務器

寫數據到服務器

等待接受服務器返回相同的數據

關閉鏈接

2.2.1 引導客戶端

引導客戶端啓動和引導服務器很相似,客戶端需同時指定host和port來告訴客戶端鏈接哪一個服務器。看下面代碼:

package netty.example;  
import io.netty.bootstrap.Bootstrap;  
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.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;  
    private final int port;  
    public EchoClient(String host, int port) {  
        this.host = host;  
        this.port = port;  
    }  
    public void start() throws Exception {  
        EventLoopGroup group = new NioEventLoopGroup();  
        try {  
            Bootstrap b = new Bootstrap();  
            b.group(group).channel(NioSocketChannel.class).remoteAddress(new InetSocketAddress(host, port))  
                    .handler(new ChannelInitializer<SocketChannel>() {  
                        @Override  
                        protected void initChannel(SocketChannel ch) throws Exception {  
                            ch.pipeline().addLast(new EchoClientHandler());  
                        }  
                    });  
            ChannelFuture f = b.connect().sync();  
            f.channel().closeFuture().sync();  
        } finally {  
            group.shutdownGracefully().sync();  
        }  
    }  
    public static void main(String[] args) throws Exception {  
        new EchoClient("localhost", 20000).start();  
    }  
}

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

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

建立EventLoopGroup對象並設置到Bootstrap中,EventLoopGroup能夠理解爲是一個線程池,這個線程池用來處理鏈接、接受數據、發送數據

建立InetSocketAddress並設置到Bootstrap中,InetSocketAddress是指定鏈接的服務器地址

添加一個ChannelHandler,客戶端成功鏈接服務器後就會被執行

調用Bootstrap.connect()來鏈接服務器

最後關閉EventLoopGroup來釋放資源

2.2.2 實現客戶端的業務邏輯

和編寫服務器的ChannelHandler同樣,在這裏將自定義一個繼承SimpleChannelInboundHandler的ChannelHandler來處理業務

經過重寫父類的三個方法來處理感興趣的事件:

channelActive():客戶端鏈接服務器後被調用

channelRead0():從服務器接收到數據後調用

exceptionCaught():發生異常時被調用

package netty.example;  
import io.netty.buffer.ByteBuf;  
import io.netty.buffer.ByteBufUtil;  
import io.netty.buffer.Unpooled;  
import io.netty.channel.ChannelHandlerContext;  
import io.netty.channel.SimpleChannelInboundHandler;  
import io.netty.util.CharsetUtil;  
public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {  
    @Override  
    public void channelActive(ChannelHandlerContext ctx) throws Exception {  
        ctx.write(Unpooled.copiedBuffer("Netty rocks!",CharsetUtil.UTF_8));  
    }  
    @Override  
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {  
        System.out.println("Client received: " + ByteBufUtil.hexDump(msg.readBytes(msg.readableBytes())));  
    }  
    @Override  
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {  
        cause.printStackTrace();  
        ctx.close();  
    }  
}

可能你會問爲何在這裏使用的是SimpleChannelInboundHandler而不使用ChannelInboundHandlerAdapter?

緣由是ChannelInboundHandlerAdapter在處理完消息後須要負責釋放資源。在這裏將調用ByteBuf.release()來釋放資源。SimpleChannelInboundHandler會在完成channelRead0後釋放消息,這是經過Netty處理全部消息的ChannelHandler實現了ReferenceCounted接口達到的。

爲何在服務器中不使用SimpleChannelInboundHandler呢?

由於服務器要返回相同的消息給客戶端,在服務器執行完成寫操做以前不能釋放調用讀取到的消息,由於寫操做是異步的,一旦寫操做完成後,Netty中會自動釋放消息。

2.3 編譯和運行echo(應答)程序客戶端和服務器

相關文章
相關標籤/搜索