Java NIO基本使用實例
NIO是Java提供的非阻塞I/O API。
非阻塞的意義在于可以使用一個線程對大量的數(shù)據(jù)連接進(jìn)行處理,非常適用于"短數(shù)據(jù)長連接"的應(yīng)用場景,例如即時通訊軟件。
在一個阻塞C/S系統(tǒng)中,服務(wù)器要為每一個客戶連接開啟一個線程阻塞等待客戶端發(fā)送的消息.若使用非阻塞技術(shù),服務(wù)器可以使用一個線程對連接進(jìn)行輪詢,無須阻塞等待.這大大減少了內(nèi)存資源的浪費(fèi),也避免了服務(wù)器在客戶線程中不斷切換帶來的CPU消耗,服務(wù)器對CPU的有效使用率大大提高.
其核心概念包括Channel,Selector,SelectionKey,Buffer。
Channel是I/O通道,可以向其注冊Selector,應(yīng)用成功可以通過select操作獲取當(dāng)前通道已經(jīng)準(zhǔn)備好的可以無阻塞執(zhí)行的操作.這由SelectionKey表示。
SelectionKey的常量字段SelectionKey.OP_***分別對應(yīng)Channel的幾種操作例如connect(),accept(),read(),write()。
select操作后得到SelectionKey.OP_WRITE或者READ即可在Channel上面無阻塞調(diào)用read和write方法,Channel的讀寫操作均需要通過Buffer進(jìn)行.即讀是講數(shù)據(jù)從通道中讀入Buffer然后做進(jìn)一步處理.寫需要先將數(shù)據(jù)寫入Buffer然后通道接收Buffer。
下面是一個使用NIO的基本C/S示例.該示例只為顯示如何使用基本的API而存在,其代碼的健壯性,合理性都不具參考價值。
這個示例,實現(xiàn)一個簡單的C/S,客戶端想服務(wù)器端發(fā)送消息,服務(wù)器將收到的消息打印到控制臺.現(xiàn)實的應(yīng)用中需要定義發(fā)送數(shù)據(jù)使用的協(xié)議,以幫助服務(wù)器解析消息.本示例只是無差別的使用默認(rèn)編碼將收到的字節(jié)轉(zhuǎn)換字符并打印.通過改變初始分配的ByteBuffer的容量,可以看到打印消息的變化.容量越小,對一條消息的處理次數(shù)就越多,容量大就可以在更少的循環(huán)次數(shù)內(nèi)讀完整個消息.所以真是的應(yīng)用場景,要考慮適當(dāng)?shù)木彺娲笮∫蕴岣咝省?/p>
首先是Server:
- package hadix.demo.nio;
- import java.io.IOException;
- import java.net.InetSocketAddress;
- import java.nio.ByteBuffer;
- import java.nio.channels.SelectionKey;
- import java.nio.channels.Selector;
- import java.nio.channels.ServerSocketChannel;
- import java.nio.channels.SocketChannel;
- import java.util.*;
- import java.util.concurrent.ConcurrentHashMap;
- /**
- * User: hAdIx
- * Date: 11-11-2
- * Time: 上午11:26
- */
- public class Server {
- private Selector selector;
- private ByteBuffer readBuffer = ByteBuffer.allocate(8);//調(diào)整緩存的大小可以看到打印輸出的變化
- private Map<SocketChannel, byte[]> clientMessage = new ConcurrentHashMap<>();
- public void start() throws IOException {
- ServerSocketChannel ssc = ServerSocketChannel.open();
- ssc.configureBlocking(false);
- ssc.bind(new InetSocketAddress("localhost", 8001));
- selector = Selector.open();
- ssc.register(selector, SelectionKey.OP_ACCEPT);
- while (!Thread.currentThread().isInterrupted()) {
- selector.select();
- Set<SelectionKey> keys = selector.selectedKeys();
- Iterator<SelectionKey> keyIterator = keys.iterator();
- while (keyIterator.hasNext()) {
- SelectionKey key = keyIterator.next();
- if (!key.isValid()) {
- continue;
- }
- if (key.isAcceptable()) {
- accept(key);
- } else if (key.isReadable()) {
- read(key);
- }
- keyIterator.remove();
- }
- }
- }
- private void read(SelectionKey key) throws IOException {
- SocketChannel socketChannel = (SocketChannel) key.channel();
- // Clear out our read buffer so it's ready for new data
- this.readBuffer.clear();
- // Attempt to read off the channel
- int numRead;
- try {
- numRead = socketChannel.read(this.readBuffer);
- } catch (IOException e) {
- // The remote forcibly closed the connection, cancel
- // the selection key and close the channel.
- key.cancel();
- socketChannel.close();
- clientMessage.remove(socketChannel);
- return;
- }
- byte[] bytes = clientMessage.get(socketChannel);
- if (bytes == null) {
- bytes = new byte[0];
- }
- if (numRead > 0) {
- byte[] newBytes = new byte[bytes.length + numRead];
- System.arraycopy(bytes, 0, newBytes, 0, bytes.length);
- System.arraycopy(readBuffer.array(), 0, newBytes, bytes.length, numRead);
- clientMessage.put(socketChannel, newBytes);
- System.out.println(new String(newBytes));
- } else {
- String message = new String(bytes);
- System.out.println(message);
- }
- }
- private void accept(SelectionKey key) throws IOException {
- ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
- SocketChannel clientChannel = ssc.accept();
- clientChannel.configureBlocking(false);
- clientChannel.register(selector, SelectionKey.OP_READ);
- System.out.println("a new client connected");
- }
- public static void main(String[] args) throws IOException {
- System.out.println("server started...");
- new Server().start();
- }
- }
然后是Client:
- package hadix.demo.nio;
- import java.io.IOException;
- import java.net.InetSocketAddress;
- import java.nio.ByteBuffer;
- import java.nio.channels.SelectionKey;
- import java.nio.channels.Selector;
- import java.nio.channels.SocketChannel;
- import java.util.Iterator;
- import java.util.Scanner;
- import java.util.Set;
- /**
- * User: hAdIx
- * Date: 11-11-2
- * Time: 上午11:26
- */
- public class Client {
- public void start() throws IOException {
- SocketChannel sc = SocketChannel.open();
- sc.configureBlocking(false);
- sc.connect(new InetSocketAddress("localhost", 8001));
- Selector selector = Selector.open();
- sc.register(selector, SelectionKey.OP_CONNECT);
- Scanner scanner = new Scanner(System.in);
- while (true) {
- selector.select();
- Set<SelectionKey> keys = selector.selectedKeys();
- System.out.println("keys=" + keys.size());
- Iterator<SelectionKey> keyIterator = keys.iterator();
- while (keyIterator.hasNext()) {
- SelectionKey key = keyIterator.next();
- keyIterator.remove();
- if (key.isConnectable()) {
- sc.finishConnect();
- sc.register(selector, SelectionKey.OP_WRITE);
- System.out.println("server connected...");
- break;
- } else if (key.isWritable()) {
- System.out.println("please input message");
- String message = scanner.nextLine();
- ByteBuffer writeBuffer = ByteBuffer.wrap(message.getBytes());
- sc.write(writeBuffer);
- }
- }
- }
- }
- public static void main(String[] args) throws IOException {
- new Client().start();
- }
- }
此外有一個代碼寫得更好的例子,非常值得參考。http://rox-xmlrpc.sourceforge.net/niotut/index.html
這個例子里面的客戶端將消息發(fā)送給服務(wù)器,服務(wù)器收到后立即寫回給客戶端.例子中代碼雖然也沒有做有意義的處理,但是其結(jié)構(gòu)比較合理,值得以此為基礎(chǔ)進(jìn)行現(xiàn)實應(yīng)用的擴(kuò)展開發(fā)。
原文鏈接:http://hadix.iteye.com/blog/1233180
【編輯推薦】
- Java NIO的介紹及工作原理
- Apache Ant對決Make:實戰(zhàn)Java構(gòu)建工具
- 面試Java前必須了解的10個概念
- Java七步創(chuàng)建以JDBC連接數(shù)據(jù)庫的程序
- Java NIO之選擇就緒模式