天天看點

對i/o端口的通路和i/o空間的通路方式是一樣的_揭開網絡程式設計的神秘面紗2(BIO、僞異步IO、NIO、AIO)...Channel(通道)Buffer(緩沖區)Selector(多路複用選擇器)NIO通信模型AIO通信模型總結

從上文揭開網絡程式設計的神秘面紗1(BIO、僞異步IO、NIO、AIO)中我們知道,不管是BIO模型還是僞異步IO模型,服務端都需要建立一定量的線程來處理用戶端Socket的請求,也就是說在任何時候都可能有大量線程處于休眠狀态,隻是等待輸入或者輸出資料就緒;而且由于需要為每個線程的調用棧配置設定記憶體。這些都是一種資源的浪費。即使jvm在實體上有很大的資源,可以支援非常大數量的線程,但是線程上下文切換所帶來的開銷也會給我們帶來麻煩。

對i/o端口的通路和i/o空間的通路方式是一樣的_揭開網絡程式設計的神秘面紗2(BIO、僞異步IO、NIO、AIO)...Channel(通道)Buffer(緩沖區)Selector(多路複用選擇器)NIO通信模型AIO通信模型總結

是以BIO模型和僞異步IO模型隻适用于中小數量的用戶端請求。如果需要支撐高并發的資料,我們需要選擇NIO或者AIO模型。

由于NIO涉及到Buffer、Channel、Selector等基礎知識,為了便于了解,我們舉個例子。小JIA要結婚了,需要給親朋好友發請柬,這個時候小JIA需要一一拜訪,每次拿一張請柬給一個親朋好友,發送完成後,再回家再取一張請柬發給另外一個親朋好友,直到所有的好友都拿到請柬。這就是普通的Socket模式,來一個請求ServerSocket就進行處理,處理完再繼續接受請求。發到一半的時候小JIA發現還有幾百個親朋好友沒發,照這個速度發下去,得到猴年馬月,說不定婚禮都過了,請柬還沒發完。這個時候小JIA開始動員親朋好友來幫忙發請柬。小JIA通知了幾個親戚和朋友,讓他們過來拿請柬幫忙分發,小JIA的父母幫忙分揀請柬。這就是NioSocket,Buffer就是請柬,而Channel就是幫忙分發請柬的親戚朋友,而小JIA的父母充當了Selector的職責,負責請柬的分揀。這樣的處理方式極大的提高了IO的效率。小JIA再也不用擔心親戚朋友沒有準時收到請柬而錯過婚禮了。

NioSocket提供ServerSocketChannel和SocketChannel,分别對應ServerSocket和Socket。接下來我們來介紹NIO的幾個基礎知識。

Channel(通道)

Channel就像水管一樣,是一個通道。通道與流的不同之處在于通道是雙向的,可以用來讀、寫或同時讀寫操作。因為是雙向的,是以通道可以比流更好地反應底層作業系統的真實情況。

對i/o端口的通路和i/o空間的通路方式是一樣的_揭開網絡程式設計的神秘面紗2(BIO、僞異步IO、NIO、AIO)...Channel(通道)Buffer(緩沖區)Selector(多路複用選擇器)NIO通信模型AIO通信模型總結

Channel主要分為兩大類:SelectableChannel(使用者網絡讀寫)和FileChannel(用于檔案操作)。ServerSocketChannel和SocketChannel都是SelectableChannel的子類。

Buffer(緩沖區)

Channel提供從檔案、網絡讀取資料的通道,可是讀取或寫入資料都必須經過Buffer。Buffer實際上是一個數組,并提供對資料結構化通路以及維護讀寫位置等資訊。

對i/o端口的通路和i/o空間的通路方式是一樣的_揭開網絡程式設計的神秘面紗2(BIO、僞異步IO、NIO、AIO)...Channel(通道)Buffer(緩沖區)Selector(多路複用選擇器)NIO通信模型AIO通信模型總結

具體的緩存區有:ByteBuffer、CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer和DoubleBuffer。它們實作了共同的接口:Buffer。

Selector(多路複用選擇器)

Selector一般稱為選擇器,當然也可以叫做多路複用器。它是Java NIO核心元件中的一個,用于檢查一個或者多個NIO Channel(通道)的狀态是否處于可讀、可寫。

對i/o端口的通路和i/o空間的通路方式是一樣的_揭開網絡程式設計的神秘面紗2(BIO、僞異步IO、NIO、AIO)...Channel(通道)Buffer(緩沖區)Selector(多路複用選擇器)NIO通信模型AIO通信模型總結

一個Selector可以同時輪詢多個Channel,因為jdk使用了epoll()代替傳統的select實作,是以沒有最大連接配接句柄1024/2048的限制。也就是一個線程負責Selector的輪詢,可以管理成千上萬個網絡用戶端連接配接。

NIO通信模型

NIO提供了一個全新底層I/O模型。采用面向塊的概念,IO操作以大的資料塊為機關進行操作,而不是一個個位元組或字元進行操作,是以性能有了很大的提高。

//建立選擇器selector = Selector.open();//打開監聽通道serverChannel = ServerSocketChannel.open();//如果為 true,則此通道将被置于阻塞模式;如果為 false,則此通道将被置于非阻塞模式serverChannel.configureBlocking(false);//開啟非阻塞模式//綁定端口 backlog設為1024serverChannel.socket().bind(new InetSocketAddress(port),1024);//監聽用戶端連接配接請求serverChannel.register(selector, SelectionKey.OP_ACCEPT);
           

