本章介紹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(應答)程序客戶端和服務器