基於nio的socket編程

io編程中存在兩個問題,io是阻塞的,並且保持多個鏈接的時候須要加入多線程來保持socket鏈接。這種方式比較浪費資源,由於每一個鏈接都須要一個線程來保持,這在鏈接比較多的時候是一個浪費的資源是至關嚴重的。而兩個問題在nio編程中就很好的解決這個問題,可是相對來講,對於編程的複雜度也相對有點提升。java

 

這裏不去深刻的討論nio編程的原理,只是基於實用的角度上作一些簡單的介紹。在nio編程中,主要有三個組件比較重要:channel、buffer、selector。編程

 

channel:網絡

通道,它和io中流有點類似,因此的源數據或者目標數據都是直接和channel打交道的。java nio中channel有如下幾種實現,這些通道涵蓋了TCP和UDP網絡io:多線程

  • FileChannel
  • DatagramChannel
  • SocketChannel
  • ServerSocketChannel

 

buffer:socket

緩衝區 , 他是直接與channel交互的,咱們若是要讀取channle中的數據時,都是先把數據從channle中讀入到buffer中;一樣的,若是咱們要向channel中寫數據的時候,也要先把數據寫入到buffer中。java nio中有也有多個實現,他們不要爲了針對不一樣的數據類型:函數

  • ByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer

 

注意:這裏要介紹一個buffer中三個比較重要的方法,這三個方法在buffer讀寫的過程當中都頗有用。這三個方法都跟buffer中三個標記有關係:position、limit、capacity。position至關於指針,表示當前正在讀寫的位置;limit指的是界限,表示讀寫的時候界限;capacity表示容量,也就是buffer緩衝區的大小。爲了弄明白這些方法,瞭解這三個標記是前提,說了這麼多,這三個方法是:clear(); rewind(); flip()。這三個方法也就是調整這些標記位置,下面是這三個方法的源碼,相信很好理解:post

  1. public final Buffer clear() {   
  2.          position = 0; //設置當前下標爲0  
  3.          limit = capacity; //設置寫越界位置與和Buffer容量相同  
  4.          mark = -1; //取消標記  
  5.          return this;   

 

  1. public final Buffer rewind() {   
  2.         position = 0;   
  3.         mark = -1;    //取消標記 
  4.         return this;   
  5. }  

 

  1. public final Buffer flip() {   
  2.         limit = position;   
  3.         position = 0;   
  4.         mark = -1;    //取消標記 
  5.         return this;   
  6.  } 

 

java nio緩衝區中標誌mark標記,使緩衝區可以記住一個位置並在以後將其返回。緩衝區的標記在mark( )函數被調用以前是未定義的,調用時標記被設爲當前位置的值。reset( )函數將位置設爲當前的標記值。若是標記值未定義,調用reset( )將致使InvalidMarkException異常。一些緩衝區函數會拋棄已經設定的標記(rewind( ),clear( ),以及flip( )老是拋棄標記)。若是新設定的值比當前的標記小,調用limit( )或position( )帶有索引參數的版本會拋棄標記。如調用mark( )來設定mark = postion。調用reset( )設定position = mark。標記在設定前是未定義的(undefined)。這四個屬性之間老是遵循如下關係: 0 <= mark <= position <= limit <= capacitythis

 

 

 

selector:線程

選擇器,這個就是nio爲了解決io中多鏈接必需要多線程來處理的問題。selector容許單線程處理多個channel,你打開多個鏈接就會有多個channel,而selector能夠管理多個channel。值得注意的是,selector必需要和非阻塞的通道配合使用,也就是說selector不能和fileChannel一塊兒使用,由於fileChannel不能配置非阻塞的模式。指針

 

java io和java nio的主要區別:

一、io是面向流的,nio是面向緩衝區的。這就意味着io只能從流中讀入一個或者或者多個字節。而nio由於是面向緩衝區的,數據都是先讀入到緩衝區,也就是上面buffer中,並且在須要的時候,還能夠在緩衝區中先後移動來獲取不一樣位置的數據。可是也有個麻煩的地方,就是要檢查緩衝區中是否含有本身要處理的數據。

二、io是阻塞的,nio是非阻塞的。io在數據讀入或者寫入的時候是阻塞的,直到數據處理完成爲止。而nio則是非阻塞的,咱們從buffer中讀取數據的時候是當即返回的,可是這裏有個問題就是咱們沒法保證緩衝區中是有數據的。可是好處就是,咱們讀取數據的時候不會阻塞,能夠幹別的事情。

 

下圖能夠說明這個問題(左邊的是nio,右邊的是io):

下面就是一個基於NIO的socket編程的例子:

public class NioSocketServer {
    public static void main(String[] args) {
        try {
            ServerSocketChannel server = ServerSocketChannel.open(); //新建NIO通道
            server.configureBlocking(false); //配置通道爲非阻塞的
            ServerSocket socket = server.socket(); //建立socket服務
            socket.bind(new InetSocketAddress(9123)); //綁定端口號
            Selector selector = Selector.open(); //建立選擇器
            server.register(selector, SelectionKey.OP_ACCEPT); //將服務註冊到選擇器上,而且配置關注的事件
            //循環獲取通道內是否有關注的事件
            while(true) {
                //判斷通道中事發後有關注的事件。若是 >1 則表示通道中有關注的事件
                int num = selector.select();
                if(num < 1) {
                    continue;
                }
                Set selectKeys = selector.selectedKeys();//得到事件的key值
                //遍歷全部的事件
                Iterator iterator = selectKeys.iterator(); //得到迭代器
                while(iterator.hasNext()) {
                    SelectionKey key = (SelectionKey) iterator.next();
                    //移走這次事件
                    iterator.remove();
                    //判斷該事件是不是請求鏈接的事件
                    if(key.isAcceptable()) {
                        //得到相應的socket channel
                        SocketChannel client = server.accept();
                        System.out.println("接受來自" + client + "的請求!");
                        client.configureBlocking(false); //將通道配置成非阻塞的
                        ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[1024]); //建立緩衝區
                        //在此通道上註冊寫事件
                        SelectionKey keyClient = client.register(selector, SelectionKey.OP_WRITE);
                        //通道執行事件
                        keyClient.attach(byteBuffer);
                    }else if(key.isWritable()) {
                        //得到此通道上的socket channel
                        SocketChannel client = (SocketChannel) key.channel(); //得到當前事件下的socketChannel
                        ByteBuffer outByteBuffer = (ByteBuffer)key.attachment(); //得到通道上綁定的byteBuffer
                        //若是緩衝區存在數據,清除掉,防止數據粘連,重置一下
                        if(outByteBuffer.hasRemaining()) {
                            outByteBuffer.clear();
                        }
                        //往此通道上寫數據,先寫入byteBuffer中
                        outByteBuffer.put("hello world".getBytes());
                        outByteBuffer.flip();
                        client.write(outByteBuffer);
                    }
                    key.channel().close();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
相關文章
相關標籤/搜索