1、初始化多路複用器selector、serverSocketChannel通道、設定通道的模式為非阻塞、注冊channel到selector上,并監聽accept請求;

Set keys = selector.selectedKeys();Iterator it = keys.iterator();
           

2、啟動Server伺服器,循環selectedKeys,當有channel準備好時就處理,否則一直循環。

3、selectedKey儲存了目前請求的Channel和Selector,并提供了不同的操作類型。共有四種:

  • SelectionKey.OP_ACCEPT //請求操作
  • SelectionKey.OP_CONNECT //連結操作
  • SelectionKey.OP_READ //讀操作
  • SelectionKey.OP_WRITE //寫操作
//處理新接入的請求消息if(key.isAcceptable()){ ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); //通過ServerSocketChannel的accept建立SocketChannel執行個體 //完成該操作意味着完成TCP三向交握,TCP實體鍊路正式建立 SocketChannel sc = ssc.accept(); //設定為非阻塞的 sc.configureBlocking(false); //注冊為讀 sc.register(selector, SelectionKey.OP_READ);}
           

3.1 隻有在register方法中注冊了相應的操作,Selector才會關心相應類型操作的請求。比如我們在上面的代碼中注冊了SelectionKey.OP_ACCEPT請求操作,那麼Seletor就關心新的用戶端接入,如果監聽到新的用戶端接入,就處理新的接入請求,完成TCP三向交握,建立實體鍊路。并把ServerSocketChannel注冊為SelectionKey.OP_READ讀操作。

//讀消息if(key.isReadable()){ SocketChannel sc = (SocketChannel) key.channel(); //建立ByteBuffer,并開辟一個1M的緩沖區 ByteBuffer buffer = ByteBuffer.allocate(1024); //讀取請求碼流,傳回讀取到的位元組數 int readBytes = sc.read(buffer); //讀取到位元組,對位元組進行編解碼 if(readBytes>0){ }//鍊路已經關閉,釋放資源 else if(readBytes<0){ key.cancel(); sc.close(); }}
           

3.2 當把新接入的用戶端連接配接注冊到Selector上,開始監聽都操作,讀取用戶端發送的網絡消息,把讀取到的消息放到緩沖區,然後進行服務端資料處理操作。

由于jdk的selector在linux等主流作業系統上通過epoll實作,它沒有連結句柄數的限制,意味着一個selector可以連接配接成千上萬個用戶端,而性能不會随着用戶端連接配接數的增長而線性下降,是以,它适合做高性能、高負載的網絡伺服器。

AIO通信模型

AIO是異步IO的縮寫,雖然NIO在網絡操作中,提供了非阻塞的方法,但是NIO的IO行為還是同步的。對于NIO來說,我們的業務線程是在IO操作準備好時,得到通知,接着由這個線程自行進行IO操作,IO操作本身是同步的。

//建立服務端通道channel = AsynchronousServerSocketChannel.open();//綁定端口channel.bind(new InetSocketAddress(port));//用于接收用戶端的連接配接channel.accept(this,new AcceptHandler());
           

1、與NIO不同,當進行讀寫操作時,隻須直接調用API的read或write方法即可。這兩種方法均為異步的,對于讀操作而言,當有流可讀取時,作業系統會将可讀的流傳入read方法的緩沖區,并通知應用程式;對于寫操作而言,當作業系統将write方法傳遞的流寫入完畢時,作業系統主動通知應用程式。 即可以了解為,read/write方法都是異步的,完成後會主動調用回調函數。在JDK1.7中,這部分内容被稱作NIO.2,主要在Java.nio.channels包下增加了下面四個異步通道:

  • AsynchronousSocketChannel
  • AsynchronousServerSocketChannel
  • AsynchronousFileChannel
  • AsynchronousDatagramChannel

在AIO socket程式設計中,服務端通道是AsynchronousServerSocketChannel,這個類提供了一個open()靜态工廠,一個bind()方法用于綁定服務端IP位址(還有端口号),另外還提供了accept()用于接收使用者連接配接請求。

public class AcceptHandler implements CompletionHandler{@Overridepublic void completed(AsynchronousSocketChannel channel,AsyncServerHandler serverHandler) {serverHandler.channel.accept(serverHandler, this);//建立新的BufferByteBuffer buffer = ByteBuffer.allocate(1024);//異步讀 第三個參數為接收消息回調的業務Handlerchannel.read(buffer, buffer, new ReadHandler(channel));}@Overridepublic void failed(Throwable exc, AsyncServerHandler serverHandler) {exc.printStackTrace();}}
           

2、發出一個事件(accept read write等)之後要指定時間處理類(回調函數),AIO中的事件處理類是CompletionHandler,這個接口定義了兩個方法,分别在一部操作和成功時被回調。

  • void completed(V result, A attachment);
  • void failed(Throwable exc, A attachment);

是以對于AIO來說,它不是在IO準備好時再通知線程,而是在IO操作已經完成後,再通知線程發出通知。是以AIO是不會阻塞的,此時我們的業務邏輯将變成一個回調函數,等待IO操作完成後,由系統自動觸發。

總結

BIO适用于連接配接數目比較小且固定的架構,這種方式對伺服器資源要求比較高。但程式簡單易了解。

NIO适用于連接配接數目多且連接配接比較短(輕操作)的架構,比如聊天伺服器,程式設計複雜。

AIO适用于連接配接數目多且連接配接比較長(重操作)的架構,比如相冊伺服器,程式設計比較複雜。