在Java中使用NIO進行網路編程

來源:互聯網
上載者:User

在JDK中,有一個非常有意思的庫:NIO(New I/O)。這個庫中有3個重要的類,分別是java.nio.channels中Selector和Channel,以及java.nio中的Buffer。

本篇文章我們首先瞭解一下為什麼需要NIO來進行網路編程,然後看看一步一步來講解如何在網路編程中使用NIO。

為什麼需要NIO

使用Java編寫過Socket程式的同學一定都知道Socket和SocketServer。當調用某個調用的時候,調用的地方就會阻塞,等待響應。這種方式對於小規模的程式非常方便,但是對於大型的程式就有點力不從心了,當有大量的串連的時候,我們可以為每一個串連建立一個線程來操作。但是這種做法帶來的缺陷也是顯而易見的:

    1. 硬體能夠支援大量的並發。

    2. 並發的數量始終有一個上限。

    3. 各個線程之間的優先順序不好控制。

    4. 各個Client之間的互動與同步困難。

      我們也可以使用一個線程來處理所有的請求,使用不阻塞的IO,輪詢查詢所有的Client。這種做法同樣也有缺陷:無法迅速響應Client端,同時會消耗大量輪詢查詢的時間。

      所以,我們需要一種poll的模式來處理這種情況,從大量的網路連接中找出來真正需要服務的Client。這正是NIO誕生的原因:提供一種Poll的模式,在所有的Client中找到需要服務的Client。

      回到我們剛剛說到的3個最最重要的Class:java.nio.channels中Selector和Channel,以及java.nio中的Buffer。

      Channel代表一個可以被用於Poll操作的對象(可以是檔案流也可以使網路流),Channel能夠被註冊到一個Selector中。通過調用Selector的select方法可以從所有的Channel中找到需要服務的執行個體(Accept,read ..)。Buffer對象提供讀寫資料的緩衝。相對於我們熟悉的Stream對象,Buffer提供更好的效能以及更好的編程透明性(人為控制緩衝的大小以及具體的操作)。

      配合Buffer使用Channel

      與傳統模式的編程不用,Channel不使用Stream,而是Buffer。我們來實現一個簡單的非阻塞Echo Client:

      package com.cnblogs.gpcuster;

      import java.net.InetSocketAddress;
      import java.net.SocketException;
      import java.nio.ByteBuffer;
      import java.nio.channels.SocketChannel;

      public class TCPEchoClientNonblocking {
      public static void main(String args[]) throws Exception {
      if ((args.length < 2) || (args.length > 3))// Testforcorrect#ofargsthrow new IllegalArgumentException(
      "Parameter(s): <Server> <Word> [<Port>]");
      String server = args[0];// ServernameorIPaddress// ConvertinputStringtobytesusingthedefaultcharsetbyte[] argument = args[1].getBytes();
      int servPort = (args.length == 3) ? Integer.parseInt(args[2]) : 7;
      // CreatechannelandsettononblockingSocketChannel clntChan = SocketChannel.open();clntChan.configureBlocking(false);
      // Initiateconnectiontoserverandrepeatedlypolluntilcompleteif (!clntChan.connect(new InetSocketAddress(server, servPort))) {
      while (!clntChan.finishConnect()) {
      System.out.print(".");// Dosomethingelse}}ByteBuffer writeBuf = ByteBuffer.wrap(argument);ByteBuffer readBuf = ByteBuffer.allocate(argument.length);int totalBytesRcvd = 0;// Totalbytesreceivedsofarint bytesRcvd;// Bytesreceivedinlastreadwhile (totalBytesRcvd < argument.length) {
      if (writeBuf.hasRemaining()) {
      clntChan.write(writeBuf);
      }
      if ((bytesRcvd = clntChan.read(readBuf)) == -1) {
      throw new SocketException("Connection closed prematurely");
      }
      totalBytesRcvd += bytesRcvd;
      System.out.print(".");// Dosomethingelse}System.out.println("Received:" + // converttoStringperdefaultcharsetnew String(readBuf.array(), 0, totalBytesRcvd));
      clntChan.close();
      }
      }

      這段代碼使用ByteBuffer來儲存讀寫的資料。通過clntChan.configureBlocking(false); 設定後,其中的connect,read,write操作都不回阻塞,而是立刻放回結果。

      使用Selector

       

       

      Selector的可以從所有的被註冊到自己Channel中找到需要服務的執行個體。

      我們來實現Echo Server。

      首先,定義一個介面:

      package com.cnblogs.gpcuster;

      import java.nio.channels.SelectionKey;
      import java.io.IOException;

      public interface TCPProtocol {
      void handleAccept(SelectionKey key) throws IOException;

      void handleRead(SelectionKey key) throws IOException;

      void handleWrite(SelectionKey key) throws IOException;
      }
      我們的Echo Server將使用這個介面。然後我們實現Echo Server:
      import java.io.IOException;
      import java.net.InetSocketAddress;
      import java.nio.channels.SelectionKey;
      import java.nio.channels.Selector;
      import java.nio.channels.ServerSocketChannel;
      import java.util.Iterator;

      public class TCPServerSelector {
      private static final int BUFSIZE = 256;// Buffersize(bytes)private static final int TIMEOUT = 3000;// Waittimeout(milliseconds)public static void main(String[] args) throws IOException {
      if (args.length < 1) {// Testforcorrect#ofargsthrow new IllegalArgumentException("Parameter(s):<Port>...");
      }
      // CreateaselectortomultiplexlisteningsocketsandconnectionsSelector selector = Selector.open();// Createlisteningsocketchannelforeachportandregisterselectorfor (String arg : args) {
      ServerSocketChannel listnChannel = ServerSocketChannel.open();
      listnChannel.socket().bind(
      new InetSocketAddress(Integer.parseInt(arg)));
      listnChannel.configureBlocking(false);// mustbenonblockingtoregister// Registerselectorwithchannel.ThereturnedkeyisignoredlistnChannel.register(selector, SelectionKey.OP_ACCEPT);}// CreateahandlerthatwillimplementtheprotocolTCPProtocol protocol = new EchoSelectorProtocol(BUFSIZE);
      while (true) {// Runforever,processingavailableI/Ooperations// Waitforsomechanneltobeready(ortimeout)if (selector.select(TIMEOUT) == 0) {// returns#ofreadychansSystem.out.print(".");
      continue;
      }
      // GetiteratoronsetofkeyswithI/OtoprocessIterator<SelectionKey> keyIter = selector.selectedKeys().iterator();while (keyIter.hasNext()) {
      SelectionKey key = keyIter.next();// Keyisbitmask// Serversocketchannelhaspendingconnectionrequests?if (key.isAcceptable()) {
      protocol.handleAccept(key);
      }
      // Clientsocketchannelhaspendingdata?if (key.isReadable()) {
      protocol.handleRead(key);
      }
      // Clientsocketchannelisavailableforwritingand// keyisvalid(i.e.,channelnotclosed)?if (key.isValid() && key.isWritable()) {
      protocol.handleWrite(key);
      }
      keyIter.remove();// removefromsetofselectedkeys}}}}
       

      我們通過listnChannel.register(selector, SelectionKey.OP_ACCEPT); 註冊了一個我們感興趣的事件,然後調用selector.select(TIMEOUT)等待訂閱的時間發生,然後再採取相應的處理措施。

      最後我們實現EchoSelectorProtocol

      package com.cnblogs.gpcuster;

      import java.nio.channels.SelectionKey;
      import java.nio.channels.SocketChannel;
      import java.nio.channels.ServerSocketChannel;
      import java.nio.ByteBuffer;
      import java.io.IOException;

      public class EchoSelectorProtocol implements TCPProtocol {
      private int bufSize;// SizeofI/Obufferpublic EchoSelectorProtocol(int bufSize) {
      this.bufSize = bufSize;
      }

      public void handleAccept(SelectionKey key) throws IOException {
      SocketChannel clntChan = ((ServerSocketChannel) key.channel()).accept();
      clntChan.configureBlocking(false);// Mustbenonblockingtoregister// RegistertheselectorwithnewchannelforreadandattachbytebufferclntChan.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(bufSize));}public void handleRead(SelectionKey key) throws IOException {
      // ClientsocketchannelhaspendingdataSocketChannel clntChan = (SocketChannel) key.channel();ByteBuffer buf = (ByteBuffer) key.attachment();long bytesRead = clntChan.read(buf);
      if (bytesRead == -1) {// Didtheotherendclose?clntChan.close();} else if (bytesRead > 0) {
      // Indicateviakeythatreading/writingarebothofinterestnow.key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);}}public void handleWrite(SelectionKey key) throws IOException {
      /*
      * Channelisavailableforwriting,andkeyisvalid(i.e.,clientchannel
      * notclosed).
      */// RetrievedatareadearlierByteBuffer buf = (ByteBuffer) key.attachment();buf.flip();// PreparebufferforwritingSocketChannel clntChan = (SocketChannel) key.channel();clntChan.write(buf);if (!buf.hasRemaining()) {// Buffercompletelywritten?// Nothingleft,sonolongerinterestedinwriteskey.interestOps(SelectionKey.OP_READ);}buf.compact();// Makeroomformoredatatobereadin}}

      在這裡,我們又進一步對Selector註冊了相關的事件:key.interestOps(SelectionKey.OP_READ);

      這樣,我們就實現了基於NIO的Echo 系統。

      參考

      《TCP.IP.Sockets.in.Java.2nd.Edition》

      聯繫我們

      該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

      如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

      A Free Trial That Lets You Build Big!

      Start building with 50+ products and up to 12 months usage for Elastic Compute Service

      • Sales Support

        1 on 1 presale consultation

      • After-Sales Support

        24/7 Technical Support 6 Free Tickets per Quarter Faster Response

      • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.