程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 在Java中使用NIO進行網絡編程

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

編輯:關於JAVA

在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#ofargs
    throw new IllegalArgumentException(
      "Parameter(s): <Server> <Word> [<Port>]");
   String server = args[0];// ServernameorIPaddress
   // ConvertinputStringtobytesusingthedefaultcharset
   byte[] argument = args[1].getBytes();
   int servPort = (args.length == 3) ? Integer.parseInt(args[2]) :  7;
   // Createchannelandsettononblocking
   SocketChannel clntChan = SocketChannel.open();
   clntChan.configureBlocking(false);
   // Initiateconnectiontoserverandrepeatedlypolluntilcomplete
   if (!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;// Totalbytesreceivedsofar
   int bytesRcvd;// Bytesreceivedinlastread
   while (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:" + //  converttoStringperdefaultcharset
     new 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#ofargs
    throw new IllegalArgumentException("Parameter (s):<Port>...");
   }
   // Createaselectortomultiplexlisteningsocketsandconnections
   Selector selector = Selector.open();
   // Createlisteningsocketchannelforeachportandregisterselector
   for (String arg : args) {
    ServerSocketChannel listnChannel = ServerSocketChannel.open();
    listnChannel.socket().bind(
      new InetSocketAddress(Integer.parseInt(arg)));
    listnChannel.configureBlocking(false);//  mustbenonblockingtoregister
    // Registerselectorwithchannel.Thereturnedkeyisignored
    listnChannel.register(selector, SelectionKey.OP_ACCEPT);
   }
   // Createahandlerthatwillimplementtheprotocol
   TCPProtocol protocol = new EchoSelectorProtocol(BUFSIZE);
   while (true) {// Runforever,processingavailableI/Ooperations
   // Waitforsomechanneltobeready(ortimeout)
    if (selector.select(TIMEOUT) == 0) {// returns#ofreadychans
     System.out.print(".");
     continue;
    }
    // GetiteratoronsetofkeyswithI/Otoprocess
    Iterator<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/Obuffer
  public EchoSelectorProtocol(int bufSize) {
   this.bufSize = bufSize;
  }
  public void handleAccept(SelectionKey key) throws IOException {
   SocketChannel clntChan = ((ServerSocketChannel) key.channel ()).accept();
   clntChan.configureBlocking(false);// Mustbenonblockingtoregister
   // Registertheselectorwithnewchannelforreadandattachbytebuffer
   clntChan.register(key.selector(), SelectionKey.OP_READ,  ByteBuffer
     .allocate(bufSize));
  }
  public void handleRead(SelectionKey key) throws IOException {
   // Clientsocketchannelhaspendingdata
   SocketChannel 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).
    */
   // Retrievedatareadearlier
   ByteBuffer buf = (ByteBuffer) key.attachment();
   buf.flip();// Preparebufferforwriting
   SocketChannel clntChan = (SocketChannel) key.channel();
   clntChan.write(buf);
   if (!buf.hasRemaining()) {// Buffercompletelywritten?
   // Nothingleft,sonolongerinterestedinwrites
    key.interestOps(SelectionKey.OP_READ);
   }
   buf.compact();// Makeroomformoredatatobereadin
  }
}

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

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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved