Netty-02 Java IO (BIO), a pseudo-asynchronous IO, NIO, NIO2 (AIO), Netty

View the complete code: https://gitee.com/firewolf/java-io/tree/master/java-io following java-bio, java-nio, java-aio

Billion, Introduction

In Java programming IO are the following: BIO, pseudo-asynchronous IO, NIO, AIO, the following comparison:
Here Insert Picture Description

First, the traditional BIO

(A) BIO Introduction

When connected Socket Communications responsible for initiating the operation, after a successful connection, the two sides are synchronized group blocked by the input and output streams; in synchronous blocking based on the traditional model of development, ServerSocket responsible for binding the IP address and start listening port.
FIG communication model:
Here Insert Picture Description
usually consists of a Acceptor thread is responsible for listening client connection, the client receives the request to create a new thread for each client link processing, after processing by the output data stream is returned to the client, the destruction of the thread .
The biggest problem is the lack of flexibility scalability, and server thread count the number of client-side rendering 1: 1 ratio, will result in the rapid expansion of virtual machine thread, causing the system efficiency.

(B) programming examples

1. server

The main server is to create a listening socket to listen for the client's request, to create a separate thread for access to input and output processing incoming socket

  1. Start the class code as follows
package com.firewolf.java.io.bio.server;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 作者:刘兴 时间:2019/5/8
 **/
public class TimeServer {

  public static void main(String[] args) throws IOException {
    int port = 8080;
    ServerSocket serverSocket = null;
    try {
      serverSocket = new ServerSocket(port); //1.创建socket监听,如果端口没有被占用,则会启动成功
      System.out.println("时间服务器已经启动了,监听端口为" + port + "......");
      Socket socket = null;
      while (true) {
        socket = serverSocket.accept(); //2. 循环等待客户端进行连接,没有客户端连接进来的时候在此阻塞
        System.out.println("有客户端接入,");
        new Thread(new TimeServerHandler(socket)).start(); //3.开启一个新的线程处理连接进来的客户端
      }

    } finally {
      if (serverSocket != null) {
        serverSocket.close();
      }
    }
  }
}

  1. The client data processing interaction code
package com.firewolf.java.io.bio.server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Date;

/**
 * 作者:刘兴 时间:2019/5/8
 **/
public class TimeServerHandler implements Runnable {

  private Socket socket;

  public TimeServerHandler(Socket socket) {
    this.socket = socket;
  }

  @Override
  public void run() {
    BufferedReader in = null;
    PrintWriter out = null;
    try {
      in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
      out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()),true);
      String clientMsg = null;
      while (true) { //1.循环接受客户端的消息
        clientMsg = in.readLine();
        if (clientMsg == null) {
          break;
        }
        System.out.println("收到客户端的消息为: " + clientMsg);
        out.println("当前时间为:" + new Date().toString()); //2.返回信息
      }
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      System.out.println("客户端下线,释放socket....");
      try {
        if (in != null) {
          in.close();
        }
        if (out != null) {
          out.close();
        }
        if (socket != null) {
          socket.close();
        }
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }
}

2. Client

Initiating a connection request message while receiving data returned from the server

package com.firewolf.java.io.bio.client;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Date;

/**
 * 作者:刘兴 时间:2019/5/8
 **/
public class TimeClient {

  public static void main(String[] args) throws Exception {
    Socket socket = new Socket("127.0.0.1", 8080);
    BufferedReader in = null;
    PrintWriter out = null;
    try {
      in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
      out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true);
      out.println("请返回系统当前时间...."); //1.往服务端发送消息
      String serverMsg = in.readLine(); //2.接受服务端返回的消息
      System.out.println("服务端返回的消息为: " + serverMsg);
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      System.out.println("请求结束,主动释放与服务端的连接....");
      try {
        if (in != null) {
          in.close();
        }
        if (out != null) {
          out.close();
        }
        if (socket != null) {
          socket.close();
        }
      } catch (IOException e) {
        e.printStackTrace();
      }
    }

  }

}

In the case of high concurrency, the rear end of the rapid growth of the number of threads, resulting in system performance degradation or even crash, this is the biggest problem synchronous blocking I / O's.

Second, the pseudo-asynchronous IO

(A) a pseudo-asynchronous I / O Introduction

In order to solve the problems faced by a synchronous blocking IO link requires a thread processing was optimized for threading model - the back-end using the thread pool for processing, the formation of the client thread count M: The maximum number of threads in the thread pool for the back-end N Relationship. Where M is much larger than N, the back-end flexibility to control the number of threads, threads to prevent back-end resources due to the massive depletion caused by concurrent problems. Model is as follows:
Here Insert Picture Description

