本文摘自:http://blog.csdn.net/liuj2511981/article/details/8658440
BIO就是基於Thread per Request的傳統server/client實現模式,
NIO通常採用Reactor模式,
AIO通常採用Proactor模式,
AIO簡化了程序的編寫,stream的讀取和寫入都有OS來完成,不需要像NIO那樣子遍歷Selector。Windows基於IOCP實現AIO,Linux只有eppoll模擬實現了AIO。
Java7之前的JDK只支持NIO和BIO,從7開始支持AIO。
4種通信方式:TCP/IP+BIO, TCP/IP+NIO, UDP/IP+BIO, UDP/IP+NIO。
一、 Reactor and Proactor
IO讀寫時,多路複用機制都會依賴對一個事件多路分離器,負責把源事件的IO 事件分離出來,分別到相應的read/write事件分離器。涉及到事件分離器的兩種模式分別就是 Reactor和Proactor,Reactor是基於同步IO的,Proactor是基於異步IO的。
關於同步和異步IO
Io的兩個重要步驟:發起IO請求,和實際的IO操作。在unix網絡編程的定義裏異步和非異步概念的區別就是實際的IO操作是否阻塞。如果不是就是異步,如果是就是同步。
而阻塞和非阻塞的區別在於發起IO請求的時候是否會阻塞,如果會就是阻塞,不會就是非阻塞。
本人理解能力有限,想了個例子來輔助自己理解:
小明想要買一本<深入java虛擬機>的書,以下幾個場景可以來理解這幾種io模式:
1. 如果小明每天都去書店問售貨員說有沒有這本書,如果沒有就回去繼續等待,等下次再過來文。(阻塞)
2. 如果小明告訴售貨員想買一本<深入java虛擬機>的書,那麼就在家裏等着做其他事情去了,如果書到了售貨員就通知小明,小明再自己過去取。
3. 如果小明告售貨員想買一本<深入java虛擬機>的書,然後告訴售貨員到了幫他送到某某地方去,就做其他事情去了。小明就不管了,等書到了,售貨員就幫他送到那個地方了。
售貨員可以認爲是操作系統的一個服務,而小明是一個用戶進程。不知道是否有誤,如果有誤請大家拍磚指出,謝謝。
可以看出2,3的效率明顯要比1高。但是1最簡單,而2,3需要一些協作。充分證明了團隊合作的力量。
在Reactor模式中,事件分離者等待某個事件或者可應用或個操作的狀態發生(比如文件描述符可讀寫,或者是socket可讀寫),事件分離者就把這個事件傳給事先註冊的事件處理函數或者回調函數,由後者來做實際的讀寫操作。
在Proactor模式中,事件處理者(或者代由事件分離者發起)直接發起一個異步讀寫操作(相當於請求),而實際的工作是由操作系統來完成的。發起時,需要提供的參數包括用於存放讀到數據的緩存區,讀的數據大小,或者用於存放外發數據的緩存區,以及這個請求完後的回調函數等信息。事件分離者得知了這個請求,它默默等待這個請求的完成,然後轉發完成事件給相應的事件處理者或者回調。舉例來說,在Windows上事件處理者投遞了一個異步IO操作(稱有 overlapped的技術),事件分離者等IOCompletion事件完成. 這種異步模式的典型實現是基於操作系統底層異步API的,所以我們可稱之爲“系統級別”的或者“真正意義上”的異步,因爲具體的讀寫是由操作系統代勞的。
舉個例子,將有助於理解Reactor與Proactor二者的差異,以讀操作爲例(類操作類似)。
在Reactor中實現讀:
- 註冊讀就緒事件和相應的事件處理器
- 事件分離器等待事件
- 事件到來,激活分離器,分離器調用事件對應的處理器。
- 事件處理器完成實際的讀操作,處理讀到的數據,註冊新的事件,然後返還控制權。
與如下Proactor(真異步)中的讀過程比較:
- 處理器發起異步讀操作(注意:操作系統必須支持異步IO)。在這種情況下,處理器無視IO就緒事件,它關注的是完成事件。
- 事件分離器等待操作完成事件
- 在分離器等待過程中,操作系統利用並行的內核線程執行實際的讀操作,並將結果數據存入用戶自定義緩衝區,最後通知事件分離器讀操作完成。
- 事件分離器呼喚處理器。
- 事件處理器處理用戶自定義緩衝區中的數據,然後啓動一個新的異步操作,並將控制權返回事件分離器。
可以看出,兩個模式的相同點,都是對某個IO事件的事件通知(即告訴某個模塊,這個IO操作可以進行或已經完成)。在結構
上,兩者也有相同點:demultiplexor負責提交IO操作(異步)、查詢設備是否可操作(同步),然後當條件滿足時,就回調handler;
不同點在於,異步情況下(Proactor),當回調handler時,表示IO操作已經完成;同步情況下(Reactor),回調handler時,表示
IO設備可以進行某個操作(can read or can write),handler這個時候開始提交操作。
二、BIO、NIO、AIO
NIO通常採用Reactor模式,AIO通常採用Proactor模式。AIO簡化了程序的編寫,stream的讀取和寫入都有OS來完成,不需要像NIO那樣子遍歷Selector。Windows基於IOCP實現AIO,Linux只有eppoll模擬實現了AIO。
Java7之前的JDK只支持NIO和BIO,從7開始支持AIO。
4種通信方式:TCP/IP+BIO, TCP/IP+NIO, UDP/IP+BIO, UDP/IP+NIO。
(1)TCP/IP+BIO
Socket和ServerSocket實現,ServerSocket實現Server端端口監聽,Socket用於建立網絡IO連接。不適用於處理多個請求 1.生成Socket會消耗過多的本地資源。2. Socket連接的建立一般比較慢。
BIO情況下,能支持的連接數有限,一般都採取accept獲取Socket以後採用一個thread來處理,one connection one thread。無論連接是否有真正數據請求,都需要獨佔一個thread。
可以通過設立Socket池來一定程度上解決問題,但是使用池需要注意的問題是:1. 競爭等待比較多。 2. 需要控制好超時時間。Socket和ServerSocket實現,ServerSocket實現Server端端口監聽,Socket用於建立網絡IO連接。
服務器,使用ServerSocket監聽指定的端口,端口可以隨意指定(由於1024以下的端口通常屬於保留端口,在一些操作系統中不可以隨意使用,所以建議使用大於1024的端口),等待客戶連接請求,客戶連接後,會話產生;在完成會話後,關閉連接。
- package test;
- import java.io.BufferedReader;
- import java.io.IOException;
- import java.io.InputStreamReader;
- import java.net.ServerSocket;
- import java.net.Socket;
- public class Server {
- private Socket socket;
- private ServerSocket ss;
- public Server() throws IOException {
- ss = new ServerSocket(7777);
- while (true) {
- socket = ss.accept();
- BufferedReader br = new BufferedReader(new InputStreamReader(socket
- .getInputStream()));
- System.out.println("you input is : " + br.readLine());
- }
- }
- public static void main(String[] args) {
- try {
- new Server();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- package test;
- import java.io.BufferedRear;
- import java.io.IOException;
- import java.io.InputStreamReader;
- import java.io.PrintWriter;
- import java.net.Socket;
- import java.net.UnknownHostException;
- ublic class Client {
- Socket client;
- PrintWriter pw;
- public Client() throws UnknownHostException, IOException {
- client=new Socket("Socket服務器IP",7777);
- pw=new PrintWriter(client.getOutputStream());
- BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
- pw.write(br.readLine());
- pw.close();
- br.close();
- }
- public static void main(String[] args) {
- try {
- new Client();
- } catch (UnknownHostException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
不適用於處理多個請求 1.生成Socket會消耗過多的本地資源。2. Socket連接的建立一般比較慢。
BIO情況下,能支持的連接數有限,一般都採取accept獲取Socket以後採用一個thread來處理,one connection one thread。無論連接是否有真正數據請求,都需要獨佔一個thread。
可以通過設立Socket池來一定程度上解決問題,
- import java.io.BufferedReader;
- import java.io.IOException;
- import java.io.InputStreamReader;
- import java.io.PrintWriter;
- import java.net.ServerSocket;
- import java.net.Socket;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- public class BIOPoolServer {
- ExecutorService pool = null;
- public BIOPoolServer(){
- try {
- ServerSocket server = new ServerSocket(29001);
- pool = Executors.newFixedThreadPool(1);
- while(true){
- pool.execute(new Handler(server.accept()));
- }
- } catch (IOException e) {
- e.printStackTrace();
- }finally{
- pool.shutdown();
- }
- }
- class Handler implements Runnable{
- Socket socket;
- public Handler(Socket socket){
- this.socket = socket;
- }
- public void run() {
- try {
- BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
- PrintWriter out = new PrintWriter(socket.getOutputStream(),true);
- String msg = in.readLine();
- System.out.println("The client send the msg : "+msg);
- out.println("The server has received!");
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- public static void main(String[] args) {
- new BIOPoolServer();
- }
- }
但是使用池需要注意的問題是:1. 競爭等待比較多。 2. 需要控制好超時時間。
(2)TCP/IP+NIO
使用Channel(SocketChannel和ServerSocketChannel)和Selector。
Server端通常由一個thread來監聽connect事件,另外多個thread來監聽讀寫事件。這樣做的好處是這些連接只有在真是請求的時候纔會創建thread來處理,one request one thread。這種方式在server端需要支持大量連接但這些連接同時發送請求的峯值不會很多的時候十分有效。
- server端:
- import java.io.IOException;
- import java.net.InetSocketAddress;
- import java.nio.ByteBuffer;
- import java.nio.IntBuffer;
- import java.nio.channels.SelectionKey;
- import java.nio.channels.Selector;
- import java.nio.channels.ServerSocketChannel;
- import java.nio.channels.SocketChannel;
- import java.nio.channels.spi.SelectorProvider;
- import java.util.Iterator;
- import java.util.Set;
- public class NIOServer {
- ServerSocketChannel channel = null;
- public NIOServer(){
- try {
- openChannel();
- waitForConnection();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- private void openChannel() throws IOException{
- channel = ServerSocketChannel.open();
- //綁定監聽端口
- channel.socket().bind(new InetSocketAddress(29000));
- //設置爲非阻塞形式
- channel.configureBlocking(false);
- }
- private void waitForConnection() throws IOException{
- Selector acceptSelector = SelectorProvider.provider().openSelector();
- channel.register(acceptSelector, SelectionKey.OP_ACCEPT);
- int keyAdded = 0;
- while((keyAdded=acceptSelector.select())>0){
- // 某客戶已經準備好可以進行I/O操作了,獲取其ready鍵集合
- Set readKeys = acceptSelector.selectedKeys();
- Iterator iter = readKeys.iterator();
- while(iter.hasNext()){
- SelectionKey sk = (SelectionKey)iter.next();
- iter.remove();
- if(sk.isAcceptable()){
- ServerSocketChannel server = (ServerSocketChannel) sk.channel();
- SocketChannel socket = server.accept();
- ByteBuffer _buffer = ByteBuffer.allocate(8);
- IntBuffer _intBuffer = _buffer.asIntBuffer();
- _buffer.clear();
- socket.read(_buffer);
- int result = _intBuffer.get(0) + _intBuffer.get(1);
- _buffer.flip();
- _buffer.clear();
- _intBuffer.put(0, result);
- socket.write(_buffer);
- }
- }
- }
- }
- public static void main(String[] args) {
- new NIOServer();
- }
- }
- client端:
- import java.io.IOException;
- import java.net.InetSocketAddress;
- import java.nio.ByteBuffer;
- import java.nio.IntBuffer;
- import java.nio.channels.SocketChannel;
- public class NIOClient {
- public void start(int first, int second){
- SocketChannel channel = null;
- try {
- InetSocketAddress socketAddress = new InetSocketAddress("localhost", 29000);
- channel = SocketChannel.open(socketAddress);
- channel.configureBlocking(false);
- ByteBuffer _buffer = ByteBuffer.allocate(8);
- IntBuffer _intBuffer = _buffer.asIntBuffer();
- _buffer.clear();
- _intBuffer.put(0, first);
- _intBuffer.put(1, second);
- channel.write(_buffer);
- System.out.println("發送加法請求 " + first + "+" + second);
- _buffer.clear();
- channel.read(_buffer);
- int result = _intBuffer.get(0);
- System.out.println("運算結果:"+result);
- } catch (IOException e) {
- e.printStackTrace();
- }finally {
- if (channel != null) {
- try {
- channel.close();
- } catch (IOException e) {
- }
- }
- }
- }
- public static void main(String[] args) {
- new NIOClient().start(3, 23);
- }
- }
使用Channel(SocketChannel和ServerSocketChannel)和Selector。
Server端通常由一個thread來監聽connect事件,另外多個thread來監聽讀寫事件。這樣做的好處是這些連接只有在真是請求的時候纔會創建thread來處理,one request one thread。這種方式在server端需要支持大量連接但這些連接同時發送請求的峯值不會很多的時候十分有效。
(3)UDP/IP+BIO
DatagramSocket和DatagramPacket。DatagramSocket負責監聽端口以及讀寫數據,DatagramPacket作爲數據流對象進行傳輸。
UDP/IP是無連接的,無法進行雙向通信,除非雙方都成爲UDP Server。
(4)UDP/IP+NIO
通過DatagramChannel和ByteBuffer實現。DatagramChannel負責端口監聽及讀寫。ByteBuffer負責數據流傳輸。
如果要將消息發送到多臺機器,如果爲每個目標機器都建立一個連接的話,會有很大的網絡流量壓力。這時候可以使用基於UDP/IP的Multicast協議傳輸,Java中可以通過MulticastSocket和DatagramPacket來實現。
Multicast一般多用於多臺機器的狀態同步,比如JGroups。SRM, URGCP都是Multicast的實現方式。eBay就採用SRM來實現將數據從主數據庫同步到各個搜索節點機器。