一、前言
在簡單學習了Netty中的元件後,接著學習Netty中資料的傳輸細節。
二、傳輸
2.1 傳輸示例
Netty中的資料傳輸都是使用的位元組型別,下面通過一個例項進行說明,該例項中伺服器接受請求,然後向客戶端傳送一個Hi,最後關閉連線。下面是不同方式的實現。
1. OIO方式
OIO與NIO對應,使用阻塞式的IO處理,其服務端程式碼如下
package com.hust.grid.leesf.chapter4; import java.io.IOException; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; import java.nio.charset.Charset; public class PlainOioServer { public void serve(int port) throws IOException { final ServerSocket socket = new ServerSocket(port); try { for (;;) { final Socket clientSocket = socket.accept(); System.out.println("Accepted connection from: " + clientSocket); new Thread(new Runnable() { @Override public void run() { OutputStream out; try { out = clientSocket.getOutputStream(); out.write("Hi!\r\n".getBytes(Charset.forName("UTF-8"))); out.flush(); clientSocket.close(); } catch (IOException e) { e.printStackTrace(); try { clientSocket.close(); } catch (IOException ex) { // ignore on close } } } }).start(); } } catch (IOException e) { e.printStackTrace(); } } }
說明:其中,在for迴圈中會不斷的去監聽是否有新請求到達,當有請求到達後,初始化一個新的執行緒去處理,完成向客戶端傳送Hi字串,最後關閉連線。使用OIO方式的效能較差,擴充套件性也不好,需要使用非同步方式處理。
2. NIO方式
package com.hust.grid.leesf.chapter4; import java.io.IOException; 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; import java.util.Set; public class PlainNioServer { public void serve(int port) throws IOException { ServerSocketChannel serverChannel = ServerSocketChannel.open(); serverChannel.configureBlocking(false); ServerSocket ss = serverChannel.socket(); InetSocketAddress address = new InetSocketAddress(port); ss.bind(address); Selector selector = Selector.open(); serverChannel.register(selector, SelectionKey.OP_ACCEPT); final ByteBuffer msg = ByteBuffer.wrap("Hi!\r\n".getBytes()); for (;;) { try { selector.select(); } catch (IOException ex) { ex.printStackTrace(); // handle exception break; } Set<SelectionKey> readyKeys = selector.selectedKeys(); //5 Iterator<SelectionKey> iterator = readyKeys.iterator(); while (iterator.hasNext()) { SelectionKey key = iterator.next(); iterator.remove(); try { if (key.isAcceptable()) { //6 ServerSocketChannel server = (ServerSocketChannel)key.channel(); SocketChannel client = server.accept(); client.configureBlocking(false); client.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ, msg.duplicate()); //7 System.out.println( "Accepted connection from " + client); } if (key.isWritable()) { //8 SocketChannel client = (SocketChannel)key.channel(); ByteBuffer buffer = (ByteBuffer)key.attachment(); while (buffer.hasRemaining()) { if (client.write(buffer) == 0) { //9 break; } } client.close(); //10 } } catch (IOException ex) { key.cancel(); try { key.channel().close(); } catch (IOException cex) { // ignore on close } } } } } }
說明:NIO方法使用了Selector和Channel等元件,使用Selector來處理多個Channel,其程式碼與OIO程式碼幾乎是完全不相同。
3. Netty的OIO方式
當使用Netty框架處理時,並且採用OIO的方式,其程式碼如下
package com.hust.grid.leesf.chapter4; 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.oio.OioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.oio.OioServerSocketChannel; import java.net.InetSocketAddress; import java.nio.charset.Charset; public class NettyOioServer { public void server(int port) throws Exception { final ByteBuf buf = Unpooled.unreleasableBuffer( Unpooled.copiedBuffer("Hi!\r\n", Charset.forName("UTF-8"))); EventLoopGroup group = new OioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(group) .channel(OioServerSocketChannel.class) .localAddress(new InetSocketAddress(port)) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { ctx.writeAndFlush(buf.duplicate()).addListener(ChannelFutureListener.CLOSE); } }); } }); ChannelFuture f = b.bind().sync(); f.channel().closeFuture().sync(); } finally { group.shutdownGracefully().sync(); } } }
可以看到上述程式碼很多都是和之前示例中使用的程式碼相同,再來看看在Netty框架下使用NIO時的處理方式。
4. Netty的NIO方式
package com.hust.grid.leesf.chapter4; 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.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import java.net.InetSocketAddress; import java.nio.charset.Charset; public class NettyNioServer { public void server(int port) throws Exception { final ByteBuf buf = Unpooled.unreleasableBuffer( Unpooled.copiedBuffer("Hi!\r\n", Charset.forName("UTF-8"))); NioEventLoopGroup group = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(new NioEventLoopGroup(), new NioEventLoopGroup()) .channel(NioServerSocketChannel.class) .localAddress(new InetSocketAddress(port)) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { ctx.writeAndFlush(buf.duplicate()).addListener(ChannelFutureListener.CLOSE); } }); } }); ChannelFuture f = b.bind().sync(); f.channel().closeFuture().sync(); } finally { group.shutdownGracefully().sync(); } } }
可以看到Netty框架下的NIO和OIO的程式碼基本相同。
2.2 傳輸細節
傳輸的核心是Channel介面,其繼承結構圖如下所示
其中,Channel繼承了AttributeMap和Comparable介面,其包含ChannelPipeline和ChannelConfig兩個例項,ChannelConfig包含了所有的配置資訊,ChannelPipeline包含了所有的ChannelHandler例項,其中用於存放使用者的處理邏輯。典型的ChannelHandler用法如下
· 將資料型別進行轉化
· 提供異常通知
· 提供Channel變為活動或非活動的通知
· 提供當Channel在EventLoopGroup中註冊或者登出時的通知
· 提供使用者定義事件的通知
Netty中的Channel實現是執行緒安全的,所以在多執行緒環境中可安全使用。
2.3 傳輸方案
Netty提供了多種傳輸方案,你可根據應用的不同選擇合適的傳輸方案。
1. NIO-非阻塞型I/O
NIO提供了完全非同步的IO實現,其使用基於選擇器的API,選擇器的核心概念是將其作為登錄檔,當通道的狀態變化時會接受到通知,可能有的變化狀態如下
· 一個新通道被接受並已準備好
· 一個通道的連線已經完成
· 一個通道已經有準備好讀取的資料
· 一個通道可以寫入資料
當應用程式對狀態的變化做出反應後,會重置選擇器並重復處理,根據不同的狀態給出不同的響應,選擇操作有如下四種型別OP_ACCEPT 、OP_CONNECT、OP_READ 、OP_WRITE,其中選擇器的處理流程如下圖所示
2. Epoll-Linux的本機非阻塞傳輸
Netty可在任何系統上執行,但對於不同的系統會有不同的折中,Linux系統中的epoll具有高可擴充套件的I/O的事件通知,Linux上的JDK的NIO則是基於epoll,當使用epoll取代NIO時,可以使用Netty中的EpollEventLoopGroup取代NioEventLoopGroup,使用EpollServerSocketChannel.class取代NioServerSocketChannel.class。
3. OIO-阻塞型I/O
Netty的OIO是一種妥協方案,其使用JAVA中原生態的舊的API,其是同步阻塞的。在java.net的API中,通常使用一個執行緒接受來自指定埠的請求,當建立一個套接字時,就會建立一個新的執行緒來進行處理,其處理流程圖如下圖所示
4. 在JVM內進行通訊的本地傳輸
Netty提供了在同一個JVM中的客戶端與服務端之間的非同步通訊,在此傳輸中,與伺服器通道相關聯的SocketAddress不繫結到物理網路地址,相反,當伺服器執行時,其儲存在登錄檔中,關閉時取消註冊。因為傳輸不能接受真正的網路流量,所以它不能與其他傳輸實現互操作。
5. 嵌入式傳輸
Netty還提供了一個額外的傳輸,其可以將ChannelHandler作為幫助類嵌入其他的ChannelHandler中,以這種方式,你無需修改其內部程式碼便能擴充套件ChannelHandler的功能。
2.4 傳輸示例
並非所有的傳輸都支援所有的傳輸協議,下表是傳輸方式與傳輸協議之間的關係
而對於不同的應用,可根據下表選擇合適的傳輸方式
三、總結
本篇博文講解了Netty中的傳輸細節,瞭解了多種不同的傳輸方式,以及其和不同傳輸協議之間的關係,也謝謝各位園友的觀看~