(B) programming examples

Pseudo asynchronous io is some transformation on the basis of BIO, he is to perform every time you start a thread to be changed into a socket after use thread pool to handle, so the only change here server-side code, the client does not transform.

1. Create a thread pool used to start the task

package com.firewolf.java.io.pseudoasync.server;

import com.firewolf.java.io.bio.server.TimeServerHandler;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 作者:刘兴 时间:2019/5/13
 **/
public class TimeServerHandlerExecutor {

  private ExecutorService executorService;


  public TimeServerHandlerExecutor(int maxPoolSize, int queueSize) {
    executorService = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(), maxPoolSize, 120L,
        TimeUnit.SECONDS, new ArrayBlockingQueue<>(queueSize));
  }


  public void executeTask(TimeServerHandler handler){
    executorService.execute(handler);
  }
}

2. The thread pool is created when the service starts, with the socket when to start the task using a thread pool

package com.firewolf.java.io.pseudoasync.server;

import com.firewolf.java.io.bio.server.TimeServerHandler;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 作者:刘兴 时间:2019/5/8
 **/
public class TimeServer {

  public static void main(String[] args) throws IOException {
    int port = 8080;
    ServerSocket serverSocket = null;
    try {
      serverSocket = new ServerSocket(port); //1.创建socket监听,如果端口没有被占用,则会启动成功
      System.out.println("时间服务器已经启动了,监听端口为" + port + "......");
      TimeServerHandlerExecutor timeServerHandlerExecutor = new TimeServerHandlerExecutor(20, 1000);
      Socket socket = null;
      while (true) {
        socket = serverSocket.accept(); //2. 循环等待客户端进行连接,没有客户端连接进来的时候在此阻塞
        System.out.println("有客户端接入,");
        timeServerHandlerExecutor.executeTask(new TimeServerHandler(socket)); //3.使用线程池开启一个新的线程处理连接进来的客户端
      }

    } finally {
      if (serverSocket != null) {
        serverSocket.close();
      }
    }
  }
}

Pseudo Asynchronous IO can prevent the problem from back-end server resources because too many threads lead to depletion, but the front desk will lead to a request to produce obstruction, causing the system efficiency is still not high.

Three, NIO

(A) NIO Profile

NIO understanding are of two kinds,

  • New I / O: The reason is that Java NIO later the new I / O library;
  • Non-Bolck I / O: because the purpose is to make Java NIO non-blocking I / O (personally prefer this argument)
    and BIO in ServerSocket and Socket, like, NIO also provides two different sets of ServerSocketChannel and SocketChannel Sockets channel implementation. Both new channel supports blocking and non-blocking mode, blocking mode using a simple, but good performance, non-blocking just right opposite.

(B) NIO core concepts

1. Buffer Buffer

Buffer is an object that contains data to be written or read out. In NIO, all data are processed using the Buffer. Is essentially an array, generally ByteBuffer, however, he is not just an array, also it provides a structured access to data read and write as well as the maintenance position (limit) and other information. ByteBuffer buffer Buffer is the most common, in fact, Java basic data for each type has a corresponding Buffer buffer, these classes have basically the same functionality, but the data processing is different.

2. Channel Channel

Channel is a channel like a water pipe, the network data is processed using the Channel, and different places flow channels, the flow is unidirectional, and the channel is bidirectional. Flow can only move in one direction (InputStream or OutputStream must be of one), and the channels can be used to read, write, or both simultaneously.

3. Selector multiplexer

Multiplexer NIO Selector is the key to programming, it will continue to Channel poll registered above him, if read or write event occurs on a Channel, the Channel will be in a ready state, polling will be out Selector then you can get the set ready by Channel SelectionKey, perform IO processing.
Selector can poll a plurality Channel, since the use of the epoll jdk () instead of select (), and there is no limit on the maximum number of connections 1024 handles. So we only need one thread to poll Selector, you can access hundreds of thousands of clients.

(C) programming examples

The use of asynchronous IO write a capable group chat applet

1. server

a. a timing chart

Here Insert Picture Description

b. server code

package com.firewolf.java.io.server;

import java.io.IOException;
import java.net.InetSocketAddress;
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.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * 作者:刘兴 时间:2019/5/13
 **/
public class ChatServer {

  private int port; //服务监听端口号

  private ServerSocketChannel serverSocketChannel = null; //服务端Socket


  private Selector selector = null; //多路复用器

  //客户端连接Channel列表
  Map<String, SocketChannel> clientChannelMap = new HashMap<>();

