Netty In Action中文版 - 第四章:Transports(傳輸)

來源:互聯網
上載者:User

標籤:util   用例   編程經驗   reference   ase   figure   工作   本地   作用   

 本章內容

  • Transports(傳輸)
  • NIO(non-blocking IO,New IO), OIO(Old IO,blocking IO), Local(本地), Embedded(嵌入式)
  • Use-case(用例)
  • APIs(介面)
        網路應用程式一個非常重要的工作是資料轉送。資料轉送的過程不一樣取決是使用哪種交通工具,可是傳輸的方式是一樣的:都是以位元組碼傳輸。

Java開發網路程式資料轉送的過程和方式是被抽象了的,我們不須要關注底層介面。僅僅須要使用Java API或其它網路架構如Netty就能達到資料轉送的目的。

發送資料和接收資料都是位元組碼。Nothing more,nothing less。

        假設你以前使用Java提供的網路介面工作過,你可能已經遇到過想從堵塞傳輸切換到非堵塞傳輸的情況,這樣的切換是比較困難的,由於堵塞IO和非堵塞IO使用的API有非常大的差異。Netty提供了上層的傳輸實現介面使得這樣的情況變得簡單。

我們能夠讓所寫的代碼儘可能通用,而不會依賴一些實現相關的APIs。當我們想切換傳輸方式的時候不須要花非常大的精力和時間來重構代碼。

        本章將介紹統一的API以及怎樣使用它們,會拿Netty的API和Java的API做比較來告訴你為什麼Netty能夠更easy的使用。

本章也提供了一些優質的用例代碼,以便最佳使用Netty。使用Netty不須要其它的網路架構或網路編程經驗。若有則僅僅是對理解netty有協助。但不是必要的。以下讓我們來看看真是世界裡的傳輸工作。

4.1 案例研究:切換傳輸方式
        為了讓你想象怎樣運輸,我會從一個簡單的應用程式開始,這個應用程式什麼都不做,僅僅是接受client串連並發送“Hi!”字串訊息到client,發送完了就中斷連線。

我不會具體解說這個過程的實現,它僅僅是一個範例。

4.1.1 使用Java的I/O和NIO
        我們將不用Netty實現這個範例,以下代碼是使用堵塞IO實現的範例:
package netty.in.action;import java.io.IOException;import java.io.OutputStream;import java.net.ServerSocket;import java.net.Socket;import java.nio.charset.Charset;/** * Blocking networking without Netty * @author c.k * */public class PlainOioServer {public void server(int port) throws Exception {//bind server to portfinal ServerSocket socket = new ServerSocket(port);try {while(true){//accept connectionfinal Socket clientSocket = socket.accept();System.out.println("Accepted connection from " + clientSocket);//create new thread to handle connectionnew Thread(new Runnable() {@Overridepublic void run() {OutputStream out;try{out = clientSocket.getOutputStream();//write message to connected clientout.write("Hi!\r\n".getBytes(Charset.forName("UTF-8")));out.flush();//close connection once message written and flushedclientSocket.close();}catch(IOException e){try {clientSocket.close();} catch (IOException e1) {e1.printStackTrace();}}}}).start();//start thread to begin handling}}catch(Exception e){e.printStackTrace();socket.close();}}}
上面的方式非常簡潔。可是這樣的堵塞模式在大串連數的情況就會有非常嚴重的問題,如用戶端連線逾時,伺服器響應嚴重延遲。為瞭解決這樣的情況,我們能夠使用非同步網路處理全部的並發串連,但問題在於NIO和OIO的API是全然不同的,所以一個用OIO開發的網路應用程式想要使用NIO重構代碼差點兒是又一次開發。


        以下代碼是使用Java NIO實現的範例:

package netty.in.action;import java.net.InetSocketAddress;import java.net.ServerSocket;import java.nio.ByteBuffer;import java.nio.channels.SelectionKey;import java.nio.channels.Selector;import java.nio.channels.ServerSocketChannel;import java.nio.channels.SocketChannel;import java.util.Iterator;/** * Asynchronous networking without Netty * @author c.k * */public class PlainNioServer {public void server(int port) throws Exception {System.out.println("Listening for connections on port " + port);//open Selector that handles channelsSelector selector = Selector.open();//open ServerSocketChannelServerSocketChannel serverChannel = ServerSocketChannel.open();//get ServerSocketServerSocket serverSocket = serverChannel.socket();//bind server to portserverSocket.bind(new InetSocketAddress(port));//set to non-blockingserverChannel.configureBlocking(false);//register ServerSocket to selector and specify that it is interested in new accepted clientsserverChannel.register(selector, SelectionKey.OP_ACCEPT);final ByteBuffer msg = ByteBuffer.wrap("Hi!\r\n".getBytes());while (true) {//Wait for new events that are ready for process. This will block until something happensint n = selector.select();if (n > 0) {//Obtain all SelectionKey instances that received eventsIterator<SelectionKey> iter = selector.selectedKeys().iterator();while (iter.hasNext()) {SelectionKey key = iter.next();iter.remove();try {//Check if event was because new client ready to get acceptedif (key.isAcceptable()) {ServerSocketChannel server = (ServerSocketChannel) key.channel();SocketChannel client = server.accept();System.out.println("Accepted connection from " + client);client.configureBlocking(false);//Accept client and register it to selectorclient.register(selector, SelectionKey.OP_WRITE, msg.duplicate());}//Check if event was because socket is ready to write dataif (key.isWritable()) {SocketChannel client = (SocketChannel) key.channel();ByteBuffer buff = (ByteBuffer) key.attachment();//write data to connected clientwhile (buff.hasRemaining()) {if (client.write(buff) == 0) {break;}}client.close();//close client}} catch (Exception e) {key.cancel();key.channel().close();}}}}}}
如你所見。即使它們實現的功能是一樣,可是代碼全然不同。以下我們將用Netty來實現同樣的功能。


4.1.2 Netty中使用I/O和NIO
        以下代碼是使用Netty作為網路架構編寫的一個堵塞IO範例:
package netty.in.action;import java.net.InetSocketAddress;import io.netty.bootstrap.ServerBootstrap;import io.netty.buffer.ByteBuf;import io.netty.buffer.Unpooled;import io.netty.channel.Channel;import io.netty.channel.ChannelFuture;import io.netty.channel.ChannelFutureListener;import io.netty.channel.ChannelHandlerContext;import io.netty.channel.ChannelInboundHandlerAdapter;import io.netty.channel.ChannelInitializer;import io.netty.channel.EventLoopGroup;import io.netty.channel.nio.NioEventLoopGroup;import io.netty.channel.socket.oio.OioServerSocketChannel;import io.netty.util.CharsetUtil;public class NettyOioServer {public void server(int port) throws Exception {final ByteBuf buf = Unpooled.unreleasableBuffer(Unpooled.copiedBuffer("Hi!\r\n", CharsetUtil.UTF_8));//事件迴圈組EventLoopGroup group = new NioEventLoopGroup();try {//用來引導server配置ServerBootstrap b = new ServerBootstrap();//使用OIO堵塞模式b.group(group).channel(OioServerSocketChannel.class).localAddress(new InetSocketAddress(port))//指定ChannelInitializer初始化handlers.childHandler(new ChannelInitializer<Channel>() {@Overrideprotected void initChannel(Channel ch) throws Exception {//加入一個“入站”handler到ChannelPipelinech.pipeline().addLast(new ChannelInboundHandlerAdapter() {@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {//串連後,寫訊息到client,寫完後便關閉串連ctx.writeAndFlush(buf.duplicate()).addListener(ChannelFutureListener.CLOSE);}});}});//綁定server接受串連ChannelFuture f = b.bind().sync();f.channel().closeFuture().sync();} catch (Exception e) {//釋放全部資源group.shutdownGracefully();}}}
上面代碼實現功能一樣,但結構清晰明了。這僅僅是Netty的優勢之中的一個。

4.1.3 Netty中實現非同步支援
        以下代碼是使用Netty實現非同步。能夠看出使用Netty由OIO切換到NIO是很的方便。

package netty.in.action;import io.netty.bootstrap.ServerBootstrap;import io.netty.buffer.ByteBuf;import io.netty.buffer.Unpooled;import io.netty.channel.ChannelFuture;import io.netty.channel.ChannelFutureListener;import io.netty.channel.ChannelHandlerContext;import io.netty.channel.ChannelInboundHandlerAdapter;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.NioServerSocketChannel;import io.netty.util.CharsetUtil;import java.net.InetSocketAddress;public class NettyNioServer {public void server(int port) throws Exception {final ByteBuf buf = Unpooled.unreleasableBuffer(Unpooled.copiedBuffer("Hi!\r\n", CharsetUtil.UTF_8));// 事件迴圈組EventLoopGroup group = new NioEventLoopGroup();try {// 用來引導server配置ServerBootstrap b = new ServerBootstrap();// 使用NIO非同步模式b.group(group).channel(NioServerSocketChannel.class).localAddress(new InetSocketAddress(port))// 指定ChannelInitializer初始化handlers.childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {// 加入一個“入站”handler到ChannelPipelinech.pipeline().addLast(new ChannelInboundHandlerAdapter() {@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {// 串連後,寫訊息到client。寫完後便關閉串連ctx.writeAndFlush(buf.duplicate()).addListener(ChannelFutureListener.CLOSE);}});}});// 綁定server接受串連ChannelFuture f = b.bind().sync();f.channel().closeFuture().sync();} catch (Exception e) {// 釋放全部資源group.shutdownGracefully();}}}
由於Netty使用同樣的API來實現每一個傳輸,它並不關心你使用什麼來實現。Netty通過操作Channel介面和ChannelPipeline、ChannelHandler來實現傳輸。

4.2 Transport API
        傳輸API的核心是Channel介面。它用於全部出站的操作。Channel介面的類階層例如以下
如所看到的,每一個Channel都會分配一個ChannelPipeline和ChannelConfig。

ChannelConfig負責設定並儲存配置。並同意在執行期間更新它們。

傳輸一般有特定的配置設定。僅僅作用於傳輸,沒有其它的實現。ChannelPipeline容納了使用的ChannelHandler執行個體,這些ChannelHandler將處理通道傳遞的“入站”和“出站”資料。ChannelHandler的實現同意你改變資料狀態和資料轉送,本書有章節具體解說ChannelHandler,ChannelHandler是Netty的重點概念。

        如今我們能夠使用ChannelHandler做以下一些事情:
  • 資料轉送時,將資料從一種格式轉換到還有一種格式
  • 異常通知
  • Channel變為有效或無效時獲得通知
  • Channel被注冊或從EventLoop中登出時獲得通知
  • 通知使用者特定事件
        這些ChannelHandler執行個體加入到ChannelPipeline中,在ChannelPipeline中按順序逐個運行。

它類似於一個鏈條,有使用過Servlet的讀者可能會更easy理解。

        ChannelPipeline實現了攔截過濾器模式,這意味著我們串連不同的ChannelHandler來攔截並處理經過ChannelPipeline的資料或事件。能夠把ChannelPipeline想象成UNIX管道。它同意不同的命令鏈(ChannelHandler相當於命令)。你還能夠在執行時依據須要加入ChannelHandler執行個體到ChannelPipeline或從ChannelPipeline中刪除,這能協助我們構建高度靈活的Netty程式。此外。訪問指定的ChannelPipeline和ChannelConfig,你能在Channel自身上進行操作。

Channel提供了非常多方法。例如以下列表:

  • eventLoop(),返回分配給Channel的EventLoop
  • pipeline(),返回分配給Channel的ChannelPipeline
  • isActive()。返回Channel是否啟用,已啟用說明與遠端連線對等
  • localAddress()。返回已綁定的本地SocketAddress
  • remoteAddress()。返回已綁定的遠程SocketAddress
  • write(),寫資料到遠程client。資料通過ChannelPipeline傳輸過去
後面會越來越熟悉這些方法,如今僅僅須要記住我們的操作都是在同樣的介面上執行。Netty的高靈活性讓你能夠以不同的傳輸實現進行重構。

        寫資料到遠程已串連client能夠調用Channel.write()方法。例如以下代碼:
Channel channel = ...//Create ByteBuf that holds data to writeByteBuf buf = Unpooled.copiedBuffer("your data", CharsetUtil.UTF_8);//Write dataChannelFuture cf = channel.write(buf);//Add ChannelFutureListener to get notified after write completescf.addListener(new ChannelFutureListener() {@Overridepublic void operationComplete(ChannelFuture future) {//Write operation completes without errorif (future.isSuccess()) {System.out.println(.Write successful.);} else {//Write operation completed but because of errorSystem.err.println(.Write error.);future.cause().printStacktrace();}}});
        Channel是安全執行緒(thread-safe)的,它能夠被多個不同的安全執行緒的操作,在多線程環境下,全部的方法都是安全的。正由於Channel是安全的,我們儲存對Channel的引用,並在學習的時候使用它寫入資料到遠程已串連的client,使用多線程也是如此。以下的代碼是一個簡單的多線程範例:
final Channel channel = ...//Create ByteBuf that holds data to writefinal ByteBuf buf = Unpooled.copiedBuffer("your data",CharsetUtil.UTF_8);//Create Runnable which writes data to channelRunnable writer = new Runnable() {@Overridepublic void run() {channel.write(buf.duplicate());}};//Obtain reference to the Executor which uses threads to execute tasksExecutor executor = Executors.newChachedThreadPool();// write in one thread//Hand over write task to executor for execution in threadexecutor.execute(writer);// write in another thread//Hand over another write task to executor for execution in threadexecutor.execute(writer);
此外,這樣的方法保證了寫入的訊息以同樣的順序通過寫入它們的方法。

想瞭解全部方法的使用能夠參考Netty API文檔。

4.3 Netty包括的傳輸實現        Netty內建了一些傳輸協議的實現,儘管沒有支援全部的傳輸協議,可是其內建的已足夠我們來使用。

Netty應用程式的傳輸協議依賴於底層協議,本節我們將學習Netty中的傳輸協議。

        Netty中的傳輸方式有例如以下幾種:
  • NIO,io.netty.channel.socket.nio,基於java.nio.channels的工具包,使用選取器作為基礎的方法。
  • OIO,io.netty.channel.socket.oio,基於java.net的工具包,使用堵塞流。
  • Local。io.netty.channel.local,用來在虛擬機器之間本地通訊。


  • Embedded。io.netty.channel.embedded。嵌入傳輸,它同意在沒有真正網路的運輸中使用ChannelHandler,能夠很實用的來測試ChannelHandler的實現。
4.3.1 NIO - Nonblocking I/O
        NIO傳輸是眼下最經常使用的方式,它通過使用選取器提供了全然非同步方式操作全部的I/O,NIO從Java 1.4才被提供。NIO中。我們能夠注冊一個通道或獲得某個通道的改變的狀態。通道狀態有以下幾種改變:
  • 一個新的Channel被接受並已準備好
  • Channel串連完畢
  • Channel中有資料並已準備好讀取
  • Channel發送資料出去
        處理完改變的狀態後需又一次設定他們的狀態,用一個線程來檢查是否有已準備好的Channel。假設有則運行相關事件。在這裡可能僅僅同一時候一個注冊的事件而忽略其它的。

選取器所支援的操作在SelectionKey中定義,詳細例如以下:

  • OP_ACCEPT。有新串連時得到通知
  • OP_CONNECT,串連完畢後得到通知
  • OP_READ。準備好讀取資料時得到通知
  • OP_WRITE,寫入資料到通道時得到通知
        Netty中的NIO傳輸就是基於這種模型來接收和發送資料。通過封裝將自己的介面提供給使用者使用,這全然隱藏了內部實現。

如前面所說,Netty隱藏內部的實現細節。將抽象出來的API暴露出來供使用,以下是處理流程圖:


        NIO在處理過程也會有一定的延遲,若串連數不大的話,延遲一般在毫秒級。可是其輸送量依舊比OIO模式的要高。Netty中的NIO傳輸是“zero-file-copy”,也就是零檔案複製,這樣的機制能夠讓程式速度更快,更高效的從檔案系統中傳輸內容,零複製就是我們的應用程式不會將發送的資料先拷貝到JVM堆棧在進行處理,而是直接從核心空間操作。接下來我們將討論OIO傳輸。它是堵塞的。

4.3.2 OIO - Old blocking I/O
        OIO就是java中提供的Socket介面。java最開始僅僅提供了堵塞的Socket,堵塞會導致程式效能低。以下是OIO的處理流程圖。若想具體瞭解,能夠參閱其它相關資料。



4.3.3 Local - In VM transport
         Netty包括了本地傳輸,這個傳輸實現使用同樣的API用於虛擬機器之間的通訊,傳輸是全然非同步。每一個Channel使用唯一的SocketAddress,client通過使用SocketAddress進行串連,在server會被注冊為長期執行,一旦通道關閉。它會自己主動登出,client無法再使用它。        串連到本地傳輸server的行為與其它的傳輸實現差點兒是同樣的。須要注意的一個重點是僅僅能在本地的server和client上使用它們。Local未綁定不論什麼Socket,值提供JVM進程之間的通訊。4.3.4 Embedded transport
        Netty還包含嵌入傳輸,與之前講述的其它傳輸實現比較。它是不是一個真的傳輸呢?若不是一個真的傳輸,我們用它能夠做什麼呢?Embedded transport同意更easy的使用不同的ChannelHandler之間的互動,這也更easy嵌入到其它的ChannelHandler執行個體並像一個輔助類一樣使用它們。它一般用來測試特定的ChannelHandler實現。也能夠在ChannelHandler中又一次使用一些ChannelHandler來進行擴充,為了實現這種目的。它內建了一個詳細的Channel實現。即:EmbeddedChannel。4.4 每種傳輸方式在什麼時候使用?
        不多加贅述。看以下列表:
  • OIO。在低串連數、須要低延遲時、堵塞時使用
  • NIO。在高串連數時使用
  • Local。在同一個JVM內通訊時使用
  • Embedded,測試ChannelHandler時使用

Netty In Action中文版 - 第四章:Transports(傳輸)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.