Netty序章之BIO NIO AIO演變

Netty是一個提供異步事件驅動的網絡應用框架,用以快速開發高性能高可靠的網絡服務器和客戶端程序。Netty簡化了網絡程序的開發,是不少框架和公司都在使用的技術。更是面試的加分項。Netty並不是橫空出世,它是在BIO,NIO,AIO演變中的產物,是一種NIO框架。而BIO,NIO,AIO更是筆試中要考,面試中要問的技術。也是一個很好的加分項,加分就是加工資,你還在等什麼?本章帶你細細品味三者的不一樣!java

流程圖:
640?wx_fmt=pnggit

技術:BIO,NIO,AIO
說明:github上有更全的源碼。
源碼:https://github.com/ITDragonBlog/daydayup/tree/master/Netty/socket-iogithub

BIO

BIO 全稱Block-IO 是一種阻塞同步的通訊模式。咱們常說的Stock IO 通常指的是BIO。是一個比較傳統的通訊方式,模式簡單使用方便。但併發處理能力低通訊耗時依賴網速
BIO 設計原理:
服務器經過一個Acceptor線程負責監聽客戶端請求和爲每一個客戶端建立一個新的線程進行鏈路處理。典型的一請求一應答模式。若客戶端數量增多,頻繁地建立和銷燬線程會給服務器打開很大的壓力。後改良爲用線程池的方式代替新增線程,被稱爲僞異步IO。web

服務器提供IP地址和監聽的端口,客戶端經過TCP的三次握手與服務器鏈接,鏈接成功後,雙放才能經過套接字(Stock)通訊。
小結:BIO模型中經過Socket和ServerSocket完成套接字通道的實現。阻塞,同步,創建鏈接耗時面試

