【Netty】Netty傳輸

leesf發表於2017-05-23

一、前言

  在簡單學習了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();
        }
    }
}
OIO

  說明:其中,在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

  說明: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的OIO

  可以看到上述程式碼很多都是和之前示例中使用的程式碼相同,再來看看在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

  可以看到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中的傳輸細節,瞭解了多種不同的傳輸方式,以及其和不同傳輸協議之間的關係,也謝謝各位園友的觀看~

相關文章