  public ChatServer(int port) {
    this.port = port;
    initServer();
  }

  /**
   * 初始化服务,打开监听端口
   */
  private void initServer() {
    try {
      serverSocketChannel = ServerSocketChannel.open();//打开Socket
      selector = Selector.open(); //创建多路复用
      serverSocketChannel.bind(new InetSocketAddress(port));//绑定端口
      serverSocketChannel.configureBlocking(false);//设置非阻塞
      serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);//准备监听客户端接入
    } catch (IOException e) {
      e.printStackTrace();
    }
  }


  /**
   * 监听客户端请求
   */
  public void listen() {
    try {
      System.out.println("正在进行监听,监听端口为:" + this.port);
      while (true) {
        selector.select(); //轮询直到有事件进入
        Set<SelectionKey> selectionKeys = selector.selectedKeys();
        selectionKeys.forEach(selectionKey -> handleSelectKey(selectionKey)); //对轮询到的事件进行处理
        selectionKeys.clear();//清空轮询了事件
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
  }


  /**
   * 处理轮询到的事件
   */
  private void handleSelectKey(SelectionKey selectionKey) {
    try {
      if (selectionKey.isValid()) {
        if (selectionKey.isAcceptable()) { //有新客户端接入
          handleNewClientConnect(selectionKey);
        } else if (selectionKey.isReadable()) { //有客户端写入数据
          handleClientMessage(selectionKey);
        }
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  /**
   * 处理新客户端接入
   */
  private void handleNewClientConnect(SelectionKey selectionKey) throws IOException {
    ServerSocketChannel ss = (ServerSocketChannel) selectionKey.channel();
    SocketChannel client = ss.accept();
    System.out.println("有新的客户端接入.....");
    String address = client.getRemoteAddress().toString(); //客户端的地址
    clientChannelMap.put(address, client); //保存客户端的请求
    client.configureBlocking(false);
    client.register(selector, SelectionKey.OP_READ);//准备读取数据
  }


  /**
   * 读取客户端发送的信息,然后进行转发
   */
  private void handleClientMessage(SelectionKey selectionKey) {
    try {
      SocketChannel channel = (SocketChannel) selectionKey.channel();
      ByteBuffer readBuffer = ByteBuffer.allocate(1024);
      int bytes = channel.read(readBuffer);
      if (bytes > 0) {
        readBuffer.flip();
        String message = new String(readBuffer.array(), "UTF-8");
        System.out.println("有客户端发送消息为:" + message);
        //转发消息,正常聊天程序会发送给对应的用户(这个信息是携带在message里面的),这里简单的群发给所有人
        dispathMessage(message);
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  /**
   * 转发信息
   */
  public void dispathMessage(String message) {
    clientChannelMap.values().forEach(client -> {
      ByteBuffer writeBuffer = ByteBuffer.allocate(message.getBytes().length);
      writeBuffer.put(message.getBytes());
      writeBuffer.flip();
      try {
        client.write(writeBuffer);
      } catch (IOException e) {
        e.printStackTrace();
      }
    });
  }

  public static void main(String[] args) {
    ChatServer server = new ChatServer(8080);
    server.listen();
  }
}

2. Client

a. a timing chart

Here Insert Picture Description

b. client code

package com.firewolf.java.io.client;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Scanner;
import java.util.Set;

/**
 * 作者:刘兴 时间:2019/5/14 聊天程序客户端
 **/
public class ChatClient {


  private Selector selector = null;

  private SocketChannel socketChannel = null;

  public ChatClient(String host, int port) {
    connectServer(host, port);
  }


  private void connectServer(String host, int port) {
    try {
      socketChannel = SocketChannel.open();
      socketChannel.configureBlocking(false);
      selector = Selector.open();
      socketChannel.register(selector, SelectionKey.OP_CONNECT);
      socketChannel.connect(new InetSocketAddress(host, port));//连接客户端
      while (true) {
        selector.select();
        Set<SelectionKey> selectionKeys = selector.selectedKeys();
        selectionKeys.forEach(selectionKey -> handleSelectionKey(selectionKey));
        selectionKeys.clear();//清空事件
      }

    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  /**
   * 处理被轮询到的事件
   */
  private void handleSelectionKey(SelectionKey selectionKey) {
    if (selectionKey.isValid()) {
      if (selectionKey.isConnectable()) { //连接就绪事件
        handleConnection(selectionKey);
      } else if (selectionKey.isReadable()) { //有信息从服务端发过来
        readMessage(selectionKey);
      }
    }
  }


  /**
   * 连接就绪,准备接受用户的输入
   */
  private void handleConnection(SelectionKey selectionKey) {
    try {
      SocketChannel channel = (SocketChannel) selectionKey.channel();
      if (channel.isConnectionPending()) {
        System.out.println("连接已经就绪....");
        channel.finishConnect();
        //启动线程监听客户端输入信息
        new Thread(new Runnable() {
          @Override
          public void run() {
            Scanner scanner = new Scanner(System.in);
            while (true) {
              String message = scanner.nextLine();
              ByteBuffer writeBuffer = ByteBuffer.allocate(message.getBytes().length);
              writeBuffer.put(message.getBytes());
              writeBuffer.flip();
              try {
                channel.write(writeBuffer);
              } catch (IOException e) {
                e.printStackTrace();
              }
            }
          }
        }).start();

        channel.register(selector,SelectionKey.OP_READ);//注册读消息事件
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  /**
   * 读取服务端发送的消息
   */
  public void readMessage(SelectionKey selectionKey) {
    try {
      SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
      ByteBuffer readBuffer = ByteBuffer.allocate(1024);
      int bytes = socketChannel.read(readBuffer);
      if (bytes > 0) {
        readBuffer.flip();
        System.out.println(new String(readBuffer.array(), "UTF-8"));
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  public static void main(String[] args) {
    new ChatClient("127.0.0.1", 8080);
  }
}

After starting the server, you can start multiple clients, able to group chat operation.
NIO advantage is that the process may be asynchronous, but the high complexity of programming, the programming is not easy, the more difficult to handle some bug generated.

四, AIO

(A) AIO Introduction

NIO2.0 introduced a new concept of asynchronous channels and provides asynchronous file channel and asynchronous socket channel. Asynchronous channel provides two ways to get what operating results:

  • The results are expressed by class java.util.concurrent.Future asynchronous operations, using get () method to obtain the result
  • Passing a java.nio.channels.CompletionHandler interface implementation class when performing the asynchronous operation to asynchronous operation as a result of the callback method.

NIO2.0 asynchronous socket is a real non-blocking I / O, corresponding to the Unix Network Programming event-driven I / O (AIO), he does not need to register Selector multiplexer channel operator Round Robin asynchronous operation can be realized, which simplifies the model of NIO.

(B) programming examples

1. server

package com.firewolf.java.io.aio.server;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class AIOServer {

  static final int PORT = 30000;
  final static String UTF_8 = "utf-8";
  //保存客户端请求连接
  static List<AsynchronousSocketChannel> channelList = new ArrayList<>();

  public void startListen() throws InterruptedException, Exception {
    // 创建一个线程池
    ExecutorService executor = Executors.newFixedThreadPool(20);
    // 以指定线程池来创建一个AsynchronousChannelGroup
    AsynchronousChannelGroup channelGroup = AsynchronousChannelGroup.withThreadPool(executor);
    // 以指定线程池来创建一个AsynchronousServerSocketChannel
    AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open(channelGroup)
        // 指定监听本机的PORT端口
        .bind(new InetSocketAddress(PORT));
    // 使用CompletionHandler接受来自客户端的连接请求
    serverChannel.accept(null, new AcceptHandler(serverChannel)); // ①
    Thread.sleep(Long.MAX_VALUE); //睡眠线程,不让线程结束,实战中不需要
  }

  public static void main(String[] args) throws Exception {
    AIOServer server = new AIOServer();
    server.startListen();
  }
}

// 实现自己的CompletionHandler类
class AcceptHandler implements CompletionHandler<AsynchronousSocketChannel, Object> {

  private AsynchronousServerSocketChannel serverChannel;

  public AcceptHandler(AsynchronousServerSocketChannel sc) {
    this.serverChannel = sc;
  }

  // 定义一个ByteBuffer准备读取数据
  ByteBuffer buff = ByteBuffer.allocate(1024);

  // 当实际IO操作完成时候触发该方法
  @Override
  public void completed(final AsynchronousSocketChannel sc, Object attachment) {
    // 记录新连接的进来的Channel
    AIOServer.channelList.add(sc);
    // 准备接受客户端的下一次连接
    serverChannel.accept(null, this);
    sc.read(buff, null, new CompletionHandler<Integer, Object>() // ②
    {
      @Override
      public void completed(Integer result, Object attachment) {
        buff.flip();
        // 将buff中内容转换为字符串
        String content = StandardCharsets.UTF_8.decode(buff).toString();
        // 遍历每个Channel,将收到的信息写入各Channel中
        for (AsynchronousSocketChannel c : AIOServer.channelList) {
          try {
            c.write(ByteBuffer.wrap(content.getBytes(AIOServer.UTF_8))).get();
          } catch (Exception ex) {
            ex.printStackTrace();
          }
        }
        buff.clear();
        // 读取下一次数据
        sc.read(buff, null, this);
      }

      @Override
      public void failed(Throwable ex, Object attachment) {
        System.out.println("读取数据失败: " + ex);
        // 从该Channel读取数据失败,就将该Channel删除
        AIOServer.channelList.remove(sc);
      }
    });
  }

  @Override
  public void failed(Throwable ex, Object attachment) {
    System.out.println("连接失败: " + ex);
  }
}

2. Client

package com.firewolf.java.io.aio.client;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class AIOClient {

  final static String UTF_8 = "utf-8";
  final static int PORT = 30000;
  // 与服务器端通信的异步Channel
  AsynchronousSocketChannel clientChannel;

  /**
 1. 监听用户输入信息
   */
  public void listenUserInput() throws Exception {
    Scanner scan = new Scanner(System.in);
    while (scan.hasNextLine()) {
      clientChannel.write(ByteBuffer.wrap(scan.nextLine().getBytes(UTF_8))).get(); // ①
    }
  }

  /**
 2. 连接服务器
   */
  public void connect() throws Exception {
    // 定义一个ByteBuffer准备读取数据
    final ByteBuffer buff = ByteBuffer.allocate(1024);
    // 创建一个线程池
    ExecutorService executor = Executors.newFixedThreadPool(80);
    // 以指定线程池来创建一个AsynchronousChannelGroup
    AsynchronousChannelGroup channelGroup = AsynchronousChannelGroup.withThreadPool(executor);
    // 以channelGroup作为组管理器来创建AsynchronousSocketChannel
    clientChannel = AsynchronousSocketChannel.open(channelGroup);
    // 让AsynchronousSocketChannel连接到指定IP、指定端口
    clientChannel.connect(new InetSocketAddress("127.0.0.1", PORT)).get();
    // jta.append("---与服务器连接成功---\n");
    System.out.println("---与服务器连接成功---");
    buff.clear();
    clientChannel.read(buff, null, new CompletionHandler<Integer, Object>() // ②
    {
      @Override
      public void completed(Integer result, Object attachment) {
        buff.flip();
        // 将buff中内容转换为字符串
        String content = StandardCharsets.UTF_8.decode(buff).toString();
        // 显示从服务器端读取的数据
        // jta.append("某人说:" + content + "\n");
        System.out.println("某人说:" + content);
        buff.clear();
        //读取下一次数据
        clientChannel.read(buff, null, this);
      }

      @Override
      public void failed(Throwable ex, Object attachment) {
        System.out.println("读取数据失败: " + ex);
      }
    });
  }

  public static void main(String[] args) throws Exception {
    AIOClient client = new AIOClient();
    client.connect();
    client.listenUserInput();
  }
}

Five, Netty

In the above several IO model which, blocking I / O pseudo-asynchronous IO and efficiency are relatively low, concurrent too weak, the NIO the asynchronous non-blocking programming model, but is an I / O thread can handle multiple links it debug and trace very cumbersome, especially the build environment, we are unable to effectively debug and trace, therefore, be used in the actual development of more than a inconvenience.

(A) no reasons for choosing native NIO programming

  • NIO API and library complex, cumbersome to use
  • It requires other techniques as claiming, such as multi-threaded Java
  • Reliability weak, relatively large workload and difficulty, such as a client face even broken reconnection, half a pack reader, a cache failure, the network and so on flash
  • The JDK IO BUG, ​​e.g. notorious epoll bug, null cause selector polling, resulting in 100% CPU.

(B) the reasons for choosing Netty

Netty is one of the industry's most popular NIO framework, its robustness, functionality, performance, customizability and scalability are second to none in the same framework, it has been verified through hundreds of commercial projects, such as the Hadoop Avro RPC framework on the use of Netty, there are a lot of other RPC also uses Netty framework to build. Netty has the following advantages:

  • API uses a simple, low development threshold
  • Powerful, a lot of pre-codec function
  • Customization ability, can be extended to many communications framework
  • High performance, by comparison with other NIO framework for maximum overall performance of Netty
  • Mature, stable, Netty has fixed the NIO epoll Bug
  • We experienced a lot of test large-scale commercial applications, quality assurance
  • Community activists, short version iterations, Bug found to get timely repair

Guess you like

Origin blog.csdn.net/mail_liuxing/article/details/89931679