BIO服務器代碼,負責啓動服務,阻塞服務,監聽客戶端請求,新建線程處理任務。express

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* IO 也稱爲 BIO,Block IO 阻塞同步的通信方式
* 比較傳統的技術,實際開發中基本上用Netty或者是AIO。熟悉BIO,NIO,體會其中變化的過程。做爲一個web開發人員,stock通信面試常常問題。
* BIO最大的問題是:阻塞,同步。
* BIO通信方式很依賴於網絡,若網速很差,阻塞時間會很長。每次請求都由程序執行並返回,這是同步的缺陷。
* BIO工做流程:
* 第一步:server端服務器啓動
* 第二步:server端服務器阻塞監聽client請求
* 第三步:server端服務器接收請求,建立線程實現任務
*/
public class ITDragonBIOServer {

private static final Integer PORT = 8888; // 服務器對外的端口號
public static void main(String[] args) {
ServerSocket server = null;
Socket socket = null;
ThreadPoolExecutor executor = null;
try {
server = new ServerSocket(PORT); // ServerSocket 啓動監聽端口
System.out.println("BIO Server 服務器啓動.........");
/*--------------傳統的新增線程處理----------------*/
/*while (true) {
// 服務器監聽:阻塞,等待Client請求
socket = server.accept();
System.out.println("server 服務器確認請求 : " + socket);
// 服務器鏈接確認:確認Client請求後,建立線程執行任務 。很明顯的問題,若每接收一次請求就要建立一個線程,顯然是不合理的。
new Thread(new ITDragonBIOServerHandler(socket)).start();
} */
/*--------------經過線程池處理緩解高併發給程序帶來的壓力(僞異步IO編程)----------------*/
executor = new ThreadPoolExecutor(10, 100, 1000, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(50));
while (true) {
socket = server.accept(); // 服務器監聽:阻塞,等待Client請求
ITDragonBIOServerHandler serverHandler = new ITDragonBIOServerHandler(socket);
executor.execute(serverHandler);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (null != socket) {
socket.close();
socket = null;
}
if (null != server) {
server.close();
server = null;
System.out.println("BIO Server 服務器關閉了!!!!");
}
executor.shutdown();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* IO 也稱爲 BIO,Block IO 阻塞同步的通信方式
* 比較傳統的技術,實際開發中基本上用Netty或者是AIO。熟悉BIO,NIO,體會其中變化的過程。做爲一個web開發人員,stock通信面試常常問題。
* BIO最大的問題是:阻塞,同步。
* BIO通信方式很依賴於網絡,若網速很差,阻塞時間會很長。每次請求都由程序執行並返回,這是同步的缺陷。
* BIO工做流程:
* 第一步:server端服務器啓動
* 第二步:server端服務器阻塞監聽client請求
* 第三步:server端服務器接收請求,建立線程實現任務
*/
public class ITDragonBIOServer {

private static final Integer PORT = 8888; // 服務器對外的端口號
public static void main(String[] args) {
ServerSocket server = null;
Socket socket = null;
ThreadPoolExecutor executor = null;
try {
server = new ServerSocket(PORT); // ServerSocket 啓動監聽端口
System.out.println("BIO Server 服務器啓動.........");
/*--------------傳統的新增線程處理----------------*/
/*while (true) {
// 服務器監聽:阻塞,等待Client請求
socket = server.accept();
System.out.println("server 服務器確認請求 : " + socket);
// 服務器鏈接確認:確認Client請求後,建立線程執行任務 。很明顯的問題,若每接收一次請求就要建立一個線程,顯然是不合理的。
new Thread(new ITDragonBIOServerHandler(socket)).start();
} */
/*--------------經過線程池處理緩解高併發給程序帶來的壓力(僞異步IO編程)----------------*/
executor = new ThreadPoolExecutor(10, 100, 1000, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(50));
while (true) {
socket = server.accept(); // 服務器監聽:阻塞,等待Client請求
ITDragonBIOServerHandler serverHandler = new ITDragonBIOServerHandler(socket);
executor.execute(serverHandler);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (null != socket) {
socket.close();
socket = null;
}
if (null != server) {
server.close();
server = null;
System.out.println("BIO Server 服務器關閉了!!!!");
}
executor.shutdown();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

BIO服務端處理任務代碼,負責處理Stock套接字,返回套接字給客戶端,解耦。編程

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import com.itdragon.util.CalculatorUtil;

public class ITDragonBIOServerHandler implements Runnable{

private Socket socket;
public ITDragonBIOServerHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
BufferedReader reader = null;
PrintWriter writer = null;
try {
reader = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
writer = new PrintWriter(this.socket.getOutputStream(), true);
String body = null;
while (true) {
body = reader.readLine(); // 若客戶端用的是 writer.print() 傳值,那readerLine() 是不能獲取值,細節
if (null == body) {
break;
}
System.out.println("server服務端接收參數 : " + body);
writer.println(body + " = " + CalculatorUtil.cal(body).toString());
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != writer) {
writer.close();
}
try {
if (null != reader) {
reader.close();
}
if (null != this.socket) {
this.socket.close();
this.socket = null;
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import com.itdragon.util.CalculatorUtil;

public class ITDragonBIOServerHandler implements Runnable{

private Socket socket;
public ITDragonBIOServerHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
BufferedReader reader = null;
PrintWriter writer = null;
try {
reader = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
writer = new PrintWriter(this.socket.getOutputStream(), true);
String body = null;
while (true) {
body = reader.readLine(); // 若客戶端用的是 writer.print() 傳值,那readerLine() 是不能獲取值,細節
if (null == body) {
break;
}
System.out.println("server服務端接收參數 : " + body);
writer.println(body + " = " + CalculatorUtil.cal(body).toString());
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != writer) {
writer.close();
}
try {
if (null != reader) {
reader.close();
}
if (null != this.socket) {
this.socket.close();
this.socket = null;
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

BIO客戶端代碼,負責啓動客戶端,向服務器發送請求,接收服務器返回的Stock套接字。數組

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Random;
/**
* BIO 客戶端
* Socket : 向服務端發送鏈接
* PrintWriter : 向服務端傳遞參數
* BufferedReader : 從服務端接收參數
*/
public class ITDragonBIOClient {

private static Integer PORT = 8888;
private static String IP_ADDRESS = "127.0.0.1";
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
clientReq(i);
}
}
private static void clientReq(int i) {
Socket socket = null;
BufferedReader reader = null;
PrintWriter writer = null;
try {
socket = new Socket(IP_ADDRESS, PORT); // Socket 發起鏈接操做。鏈接成功後,雙方經過輸入和輸出流進行同步阻塞式通訊
reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); // 獲取返回內容
writer = new PrintWriter(socket.getOutputStream(), true);
String []operators = {"+","-","*","/"};
Random random = new Random(System.currentTimeMillis());
String expression = random.nextInt(10)+operators[random.nextInt(4)]+(random.nextInt(10)+1);
writer.println(expression); // 向服務器端發送數據
System.out.println(i + " 客戶端打印返回數據 : " + reader.readLine());
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (null != reader) {
reader.close();
}
if (null != socket) {
socket.close();
socket = null;
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Random;
/**
* BIO 客戶端
* Socket : 向服務端發送鏈接
* PrintWriter : 向服務端傳遞參數
* BufferedReader : 從服務端接收參數
*/
public class ITDragonBIOClient {

private static Integer PORT = 8888;
private static String IP_ADDRESS = "127.0.0.1";
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
clientReq(i);
}
}
private static void clientReq(int i) {
Socket socket = null;
BufferedReader reader = null;
PrintWriter writer = null;
try {
socket = new Socket(IP_ADDRESS, PORT); // Socket 發起鏈接操做。鏈接成功後,雙方經過輸入和輸出流進行同步阻塞式通訊
reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); // 獲取返回內容
writer = new PrintWriter(socket.getOutputStream(), true);
String []operators = {"+","-","*","/"};
Random random = new Random(System.currentTimeMillis());
String expression = random.nextInt(10)+operators[random.nextInt(4)]+(random.nextInt(10)+1);
writer.println(expression); // 向服務器端發送數據
System.out.println(i + " 客戶端打印返回數據 : " + reader.readLine());
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (null != reader) {
reader.close();
}
if (null != socket) {
socket.close();
socket = null;
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

NIO

NIO 全稱New IO,也叫Non-Block IO 是一種非阻塞同步的通訊模式。
NIO 設計原理:
NIO 相對於BIO來講一大進步。客戶端和服務器之間經過Channel通訊。NIO能夠在Channel進行讀寫操做。這些Channel都會被註冊在Selector多路複用器上。Selector經過一個線程不停的輪詢這些Channel。找出已經準備就緒的Channel執行IO操做。
NIO 經過一個線程輪詢,實現千萬個客戶端的請求,這就是非阻塞NIO的特色。
1)緩衝區Buffer:它是NIO與BIO的一個重要區別。BIO是將數據直接寫入或讀取到Stream對象中。而NIO的數據操做都是在緩衝區中進行的。緩衝區其實是一個數組。Buffer最多見的類型是ByteBuffer,另外還有CharBuffer,ShortBuffer,IntBuffer,LongBuffer,FloatBuffer,DoubleBuffer。
2)通道Channel:和流不一樣,通道是雙向的。NIO能夠經過Channel進行數據的讀,寫和同時讀寫操做。通道分爲兩大類:一類是網絡讀寫(SelectableChannel),一類是用於文件操做(FileChannel),咱們使用的SocketChannel和ServerSocketChannel都是SelectableChannel的子類。
3)多路複用器Selector:NIO編程的基礎。多路複用器提供選擇已經就緒的任務的能力。就是Selector會不斷地輪詢註冊在其上的通道(Channel),若是某個通道處於就緒狀態,會被Selector輪詢出來,而後經過SelectionKey能夠取得就緒的Channel集合,從而進行後續的IO操做。服務器端只要提供一個線程負責Selector的輪詢,就能夠接入成千上萬個客戶端,這就是JDK NIO庫的巨大進步。緩存

說明:這裏的代碼只實現了客戶端發送請求,服務端接收數據的功能。其目的是簡化代碼,方便理解。github源碼中有完整代碼。
小結:NIO模型中經過SocketChannel和ServerSocketChannel完成套接字通道的實現。非阻塞/阻塞,同步,避免TCP創建鏈接使用三次握手帶來的開銷。tomcat

NIO服務器代碼,負責開啓多路複用器,打開通道,註冊通道,輪詢通道,處理通道。

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.Iterator;
/**
* NIO 也稱 New IO, Non-Block IO,非阻塞同步通訊方式
* 從BIO的阻塞到NIO的非阻塞,這是一大進步。功歸於Buffer,Channel,Selector三個設計實現。
* Buffer : 緩衝區。NIO的數據操做都是在緩衝區中進行。緩衝區其實是一個數組。而BIO是將數據直接寫入或讀取到Stream對象。
* Channel : 通道。NIO能夠經過Channel進行數據的讀,寫和同時讀寫操做。
* Selector : 多路複用器。NIO編程的基礎。多路複用器提供選擇已經就緒狀態任務的能力。
* 客戶端和服務器經過Channel鏈接,而這些Channel都要註冊在Selector。Selector經過一個線程不停的輪詢這些Channel。找出已經準備就緒的Channel執行IO操做。
* NIO經過一個線程輪詢,實現千萬個客戶端的請求,這就是非阻塞NIO的特色。
*/
public class ITDragonNIOServer implements Runnable{

private final int BUFFER_SIZE = 1024; // 緩衝區大小
private final int PORT = 8888; // 監聽的端口
private Selector selector; // 多路複用器,NIO編程的基礎,負責管理通道Channel
private ByteBuffer readBuffer = ByteBuffer.allocate(BUFFER_SIZE); // 緩衝區Buffer
public ITDragonNIOServer() {
startServer();
}
private void startServer() {
try {
// 1.開啓多路複用器
selector = Selector.open();
// 2.打開服務器通道(網絡讀寫通道)
ServerSocketChannel channel = ServerSocketChannel.open();
// 3.設置服務器通道爲非阻塞模式,true爲阻塞,false爲非阻塞
channel.configureBlocking(false);
// 4.綁定端口
channel.socket().bind(new InetSocketAddress(PORT));
// 5.把通道註冊到多路複用器上,並監聽阻塞事件
/**
* SelectionKey.OP_READ : 表示關注讀數據就緒事件
* SelectionKey.OP_WRITE : 表示關注寫數據就緒事件
* SelectionKey.OP_CONNECT: 表示關注socket channel的鏈接完成事件
* SelectionKey.OP_ACCEPT : 表示關注server-socket channel的accept事件
*/
channel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("Server start >>>>>>>>> port :" + PORT);
} catch (IOException e) {
e.printStackTrace();
}
}
// 須要一個線程負責Selector的輪詢
@Override
public void run() {
while (true) {
try {
/**
* a.select() 阻塞到至少有一個通道在你註冊的事件上就緒
* b.select(long timeOut) 阻塞到至少有一個通道在你註冊的事件上就緒或者超時timeOut
* c.selectNow() 當即返回。若是沒有就緒的通道則返回0
* select方法的返回值表示就緒通道的個數。
*/
// 1.多路複用器監聽阻塞
selector.select();
// 2.多路複用器已經選擇的結果集
Iterator<SelectionKey> selectionKeys = selector.selectedKeys().iterator();
// 3.不停的輪詢
while (selectionKeys.hasNext()) {
// 4.獲取一個選中的key
SelectionKey key = selectionKeys.next();
// 5.獲取後便將其從容器中移除
selectionKeys.remove();
// 6.只獲取有效的key
if (!key.isValid()){
continue;
}
// 阻塞狀態處理
if (key.isAcceptable()){
accept(key);
}
// 可讀狀態處理
if (key.isReadable()){
read(key);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 設置阻塞,等待Client請求。在傳統IO編程中,用的是ServerSocket和Socket。在NIO中採用的ServerSocketChannel和SocketChannel
private void accept(SelectionKey selectionKey) {
try {
// 1.獲取通道服務
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
// 2.執行阻塞方法
SocketChannel socketChannel = serverSocketChannel.accept();
// 3.設置服務器通道爲非阻塞模式,true爲阻塞,false爲非阻塞
socketChannel.configureBlocking(false);
// 4.把通道註冊到多路複用器上,並設置讀取標識
socketChannel.register(selector, SelectionKey.OP_READ);
} catch (IOException e) {
e.printStackTrace();
}
}
private void read(SelectionKey selectionKey) {
try {
// 1.清空緩衝區數據
readBuffer.clear();
// 2.獲取在多路複用器上註冊的通道
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
// 3.讀取數據,返回
int count = socketChannel.read(readBuffer);
// 4.返回內容爲-1 表示沒有數據
if (-1 == count) {
selectionKey.channel().close();
selectionKey.cancel();
return ;
}
// 5.有數據則在讀取數據前進行復位操做
readBuffer.flip();
// 6.根據緩衝區大小建立一個相應大小的bytes數組,用來獲取值
byte[] bytes = new byte[readBuffer.remaining()];
// 7.接收緩衝區數據
readBuffer.get(bytes);
// 8.打印獲取到的數據
System.out.println("NIO Server : " + new String(bytes)); // 不能用bytes.toString()
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new Thread(new ITDragonNIOServer()).start();
}
}
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.Iterator;
/**
* NIO 也稱 New IO, Non-Block IO,非阻塞同步通訊方式
* 從BIO的阻塞到NIO的非阻塞,這是一大進步。功歸於Buffer,Channel,Selector三個設計實現。
* Buffer : 緩衝區。NIO的數據操做都是在緩衝區中進行。緩衝區其實是一個數組。而BIO是將數據直接寫入或讀取到Stream對象。
* Channel : 通道。NIO能夠經過Channel進行數據的讀,寫和同時讀寫操做。
* Selector : 多路複用器。NIO編程的基礎。多路複用器提供選擇已經就緒狀態任務的能力。
* 客戶端和服務器經過Channel鏈接,而這些Channel都要註冊在Selector。Selector經過一個線程不停的輪詢這些Channel。找出已經準備就緒的Channel執行IO操做。
* NIO經過一個線程輪詢,實現千萬個客戶端的請求,這就是非阻塞NIO的特色。
*/
public class ITDragonNIOServer implements Runnable{

private final int BUFFER_SIZE = 1024; // 緩衝區大小
private final int PORT = 8888; // 監聽的端口
private Selector selector; // 多路複用器,NIO編程的基礎,負責管理通道Channel
private ByteBuffer readBuffer = ByteBuffer.allocate(BUFFER_SIZE); // 緩衝區Buffer
public ITDragonNIOServer() {
startServer();
}
private void startServer() {
try {
// 1.開啓多路複用器
selector = Selector.open();
// 2.打開服務器通道(網絡讀寫通道)
ServerSocketChannel channel = ServerSocketChannel.open();
// 3.設置服務器通道爲非阻塞模式,true爲阻塞,false爲非阻塞
channel.configureBlocking(false);
// 4.綁定端口
channel.socket().bind(new InetSocketAddress(PORT));
// 5.把通道註冊到多路複用器上,並監聽阻塞事件
/**
* SelectionKey.OP_READ : 表示關注讀數據就緒事件
* SelectionKey.OP_WRITE : 表示關注寫數據就緒事件
* SelectionKey.OP_CONNECT: 表示關注socket channel的鏈接完成事件
* SelectionKey.OP_ACCEPT : 表示關注server-socket channel的accept事件
*/
channel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("Server start >>>>>>>>> port :" + PORT);
} catch (IOException e) {
e.printStackTrace();
}
}
// 須要一個線程負責Selector的輪詢
@Override
public void run() {
while (true) {
try {
/**
* a.select() 阻塞到至少有一個通道在你註冊的事件上就緒
* b.select(long timeOut) 阻塞到至少有一個通道在你註冊的事件上就緒或者超時timeOut
* c.selectNow() 當即返回。若是沒有就緒的通道則返回0
* select方法的返回值表示就緒通道的個數。
*/
// 1.多路複用器監聽阻塞
selector.select();
// 2.多路複用器已經選擇的結果集
Iterator<SelectionKey> selectionKeys = selector.selectedKeys().iterator();
// 3.不停的輪詢
while (selectionKeys.hasNext()) {
// 4.獲取一個選中的key
SelectionKey key = selectionKeys.next();
// 5.獲取後便將其從容器中移除
selectionKeys.remove();
// 6.只獲取有效的key
if (!key.isValid()){
continue;
}
// 阻塞狀態處理
if (key.isAcceptable()){
accept(key);
}
// 可讀狀態處理
if (key.isReadable()){
read(key);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 設置阻塞,等待Client請求。在傳統IO編程中,用的是ServerSocket和Socket。在NIO中採用的ServerSocketChannel和SocketChannel
private void accept(SelectionKey selectionKey) {
try {
// 1.獲取通道服務
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
// 2.執行阻塞方法
SocketChannel socketChannel = serverSocketChannel.accept();
// 3.設置服務器通道爲非阻塞模式,true爲阻塞,false爲非阻塞
socketChannel.configureBlocking(false);
// 4.把通道註冊到多路複用器上,並設置讀取標識
socketChannel.register(selector, SelectionKey.OP_READ);
} catch (IOException e) {
e.printStackTrace();
}
}
private void read(SelectionKey selectionKey) {
try {
// 1.清空緩衝區數據
readBuffer.clear();
// 2.獲取在多路複用器上註冊的通道
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
// 3.讀取數據,返回
int count = socketChannel.read(readBuffer);
// 4.返回內容爲-1 表示沒有數據
if (-1 == count) {
selectionKey.channel().close();
selectionKey.cancel();
return ;
}
// 5.有數據則在讀取數據前進行復位操做
readBuffer.flip();
// 6.根據緩衝區大小建立一個相應大小的bytes數組,用來獲取值
byte[] bytes = new byte[readBuffer.remaining()];
// 7.接收緩衝區數據
readBuffer.get(bytes);
// 8.打印獲取到的數據
System.out.println("NIO Server : " + new String(bytes)); // 不能用bytes.toString()
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new Thread(new ITDragonNIOServer()).start();
}
}

NIO客戶端代碼,負責鏈接服務器,聲明通道,鏈接通道

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class ITDragonNIOClient {

private final static int PORT = 8888;
private final static int BUFFER_SIZE = 1024;
private final static String IP_ADDRESS = "127.0.0.1";
public static void main(String[] args) {
clientReq();
}
private static void clientReq() {
// 1.建立鏈接地址
InetSocketAddress inetSocketAddress = new InetSocketAddress(IP_ADDRESS, PORT);
// 2.聲明一個鏈接通道
SocketChannel socketChannel = null;
// 3.建立一個緩衝區
ByteBuffer byteBuffer = ByteBuffer.allocate(BUFFER_SIZE);
try {
// 4.打開通道
socketChannel = SocketChannel.open();
// 5.鏈接服務器
socketChannel.connect(inetSocketAddress);
while(true){
// 6.定義一個字節數組,而後使用系統錄入功能:
byte[] bytes = new byte[BUFFER_SIZE];
// 7.鍵盤輸入數據
System.in.read(bytes);
// 8.把數據放到緩衝區中
byteBuffer.put(bytes);
// 9.對緩衝區進行復位
byteBuffer.flip();
// 10.寫出數據
socketChannel.write(byteBuffer);
// 11.清空緩衝區數據
byteBuffer.clear();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != socketChannel) {
try {
socketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class ITDragonNIOClient {

private final static int PORT = 8888;
private final static int BUFFER_SIZE = 1024;
private final static String IP_ADDRESS = "127.0.0.1";
public static void main(String[] args) {
clientReq();
}
private static void clientReq() {
// 1.建立鏈接地址
InetSocketAddress inetSocketAddress = new InetSocketAddress(IP_ADDRESS, PORT);
// 2.聲明一個鏈接通道
SocketChannel socketChannel = null;
// 3.建立一個緩衝區
ByteBuffer byteBuffer = ByteBuffer.allocate(BUFFER_SIZE);
try {
// 4.打開通道
socketChannel = SocketChannel.open();
// 5.鏈接服務器
socketChannel.connect(inetSocketAddress);
while(true){
// 6.定義一個字節數組,而後使用系統錄入功能:
byte[] bytes = new byte[BUFFER_SIZE];
// 7.鍵盤輸入數據
System.in.read(bytes);
// 8.把數據放到緩衝區中
byteBuffer.put(bytes);
// 9.對緩衝區進行復位
byteBuffer.flip();
// 10.寫出數據
socketChannel.write(byteBuffer);
// 11.清空緩衝區數據
byteBuffer.clear();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != socketChannel) {
try {
socketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

AIO

AIO 也叫NIO2.0 是一種非阻塞異步的通訊模式。在NIO的基礎上引入了新的異步通道的概念,並提供了異步文件通道和異步套接字通道的實現。
AIO 並無採用NIO的多路複用器,而是使用異步通道的概念。其read,write方法的返回類型都是Future對象。而Future模型是異步的,其核心思想是:去主函數等待時間。

小結:AIO模型中經過AsynchronousSocketChannel和AsynchronousServerSocketChannel完成套接字通道的實現。非阻塞,異步

AIO服務端代碼,負責建立服務器通道,綁定端口,等待請求。

import java.net.InetSocketAddress;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* AIO, 也叫 NIO2.0 是一種異步非阻塞的通訊方式
* AIO 引入了異步通道的概念 AsynchronousServerSocketChannel和AsynchronousSocketChannel 其read和write方法返回值類型是Future對象。
*/
public class ITDragonAIOServer {

private ExecutorService executorService; // 線程池
private AsynchronousChannelGroup threadGroup; // 通道組
public AsynchronousServerSocketChannel asynServerSocketChannel; // 服務器通道
public void start(Integer port){
try {
// 1.建立一個緩存池
executorService = Executors.newCachedThreadPool();
// 2.建立通道組
threadGroup = AsynchronousChannelGroup.withCachedThreadPool(executorService, 1);
// 3.建立服務器通道
asynServerSocketChannel = AsynchronousServerSocketChannel.open(threadGroup);
// 4.進行綁定
asynServerSocketChannel.bind(new InetSocketAddress(port));
System.out.println("server start , port : " + port);
// 5.等待客戶端請求
asynServerSocketChannel.accept(this, new ITDragonAIOServerHandler());
// 一直阻塞 不讓服務器中止,真實環境是在tomcat下運行,因此不須要這行代碼
Thread.sleep(Integer.MAX_VALUE);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
ITDragonAIOServer server = new ITDragonAIOServer();
server.start(8888);
}
}
import java.net.InetSocketAddress;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* AIO, 也叫 NIO2.0 是一種異步非阻塞的通訊方式
* AIO 引入了異步通道的概念 AsynchronousServerSocketChannel和AsynchronousSocketChannel 其read和write方法返回值類型是Future對象。
*/
public class ITDragonAIOServer {

private ExecutorService executorService; // 線程池
private AsynchronousChannelGroup threadGroup; // 通道組
public AsynchronousServerSocketChannel asynServerSocketChannel; // 服務器通道
public void start(Integer port){
try {
// 1.建立一個緩存池
executorService = Executors.newCachedThreadPool();
// 2.建立通道組
threadGroup = AsynchronousChannelGroup.withCachedThreadPool(executorService, 1);
// 3.建立服務器通道
asynServerSocketChannel = AsynchronousServerSocketChannel.open(threadGroup);
// 4.進行綁定
asynServerSocketChannel.bind(new InetSocketAddress(port));
System.out.println("server start , port : " + port);
// 5.等待客戶端請求
asynServerSocketChannel.accept(this, new ITDragonAIOServerHandler());
// 一直阻塞 不讓服務器中止,真實環境是在tomcat下運行,因此不須要這行代碼
Thread.sleep(Integer.MAX_VALUE);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
ITDragonAIOServer server = new ITDragonAIOServer();
server.start(8888);
}
}

AIO服務器任務處理代碼,負責,讀取數據,寫入數據

import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.ExecutionException;
import com.itdragon.util.CalculatorUtil;

public class ITDragonAIOServerHandler implements CompletionHandler<AsynchronousSocketChannel, ITDragonAIOServer> {
private final Integer BUFFER_SIZE = 1024;
@Override
public void completed(AsynchronousSocketChannel asynSocketChannel, ITDragonAIOServer attachment) {
// 保證多個客戶端均可以阻塞
attachment.asynServerSocketChannel.accept(attachment, this);
read(asynSocketChannel);
}
//讀取數據
private void read(final AsynchronousSocketChannel asynSocketChannel) {
ByteBuffer byteBuffer = ByteBuffer.allocate(BUFFER_SIZE);
asynSocketChannel.read(byteBuffer, byteBuffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer resultSize, ByteBuffer attachment) {
//進行讀取以後,重置標識位
attachment.flip();
//獲取讀取的數據
String resultData = new String(attachment.array()).trim();
System.out.println("Server -> " + "收到客戶端的數據信息爲:" + resultData);
String response = resultData + " = " + CalculatorUtil.cal(resultData);
write(asynSocketChannel, response);
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
exc.printStackTrace();
}
});
}
// 寫入數據
private void write(AsynchronousSocketChannel asynSocketChannel, String response) {
try {
// 把數據寫入到緩衝區中
ByteBuffer buf = ByteBuffer.allocate(BUFFER_SIZE);
buf.put(response.getBytes());
buf.flip();
// 在從緩衝區寫入到通道中
asynSocketChannel.write(buf).get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
@Override
public void failed(Throwable exc, ITDragonAIOServer attachment) {
exc.printStackTrace();
}
}
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.ExecutionException;
import com.itdragon.util.CalculatorUtil;

public class ITDragonAIOServerHandler implements CompletionHandler<AsynchronousSocketChannel, ITDragonAIOServer> {
private final Integer BUFFER_SIZE = 1024;
@Override
public void completed(AsynchronousSocketChannel asynSocketChannel, ITDragonAIOServer attachment) {
// 保證多個客戶端均可以阻塞
attachment.asynServerSocketChannel.accept(attachment, this);
read(asynSocketChannel);
}
//讀取數據
private void read(final AsynchronousSocketChannel asynSocketChannel) {
ByteBuffer byteBuffer = ByteBuffer.allocate(BUFFER_SIZE);
asynSocketChannel.read(byteBuffer, byteBuffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer resultSize, ByteBuffer attachment) {
//進行讀取以後,重置標識位
attachment.flip();
//獲取讀取的數據
String resultData = new String(attachment.array()).trim();
System.out.println("Server -> " + "收到客戶端的數據信息爲:" + resultData);
String response = resultData + " = " + CalculatorUtil.cal(resultData);
write(asynSocketChannel, response);
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
exc.printStackTrace();
}
});
}
// 寫入數據
private void write(AsynchronousSocketChannel asynSocketChannel, String response) {
try {
// 把數據寫入到緩衝區中
ByteBuffer buf = ByteBuffer.allocate(BUFFER_SIZE);
buf.put(response.getBytes());
buf.flip();
// 在從緩衝區寫入到通道中
asynSocketChannel.write(buf).get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
@Override
public void failed(Throwable exc, ITDragonAIOServer attachment) {
exc.printStackTrace();
}
}

AIO客戶端代碼,負責鏈接服務器,聲明通道,鏈接通道

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.Random;

public class ITDragonAIOClient implements Runnable{
private static Integer PORT = 8888;
private static String IP_ADDRESS = "127.0.0.1";
private AsynchronousSocketChannel asynSocketChannel ;
public ITDragonAIOClient() throws Exception {
asynSocketChannel = AsynchronousSocketChannel.open(); // 打開通道
}
public void connect(){
asynSocketChannel.connect(new InetSocketAddress(IP_ADDRESS, PORT)); // 建立鏈接 和NIO同樣
}
public void write(String request){
try {
asynSocketChannel.write(ByteBuffer.wrap(request.getBytes())).get();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
asynSocketChannel.read(byteBuffer).get();
byteBuffer.flip();
byte[] respByte = new byte[byteBuffer.remaining()];
byteBuffer.get(respByte); // 將緩衝區的數據放入到 byte數組中
System.out.println(new String(respByte,"utf-8").trim());
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void run() {
while(true){
}
}
public static void main(String[] args) throws Exception {
for (int i = 0; i < 10; i++) {
ITDragonAIOClient myClient = new ITDragonAIOClient();
myClient.connect();
new Thread(myClient, "myClient").start();
String []operators = {"+","-","*","/"};
Random random = new Random(System.currentTimeMillis());
String expression = random.nextInt(10)+operators[random.nextInt(4)]+(random.nextInt(10)+1);
myClient.write(expression);
}
}
}
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.Random;

public class ITDragonAIOClient implements Runnable{
private static Integer PORT = 8888;
private static String IP_ADDRESS = "127.0.0.1";
private AsynchronousSocketChannel asynSocketChannel ;
public ITDragonAIOClient() throws Exception {
asynSocketChannel = AsynchronousSocketChannel.open(); // 打開通道
}
public void connect(){
asynSocketChannel.connect(new InetSocketAddress(IP_ADDRESS, PORT)); // 建立鏈接 和NIO同樣
}
public void write(String request){
try {
asynSocketChannel.write(ByteBuffer.wrap(request.getBytes())).get();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
asynSocketChannel.read(byteBuffer).get();
byteBuffer.flip();
byte[] respByte = new byte[byteBuffer.remaining()];
byteBuffer.get(respByte); // 將緩衝區的數據放入到 byte數組中
System.out.println(new String(respByte,"utf-8").trim());
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void run() {
while(true){
}
}
public static void main(String[] args) throws Exception {
for (int i = 0; i < 10; i++) {
ITDragonAIOClient myClient = new ITDragonAIOClient();
myClient.connect();
new Thread(myClient, "myClient").start();
String []operators = {"+","-","*","/"};
Random random = new Random(System.currentTimeMillis());
String expression = random.nextInt(10)+operators[random.nextInt(4)]+(random.nextInt(10)+1);
myClient.write(expression);
}
}
}

常見面試題

1 IO,NIO,AIO區別
IO 阻塞同步通訊模式,客戶端和服務器鏈接須要三次握手,使用簡單,但吞吐量小
NIO 非阻塞同步通訊模式,客戶端與服務器經過Channel鏈接,採用多路複用器輪詢註冊的Channel。提升吞吐量和可靠性。
AIO 非阻塞異步通訊模式,NIO的升級版,採用異步通道實現異步通訊,其read和write方法均是異步方法。

2 Stock通訊的僞代碼實現流程
服務器綁定端口:server = new ServerSocket(PORT)
服務器阻塞監聽:socket = server.accept()
服務器開啓線程:new Thread(Handle handle)
服務器讀寫數據:BufferedReader PrintWriter
客戶端綁定IP和PORT:new Socket(IP_ADDRESS, PORT)
客戶端傳輸接收數據:BufferedReader PrintWriter

3 TCP協議與UDP協議有什麼區別
TCP : 傳輸控制協議是基於鏈接的協議,在正式收發數據前,必須和對方創建可靠的鏈接。速度慢,合適傳輸大量數據。
UDP : 用戶數據報協議是與TCP相對應的協議。面向非鏈接的協議,不與對方創建鏈接,而是直接就把數據包發送過去,速度快,適合傳輸少許數據。

4 什麼是同步阻塞BIO,同步非阻塞NIO,異步非阻塞AIO
同步阻塞IO : 用戶進程發起一個IO操做之後,必須等待IO操做的真正完成後,才能繼續運行。
同步非阻塞IO: 用戶進程發起一個IO操做之後,可作其它事情,但用戶進程須要常常詢問IO操做是否完成,這樣形成沒必要要的CPU資源浪費。
異步非阻塞IO: 用戶進程發起一個IO操做而後,當即返回,等IO操做真正的完成之後,應用程序會獲得IO操做完成的通知。類比Future模式。

總結

1 BIO模型中經過SocketServerSocket完成套接字通道實現。阻塞,同步,鏈接耗時。
2 NIO模型中經過SocketChannelServerSocketChannel完成套接字通道實現。非阻塞/阻塞,同步,避免TCP創建鏈接使用三次握手帶來的開銷。
3 AIO模型中經過AsynchronousSocketChannelAsynchronousServerSocketChannel完成套接字通道實現。非阻塞,異步

640?wx_fmt=png

到這裏BIO,NIO,AIO的知識點就梳理完了。下一章是Netty的入門 編解碼 數據通訊知識。若是以爲不錯能夠點個"推薦"。也能夠"關注"我,一塊兒學習,一塊兒成長。正常狀況一週一更。學習方向是JAVA架構師。

 

做者:ITDragon龍

版權:本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,不然保留追究法律責任的權利。

相關文章
相關標籤/搜索