本章介紹
- 獲得Netty4最新的版本號
- 設定執行環境,以構建和執行netty程式
- 建立一個基於Netty的server和client
- 攔截和處理異常
- 編制和執行Nettyserver和client
本章將簡介Netty的核心概念,這個狠心概念就是學習Netty是怎樣攔截和處理異常。對於剛開始學習netty的讀者。利用netty的異常攔截機制來除錯程式問題非常有幫助。本章還會介紹其它一些核心概念。如server和client的啟動以及分離通道的處理程式。本章學習一些基礎以便後面章節的深入學習。
本章中將編寫一個基於netty的server和client來互相通訊,我們首先來設定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 Nettyclient和server概述
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYWJjX2tleQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">
- client連線到server
- 建立連線後,傳送或接收資料
- server處理全部的client連線
從上圖中能夠看出,server會寫資料到client而且處理多個client的併發連線。從理論上來說,限制程式效能的因素僅僅有系統資源和JVM。為了方便理解,這裡舉了個生活樣例。在山谷或高山上大聲喊,你會聽見回聲。回聲是山返回的;在這個樣例中,你是client。山是server。喊的行為就類似於一個Nettyclient將資料傳送到server,聽到回聲就類似於server將同樣的資料返回給你,你離開山谷就斷開了連線。可是你能夠返回進行重連server而且能夠傳送很多其它的資料。
儘管將同樣的資料返回給client不是一個典型的樣例。可是client和server之間資料的來來回回的傳輸和這個樣例是一樣的。本章的樣例會證明這一點,它們會越來越複雜。
接下來的幾節將帶著你完畢基於Netty的client和server的應答程式。
2.3 編寫一個應答server
寫一個Nettyserver主要由兩部分組成:
- 配置server功能,如執行緒、port
- 實現server處理程式,它包括業務邏輯,決定當有一個請求連線或接收資料時該做什麼
2.3.1 啟動server
通過建立ServerBootstrap物件來啟動server。然後配置這個物件的相關選項。如port、執行緒模式、事件迴圈,而且加入邏輯處理程式用來處理業務邏輯(以下是個簡單的應答server樣例)
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();
} }
從上面這個簡單的server樣例能夠看出,啟動server應先建立一個ServerBootstrap物件。由於使用NIO,所以指定NioEventLoopGroup來接受和處理新連線。指定通道型別為NioServerSocketChannel,設定InetSocketAddress讓server監聽某個埠已等待client連線。
接下來,呼叫childHandler放來指定連線後呼叫的ChannelHandler。這種方法傳ChannelInitializer型別的引數。ChannelInitializer是個抽象類,所以須要實現initChannel方法,這種方法就是用來設定ChannelHandler。
最後繫結server等待直到繫結完畢。呼叫sync()方法會堵塞直到server完畢繫結,然後server等待通道關閉。由於使用sync(),所以關閉操作也會被堵塞。如今你能夠關閉EventLoopGroup和釋放全部資源,包含建立的執行緒。
這個樣例中使用NIO,由於它是眼下最經常使用的傳輸方式。你可能會使用NIO非常長時間。可是你能夠選擇不同的傳輸實現。比如。這個樣例使用OIO方式傳輸,你須要指定OioServerSocketChannel。Netty框架中實現了多重傳輸方式,將再後面講述。
本小節重點內容:
- 建立ServerBootstrap例項來引導繫結和啟動server
- 建立NioEventLoopGroup物件來處理事件,如接受新連線、接收資料、寫資料等等
- 指定InetSocketAddress。server監聽此port
- 設定childHandler執行全部的連線請求
- 都設定完成了,最後呼叫ServerBootstrap.bind() 方法來繫結server
2.3.2 實現server業務邏輯
Netty使用futures和回撥概念。它的設計同意你處理不同的事件型別。更具體的介紹將再後面章節講述。可是我們能夠接收資料。
你的channel handler必須繼承ChannelInboundHandlerAdapter而且重寫channelRead方法,這種方法在不論什麼時候都會被呼叫來接收資料,在這個樣例中接收的是位元組。
以下是handler的實現,事實上現的功能是將client發給server的資料返回給client:
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使用多個Channel Handler來達到對事件處理的分離,由於能夠非常容的加入、更新、刪除業務邏輯處理handler。
Handler非常easy,它的每一個方法都能夠被重寫,它的全部的方法中僅僅有channelRead方法是必需要重寫的。
2.3.3 捕獲異常
2.4 編寫應答程式的client
- 連線server
- 寫資料到server
- 等待接受server返回同樣的資料
- 關閉連線
2.4.1 引導client
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();
}
}
建立啟動一個client包括以下幾步:
- 建立Bootstrap物件用來引導啟動client
- 建立EventLoopGroup物件並設定到Bootstrap中,EventLoopGroup能夠理解為是一個執行緒池。這個執行緒池用來處理連線、接受資料、傳送資料
- 建立InetSocketAddress並設定到Bootstrap中。InetSocketAddress是指定連線的server地址
- 加入一個ChannelHandler,client成功連線server後就會被執行
- 呼叫Bootstrap.connect()來連線server
- 最後關閉EventLoopGroup來釋放資源
2.4.2 實現client的業務邏輯
和編寫server的ChannelHandler一樣,在這裡將自己定義一個繼承SimpleChannelInboundHandler的ChannelHandler來處理業務;通過重寫父類的三個方法來處理感興趣的事件:
- channelActive():client連線server後被呼叫
- channelRead0():從server接收到資料後呼叫
- 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介面達到的。
2.5 編譯和執行echo(應答)程式client和server
注意,netty4須要jdk1.7+。
本人測試,能夠正通常執行。
2.6 總結一下
本章介紹如何編寫基於一個簡單的Netty的server和client和傳送資料通訊。它描述瞭如何建立server和client以及Netty異常處理機制。