NIO是jdk1.4版本以后發(fā)布的新特性,主要類位于包java.nio
下黎棠,NIO主要由以下幾個(gè)部分組成:
- Buffer
緩沖區(qū)晋渺,通常用于普通類與通道之間交換數(shù)據(jù)。緩沖區(qū)提供了一個(gè)會(huì)合點(diǎn):通道既可提取放在緩沖區(qū)中的數(shù)據(jù)(寫)脓斩,也可向緩沖區(qū)存入數(shù)據(jù)供讀饶疚鳌(讀)。 - Channel
通道随静,和傳統(tǒng)IO類中的流概念有些相似八千,但I(xiàn)O流是單向的,通道則是雙向的燎猛,基于NIO的數(shù)據(jù)交換都從通道中經(jīng)過(guò)恋捆,可以將通道中的數(shù)據(jù)寫入緩沖區(qū),也可以讀取緩沖區(qū)的數(shù)據(jù)進(jìn)通道重绷。 - Selector
選擇器沸停,基于多路復(fù)用的IO模型,作用類似于多路復(fù)用器昭卓,允許通過(guò)一個(gè)線程處理多個(gè)注冊(cè)的Channel愤钾。下圖表示了單線程一個(gè)選擇器同時(shí)處理三個(gè)通道的情況:
Buffer
緩沖區(qū)是包在一個(gè)對(duì)象內(nèi)的基本數(shù)據(jù)元素?cái)?shù)組。Buffer類相比一個(gè)簡(jiǎn)單數(shù)組的優(yōu)點(diǎn)是它將關(guān)于數(shù)據(jù)的數(shù)據(jù)內(nèi)容和信息包含在一個(gè)單一的對(duì)象中候醒。Buffer類以及它專有的子類定義了一個(gè)用于處理數(shù)據(jù)緩沖區(qū)的API能颁。
緩沖區(qū)維護(hù)了四個(gè)屬性來(lái)操作內(nèi)部數(shù)據(jù):
1.Capacity
容量,緩沖區(qū)所能容納的最大數(shù)據(jù)字節(jié)值倒淫,一旦固定即不可改變伙菊。
2.Limit
上界,緩沖區(qū)中第一個(gè)不能被操作的位置,可等同于現(xiàn)存數(shù)據(jù)計(jì)數(shù)占业。
3.Position
下一個(gè)操作位绒怨,由put和get方法更新。
4.Mark
備忘位置谦疾,不太常用南蹂。
緩沖區(qū)有讀和寫兩種模式,一般通過(guò)filp()
和clear()
來(lái)切換這兩種操作模式念恍。
public abstract class Buffer {
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
}
position和limit的含義取決于Buffer處在讀模式還是寫模式:
Channel
Channel是一個(gè)通道六剥,類似于輸入輸出流的概念。區(qū)別在于流是單向的峰伙,一般在傳統(tǒng)IO模型中進(jìn)行數(shù)據(jù)讀寫需要輸入輸出流搭配使用疗疟,但是Channel則提供了雙向數(shù)據(jù)通信的機(jī)制。
Channel的繼承關(guān)系比Buffer要復(fù)雜瞳氓,其具體實(shí)現(xiàn)依賴于不同的操作系統(tǒng)策彤。大體上來(lái)說(shuō),IO通道可以分為兩大類:文件通道和socket通道匣摘,常用的Channel類有FileChannel
,SocketChannel
,ServerSocketChannel
和DatagramChannel
店诗,適用于文件IO和網(wǎng)絡(luò)流IO。
常用的Channel類一般都實(shí)現(xiàn)了ReadableByteChannel
和WriteableByteChannel
音榜,因此他們具有雙向讀寫能力:
public class NIOTest {
public static void main(String[] args) throws IOException {
NIOTest nt = new NIOTest();
ReadableByteChannel src = Channels.newChannel(System.in);
WritableByteChannel dest = Channels.newChannel(System.out);
nt.channelCopy(src, dest);
src.close();
dest.close();
}
private void channelCopy(ReadableByteChannel src, WritableByteChannel dest) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(1024);
while(src.read(buffer) != -1){
buffer.flip();
while (buffer.hasRemaining()) {
dest.write(buffer);
}
buffer.clear();
}
}
}
通道內(nèi)容的復(fù)制即可通過(guò)這兩個(gè)類來(lái)實(shí)現(xiàn)庞瘸。
Selector
選擇器類管理著一個(gè)被注冊(cè)的通道集合的信息和它們的就緒狀態(tài)。通道是和選擇器一起被注冊(cè)的赠叼,并且使用選擇器來(lái)更新通道的就緒狀態(tài)擦囊。
SelectableChannel
提供了實(shí)現(xiàn)通道的可選擇性所需要的公共方法,FileChannel
并未繼承此抽象類嘴办,因此不具有可選擇性瞬场。SelectableChannel可以被注冊(cè)到Selector對(duì)象上,同時(shí)可以指定對(duì)那個(gè)選擇器而言涧郊,應(yīng)該關(guān)注何種IO操作泌类。
SelectionKey
封裝了特定的通道與特定的選擇器的注冊(cè)關(guān)系,指示了該注冊(cè)關(guān)系所關(guān)心的通道操作底燎,以及通道已經(jīng)準(zhǔn)備好的操作刃榨。
以下通過(guò)簡(jiǎn)單的回聲服務(wù)器來(lái)實(shí)例NIO的基本編程模型:
- 服務(wù)器端,接收客戶端發(fā)送的消息并發(fā)送回給客戶端
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.Iterator;
/**
* Created by Administrator on 2017/7/19.
* Intellij IDEA
*/
public class EchoServer {
private Selector selector;
private int port;
private ByteBuffer buffer = ByteBuffer.allocate(1024);
public EchoServer(int port) {
this.port = port;
}
public static void main(String[] args) throws IOException {
new EchoServer(8000).start();
}
private void start() throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(port));
serverSocketChannel.configureBlocking(false);
this.selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//ServerHandler handler = new ServerHandler();
while (true) {
selector.select();
Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
while (keys.hasNext()) {
SelectionKey key = keys.next();
try {
if (key.isAcceptable())
handleAccept(key);
else if (key.isReadable())
handleRead(key);
else if (key.isWritable())
handleWrite(key);
} catch (Exception e) {
keys.remove();
continue;
}
keys.remove();
}
}
}
private void handleWrite(SelectionKey key) throws IOException {
buffer.clear();
SocketChannel socketChannel = (SocketChannel) key.channel();
String message = "[message from server]";
buffer.put(message.getBytes());
buffer.flip();//prepare for write
socketChannel.write(buffer);//write to client channel
System.out.println("Server send message to client"+message);
socketChannel.register(selector, SelectionKey.OP_READ);
}
private void handleRead(SelectionKey key) throws IOException {
SocketChannel socketChannel = (SocketChannel) key.channel();
buffer.clear();//clear this buffer for next read event
int length = socketChannel.read(buffer);
if (length > 0){
String message = new String(buffer.array(), 0, length);
System.out.println("Server received message from client:" + message);
socketChannel.register(selector, SelectionKey.OP_WRITE);
}
}
private void handleAccept(SelectionKey key) throws IOException {
//waiting for client to connect
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
}
}
- 客戶端双仍,接收服務(wù)端發(fā)送的消息并發(fā)送消息給服務(wù)端
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;
/**
* Created by Administrator on 2017/7/19.
* Intellij IDEA
*/
public class EchoClient {
private int port;
private Selector selector;
private ByteBuffer buffer = ByteBuffer.allocate(1024);
public static void main(String[] args) throws IOException {
new EchoClient(8000).start();
}
public EchoClient(int port) {
this.port = port;
}
public void start() throws IOException {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
this.selector = Selector.open();
socketChannel.register(selector, SelectionKey.OP_CONNECT);
socketChannel.connect(new InetSocketAddress(8000));//connect to server.
while (true){
selector.select();
Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
while (keys.hasNext()) {
SelectionKey key = keys.next();
if (key.isConnectable())
handleConnect(key);
else if (key.isReadable())
handleRead(key);
else if (key.isWritable())
handleWrite(key);
keys.remove();
}
}
}
private void handleWrite(SelectionKey key) throws IOException {
buffer.clear();
SocketChannel socketChannel = (SocketChannel) key.channel();
String message = "#message from client#";
buffer.put(message.getBytes());
buffer.flip();
socketChannel.write(buffer);
System.out.println("Client send message to server:" + message);
socketChannel.register(selector, SelectionKey.OP_READ);
}
private void handleRead(SelectionKey key) throws IOException {
SocketChannel socketChannel = (SocketChannel) key.channel();
buffer.clear();
int length = socketChannel.read(buffer);//read data from channel
if (length > 0) {
String serverMessage = new String(buffer.array(), 0, length);
System.out.println("Client received message from server::" + serverMessage);
socketChannel.register(selector, SelectionKey.OP_WRITE);
}
}
private void handleConnect(SelectionKey key) throws IOException {
SocketChannel socketChannel = (SocketChannel) key.channel();
if (socketChannel.isConnectionPending()) {
socketChannel.finishConnect();
buffer.clear();
buffer.put("Message from client".getBytes());
buffer.flip();
socketChannel.write(buffer);
}
socketChannel.register(selector, SelectionKey.OP_READ);
}
}
可見(jiàn)枢希,基于NIO的socket編程確實(shí)比傳統(tǒng)Blocking IO模型要復(fù)雜,但無(wú)疑效率更高朱沃。這個(gè)實(shí)例中苞轿,Server端僅開(kāi)啟了一個(gè)線程用于處理IO操作茅诱,由于Selector的引入使得IO操作不再需要頻繁切換線程上下文,該Selector不斷輪詢注冊(cè)在它上面的key搬卒,完成相應(yīng)的IO操作瑟俭。