Java IO與NIO
Linux I/O模型介紹
Linux IO流程
一個socket進程進行一次read可以分成兩個階段,等待數據是否準備好,以及數據從內核copy到用戶空間。
Linux IO模型
網絡IO的本質是socket的讀取,socket在linux系統被抽象爲流,IO可以理解爲對流的操作。對於一次IO訪問(以read爲例),數據會先被拷貝到操作系統內核的緩衝區中,然後纔會從操作系統內核的緩衝區拷貝到應用程序的地址空間。對於socket流而言
- 通常涉及等待網絡上的數據分組到達,然後被複制到內核的某個緩衝區。
- 把數據從內核緩衝區複製到應用進程緩衝區。
網絡應用需要處理2類問題:網絡IO和數據計算,相對於數據計算,網絡IO的延遲,給應用程序帶來的性能瓶頸較大。所以需要選擇一種合理的網絡IO模型,常見的網絡IO模型有5種。分別是阻塞式I/O模型、非阻塞式I/O模型、I/O多路複用、信號驅動式I/O、異步I/O
阻塞式I/O模型
- 阻塞就是一直等待到事情做完才返回
- 如果是阻塞進程read一直等待,進程控制權就沒了,要阻塞兩個階段,數據到來和數據從內核copy到進程緩衝區。
非阻塞式I/O模型
- 非阻塞就是立馬返回一個消息(沒做好或者已經做好)
- 非阻塞則是read一發出立馬返回一個錯誤信息,程序裏必須不斷去read直到數據到達返回可讀消息,然後進程阻塞把數據從內核緩衝區copy到進程緩衝區,實際阻塞的是後面取數據的階段。
阻塞式IO和非阻塞式IO的對比
針對一個IO,非阻塞IO沒有阻塞IO的效率高。而且相對於阻塞而言,非阻塞是輪詢要發出很多次系統調用的,而且很多次都是空輪詢,看起來毫無優勢可言。但是非阻塞IO因爲等待數據階段是非阻塞的,所以可以同時進行很多個IO操作。
I/O多路複用
儘管非阻塞支持同時多IO,但是資源上還是很浪費,存在很多空輪詢。於是出現了一種新的IO模型,叫做IO多路複用模型。例如select模型、poll模型、epoll模型
select模型
select模型支持一次性監控一大堆感興趣的IO事件發生的描述符集合,一旦有一個或多個IO準備就緒,就返回所有描述符集合。然後掃描返回列表,找到有可讀或者可寫的描述符,可能是一個也可能多個,進行相應第二階段讀取或者寫入數據。select模型調用的時候也是阻塞的,只是它支持多路IO前提下減少了不必要的空輪詢。
優點:
跨平臺性好
缺點:
- 首先支持的監控的描述符集合數量有限制,內核參數決定的。
- 每次select調用進程是阻塞的,並且需要把描述符集合從用戶態copy到內核態,一旦數量增加,資源消耗線性增加。
- select返回的描述符是沒有經過篩選的
信號驅動式I/O
首先我們允許Socket進行信號驅動IO,並安裝一個信號處理函數,進程繼續運行並不阻塞。當數據準備好時,進程會收到一個SIGIO信號,可以在信號處理函數中調用I/O操作函數處理數據。
數據準備階段:未阻塞,當數據準備完成之後,會主動的通知用戶進程數據已經準備完成,對用戶進程做一個回調。
數據拷貝階段:阻塞用戶進程,等待數據拷貝。
異步I/O
相對於同步IO,異步IO不是順序執行。用戶進程進行aio_read系統調用之後,無論內核數據是否準備好,都會直接返回給用戶進程,然後用戶態進程可以去做別的事情。等到socket數據準備好了,內核直接複製數據給進程,然後從內核向進程發送通知。IO兩個階段,進程都是非阻塞的。
小結
Java I/O
單線程程序
客戶端
package com.demo.io;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
public class EchoClient {
public static void main(String[] args) {
Socket echoSocket = null;
PrintWriter out = null;
BufferedReader in = null;
try {
echoSocket = new Socket("127.0.0.1", 8080);
out = new PrintWriter(echoSocket.getOutputStream(), true);
in = new BufferedReader(new InputStreamReader(
echoSocket.getInputStream()));
System.out.println("連接到服務器......");
System.out.println("請輸入消息[輸入\"Quit\"]退出:");
BufferedReader stdIn = new BufferedReader(new InputStreamReader(
System.in));
String userInput;
while ((userInput = stdIn.readLine()) != null) {
out.println(userInput);
System.out.println(in.readLine());
if (userInput.equals("Quit")) {
System.out.println("關閉客戶端......");
out.close();
in.close();
stdIn.close();
echoSocket.close();
System.exit(1);
}
System.out.println("請輸入消息[輸入\"Quit\"]退出:");
}
} catch (UnknownHostException e) {
System.err.println("Don't know about host: PallaviÕs MacBook Pro.");
System.exit(1);
} catch (IOException e) {
System.err.println("Couldn't get I/O for "
+ "the connection to: PallaviÕs MacBook Pro.");
System.exit(1);
}
}
}
服務端
package com.demo.io;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
public class SingleThreadEchoServer {
private int port;
public SingleThreadEchoServer(int port) {
this.port = port;
}
public void startServer() {
ServerSocket echoServer = null;
int i = 0;
System.out.println("服務器在端口[" + this.port + "]等待客戶請求......");
try {
echoServer = new ServerSocket(this.port);
while (true) {
Socket clientRequest = echoServer.accept();
handleRequest(clientRequest, i++);
}
} catch (IOException e) {
System.out.println(e);
}
}
private void handleRequest(Socket clientSocket, int clientNo) {
PrintStream os = null;
BufferedReader in = null;
try {
in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
os = new PrintStream(clientSocket.getOutputStream());
String inputLine;
while ((inputLine = in.readLine()) != null) {
// 輸入'Quit'退出
if (inputLine.equals("Quit")) {
System.out.println("關閉與客戶端[" + clientNo + "]......" + clientNo);
os.close();
in.close();
clientSocket.close();
break;
} else {
System.out.println("來自客戶端[" + clientNo + "]的輸入: [" + inputLine + "]!");
os.println("來自服務器端的響應:" + inputLine);
}
}
} catch (IOException e) {
System.out.println("Stream closed");
}
}
public static void main(String[] args) throws IOException {
new SingleThreadEchoServer(8080).startServer();
}
}
運行結果如下
多線程程序
客戶端
package com.demo.io;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
public class EchoClient {
public static void main(String[] args) {
Socket echoSocket = null;
PrintWriter out = null;
BufferedReader in = null;
try {
echoSocket = new Socket("127.0.0.1", 8080);
out = new PrintWriter(echoSocket.getOutputStream(), true);
in = new BufferedReader(new InputStreamReader(
echoSocket.getInputStream()));
System.out.println("連接到服務器......");
System.out.println("請輸入消息[輸入\"Quit\"]退出:");
BufferedReader stdIn = new BufferedReader(new InputStreamReader(
System.in));
String userInput;
while ((userInput = stdIn.readLine()) != null) {
out.println(userInput);
System.out.println(in.readLine());
if (userInput.equals("Quit")) {
System.out.println("關閉客戶端......");
out.close();
in.close();
stdIn.close();
echoSocket.close();
System.exit(1);
}
System.out.println("請輸入消息[輸入\"Quit\"]退出:");
}
} catch (UnknownHostException e) {
System.err.println("Don't know about host: PallaviÕs MacBook Pro.");
System.exit(1);
} catch (IOException e) {
System.err.println("Couldn't get I/O for "
+ "the connection to: PallaviÕs MacBook Pro.");
System.exit(1);
}
}
}
服務端
package com.demo.io;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class MultiThreadedEchoServerV1 {
private int port;
public MultiThreadedEchoServerV1(int port) {
this.port = port;
}
public void startServer() {
ServerSocket echoServer = null;
int i = 0;
System.out.println("服務器在端口[" + this.port + "]等待客戶請求......");
try {
echoServer = new ServerSocket(8080);
while (true) {
Socket clientRequest = echoServer.accept();
new Thread(new ThreadedServerHandler(clientRequest, i++)).start();
}
} catch (IOException e) {
System.out.println(e);
}
}
public static void main(String[] args) throws IOException {
new MultiThreadedEchoServerV1(8080).startServer();
}
}
package com.demo.io;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
public class ThreadedServerHandler implements Runnable {
Socket clientSocket = null;
int clientNo = 0;
ThreadedServerHandler(Socket socket, int i) {
if (socket != null) {
clientSocket = socket;
clientNo = i;
System.out.println("創建線程爲[" + clientNo + "]號客戶服務...");
}
}
@Override
public void run() {
PrintStream os = null;
BufferedReader in = null;
try {
in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
os = new PrintStream(clientSocket.getOutputStream());
String inputLine;
while ((inputLine = in.readLine()) != null) {
// 輸入'Quit'退出
if (inputLine.equals("Quit")) {
System.out.println("關閉與客戶端[" + clientNo + "]......" + clientNo);
os.close();
in.close();
clientSocket.close();
break;
} else {
System.out.println("來自客戶端[" + clientNo + "]的輸入: [" + inputLine + "]!");
os.println("來自服務器端的響應:" + inputLine);
}
}
} catch (IOException e) {
System.out.println("Stream closed");
}
}
}
運行結果如下
Java NIO
3大核心組件:Buffer、Channel、Selector
Java IO和Java NIO對比
Buffer
一個 Buffer 本質上是內存中的一塊, 可以將數據寫入這塊內存, 從這塊內存獲取數據。
Java NIO Buffer三大核心概念:position、limit、capacity
- 代表這個緩衝區的容量,一旦設定就不可以更改。比如 capacity 爲 1024 的 IntBuffer,代表其一次可以存放 1024 個 int 類型的值。
- 一旦 Buffer 的容量達到 capacity,需要清空 Buffer,才能重新寫入值。
- 從寫操作模式到讀操作模式切換的時候(flip),position 都會歸零,這樣就可以從頭開始讀寫了。
- 寫操作模式下,limit 代表的是最大能寫入的數據,這個時候 limit 等於 capacity。
- 寫結束後,切換到讀模式,此時的 limit 等於 Buffer 中實際的數據大小,因爲 Buffer 不一定被寫滿了。
案例
理解capacity、limit、position、mark
0 – mark – position – limit – capacity
package com.demo.nio.buffers;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
public class BufferCreate {
public static void main(String[] args) {
ByteBuffer buffer0 = ByteBuffer.allocate(10);
if (buffer0.hasArray()) {
System.out.println("buffer0 array: " + buffer0.array());
System.out.println("Buffer0 array offset: " + buffer0.arrayOffset());
}
System.out.println("Capacity: " + buffer0.capacity());
System.out.println("Limit: " + buffer0.limit());
System.out.println("Position: " + buffer0.position());
System.out.println("Remaining: " + buffer0.remaining());
System.out.println();
ByteBuffer buffer1 = ByteBuffer.allocateDirect(10);
if (buffer1.hasArray()) {
System.out.println("buffer1 array: " + buffer1.array());
System.out.println("Buffer1 array offset: " + buffer1.arrayOffset());
}
System.out.println("Capacity: " + buffer1.capacity());
System.out.println("Limit: " + buffer1.limit());
System.out.println("Position: " + buffer1.position());
System.out.println("Remaining: " + buffer1.remaining());
System.out.println();
byte[] bytes = new byte[10];
ByteBuffer buffer2 = ByteBuffer.wrap(bytes);
if (buffer2.hasArray()) {
System.out.println("buffer2 array: " + buffer2.array());
System.out.println("Buffer2 array offset: " + buffer2.arrayOffset());
}
System.out.println("Capacity: " + buffer2.capacity());
System.out.println("Limit: " + buffer2.limit());
System.out.println("Position: " + buffer2.position());
System.out.println("Remaining: " + buffer2.remaining());
System.out.println();
byte[] bytes2 = new byte[10];
ByteBuffer buffer3 = ByteBuffer.wrap(bytes2, 2, 3);
if (buffer3.hasArray()) {
System.out.println("buffer3 array: " + buffer3.array());
System.out.println("Buffer3 array offset: " + buffer3.arrayOffset());
}
System.out.println("Capacity: " + buffer3.capacity());
System.out.println("Limit: " + buffer3.limit());
System.out.println("Position: " + buffer3.position());
System.out.println("Remaining: " + buffer3.remaining());
}
}
package com.demo.nio.buffers;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
public class BufferAccess {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(10);
printBuffer(buffer);
buffer.put((byte)'H').put((byte)'e').put((byte)'l').put((byte)'l').put((byte)'0');
printBuffer(buffer);
buffer.flip();
printBuffer(buffer);
//取buffer
System.out.println("" + (char) buffer.get() + (char) buffer.get());
printBuffer(buffer);
buffer.mark();
printBuffer(buffer);
//讀取兩個元素後,恢復到之前mark的位置處
System.out.println("" + (char) buffer.get() + (char) buffer.get());
printBuffer(buffer);
buffer.reset();
//buffer.rewind();
printBuffer(buffer);
buffer.compact();
printBuffer(buffer);
buffer.clear();
printBuffer(buffer);
}
private static void printBuffer(Buffer buffer) {
System.out.println("[limit=" + buffer.limit()
+", position = " + buffer.position()
+", capacity = " + buffer.capacity()
+", array = " + buffer.toString()+"]");
}
}
package com.demo.nio.buffers;
import java.nio.Buffer;
import java.nio.ByteBuffer;
/**
* 緩衝區分片與數據共享
*/
public class BufferSlice {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(10);
for(int i = 0; i < buffer.capacity(); i++) {
buffer.put((byte) i);
}
printBuffer(buffer);
//
buffer.position(3).limit(7);
printBuffer(buffer);
ByteBuffer sliceBuffer = buffer.slice();
for(int i = 0;i < sliceBuffer.capacity();i++) {
byte b = sliceBuffer.get();
b *= 11;
sliceBuffer.put(i, b);
}
printBuffer(sliceBuffer);
buffer.position(0).limit(buffer.capacity());
while(buffer.hasRemaining()) {
System.out.println(buffer.get());
}
printBuffer(buffer);
}
private static void printBuffer(Buffer buffer) {
System.out.println("[limit=" + buffer.limit()
+", position = " + buffer.position()
+", capacity = " + buffer.capacity()
+", array = " + buffer.toString()+"]");
}
}
package com.demo.nio.buffers;
import java.nio.Buffer;
import java.nio.CharBuffer;
public class DuplicateBuffer {
public static void main(String[] args) {
CharBuffer buffer = CharBuffer.allocate(8);
for(int i= 0 ; i < buffer.capacity() ; i++) {
buffer.put(String.valueOf(i).charAt(0));
}
printBuffer(buffer);
buffer.flip();
printBuffer(buffer);
buffer.position(3).limit(6).mark().position(5);
printBuffer(buffer);
CharBuffer dupeBuffer = buffer.duplicate();
buffer.clear();
printBuffer(buffer);
printBuffer(dupeBuffer);
dupeBuffer.clear();
printBuffer(dupeBuffer);
}
private static void printBuffer(Buffer buffer) {
System.out.println("[limit=" + buffer.limit()
+", position = " + buffer.position()
+", capacity = " + buffer.capacity()
+", array = " + buffer.toString()+"]");
}
}
package com.demo.nio.buffers;
import java.nio.*;
public class SliceBuffer {
static public void main(String args[]) throws Exception {
ByteBuffer buffer = ByteBuffer.allocate(10);
for (int i = 0; i < buffer.capacity(); ++i) {
buffer.put((byte) i);
}
buffer.position(3);
buffer.limit(7);
ByteBuffer slice = buffer.slice();
for (int i = 0; i < slice.capacity(); ++i) {
byte b = slice.get(i);
b *= 11;
slice.put(i, b);
}
buffer.position(0);
buffer.limit(buffer.capacity());
while (buffer.remaining() > 0) {
System.out.println(buffer.get());
}
}
}
DirectByteBuffer和non-direct ByteBuffer對比
小結
Buffer創建
1、allocate/allocateDirect方法
2、wrap方法
Buffer讀取
1、put/get方法
2、flip方法
3、mark/reset方法
4、compact方法
5、rewind/clear
Buffer複製 – 淺複製
1、duplicate方法
2、asReadOnlyBuffer方法
3、slice方法
Channel
NIO 操作始於通道,通道是數據來源或數據寫入的目的地
FileChannel:文件通道,用於文件的讀和寫
DatagramChannel:用於 UDP 連接的接收和發送
SocketChannel:把它理解爲 TCP 連接通道,簡單理解就是 TCP 客戶端
ServerSocketChannel:TCP 對應的服務端,用於監聽某個端口進來的請求
案例
package com.demo.nio.channels;
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
public class CopyFile {
static public void main(String args[]) throws Exception {
String infile = "H:\\workspace\\learning-nio\\src\\main\\resources\\CopyFile.java";
String outfile = "H:\\workspace\\learning-nio\\src\\main\\resources\\CopyFile.java.copy";
// 從流中獲取通道
FileInputStream fin = new FileInputStream(infile);
FileOutputStream fout = new FileOutputStream(outfile);
FileChannel fcin = fin.getChannel();
FileChannel fcout = fout.getChannel();
// 創建緩衝區
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (true) {
// 讀入之前要清空
buffer.clear();
// position自動前進
int r = fcin.read(buffer);
if (r == -1) {
break;
}
// position = 0; limit=讀到的字節數
buffer.flip();
// 從 buffer 中讀
fcout.write(buffer);
}
}
}
Selector
Selector是Java NIO中的一個組件,用於檢查一個或多個NIO Channel的狀態是否處於可讀、可寫。如此可以實現單線程管理多個channels,也就是可以管理多個網絡連接。
創建Selector(Creating a Selector)
Selector selector = Selector.open();
註冊Channel到Selector上(Registering Channels with the Selector)
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
register的第二個參數,這個參數是一個“關注集合”,代表關注的channel狀態,
有四種基礎類型可供監聽, 用SelectionKey中的常量表示如下:
SelectionKey.OP_CONNECT
SelectionKey.OP_ACCEPT
SelectionKey.OP_READ
SelectionKey.OP_WRITE
從Selector中選擇channel(Selecting Channels via a Selector)
一旦向Selector註冊了一個或多個channel後,就可以調用select來獲取channel
select方法會返回所有處於就緒狀態的channel
select方法具體如下:
int select()
int select(long timeout)
int selectNow()
select()方法的返回值是一個int,代表有多少channel處於就緒了。也就是自上一次select後有多少channel進入就緒。
selectedKeys()
在調用select並返回了有channel就緒之後,可以通過選中的key集合來獲取channel,這個操作通過調用selectedKeys()方法:
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if(key.isAcceptable()) {
// a connection was accepted by a ServerSocketChannel.
} else if (key.isConnectable()) {
// a connection was established with a remote server.
} else if (key.isReadable()) {
// a channel is ready for reading
} else if (key.isWritable()) {
// a channel is ready for writing
}
keyIterator.remove();
}
案例
客戶端
package com.demo.nio.demo;
public class NIOEchoClient {
public static void main(String[] args) {
int port = 8080;
if (args != null && args.length > 0) {
try {
port = Integer.valueOf(args[0]);
} catch (NumberFormatException e) {
}
}
new Thread(new NIOEchoClientHandler("127.0.0.1", port), "NIOEchoClient-001").start();
}
}
package com.demo.nio.demo;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
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.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class NIOEchoClientHandler implements Runnable {
private String host;
private int port;
private Selector selector;
private SocketChannel socketChannel;
private ExecutorService executorService;
private volatile boolean stop;
public NIOEchoClientHandler(String host, int port) {
this.host = host == null ? "127.0.0.1" : host;
this.port = port;
this.executorService = Executors.newSingleThreadExecutor();
try {
selector = Selector.open();
socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
@Override
public void run() {
try {
socketChannel.register(selector, SelectionKey.OP_CONNECT);
socketChannel.connect(new InetSocketAddress(host, port));
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
while (!stop) {
try {
selector.select(1000);
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectedKeys.iterator();
SelectionKey key = null;
while (it.hasNext()) {
key = it.next();
it.remove();
try {
handleInput(key);
} catch (Exception e) {
if (key != null) {
key.cancel();
if (key.channel() != null)
key.channel().close();
}
}
}
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
// 多路複用器關閉後,所有註冊在上面的Channel和Pipe等資源都會被自動去註冊並關閉,所以不需要重複釋放資源
if (selector != null) {
try {
selector.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (executorService != null) {
executorService.shutdown();
}
}
private void handleInput(SelectionKey key) throws IOException {
if (key.isValid()) {
// 判斷是否連接成功
SocketChannel sc = (SocketChannel) key.channel();
if (key.isConnectable()) {
if (sc.finishConnect()) {
System.out.println("連接到服務器......");
ByteBuffer buffer = ByteBuffer.allocate(1024);
System.out.println("請輸入消息[輸入\"Quit\"]退出:");
executorService.submit(() -> {
while (true) {
try {
buffer.clear();
InputStreamReader input = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(input);
String msg = br.readLine();
if (msg.equals("Quit")) {
System.out.println("關閉客戶端......");
key.cancel();
sc.close();
this.stop = true;
break;
}
buffer.put(msg.getBytes());
buffer.flip();
sc.write(buffer);
System.out.println("請輸入消息[輸入\"Quit\"]退出:");
} catch (Exception ex) {
ex.printStackTrace();
}
}
});
sc.register(selector, SelectionKey.OP_READ);
} else {
// 連接失敗,進程退出
System.exit(1);
}
}
if (key.isReadable()) {
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int readBytes = sc.read(readBuffer);
if (readBytes > 0) {
readBuffer.flip();
byte[] bytes = new byte[readBuffer.remaining()];
readBuffer.get(bytes);
String body = new String(bytes, "UTF-8");
System.out.println(body);
if (body.equals("Quit")) {
this.stop = true;
}
} else if (readBytes < 0) {
// 對端鏈路關閉
key.cancel();
sc.close();
}
}
if (key.isWritable()) {
System.out.println("The key is writable");
}
}
}
}
服務端
package com.demo.nio.demo;
import java.io.IOException;
public class NIOEchoServer {
public static void main(String[] args) throws IOException {
int port = 8080;
if (args != null && args.length > 0) {
try {
port = Integer.valueOf(args[0]);
} catch (NumberFormatException e) {
// 採用默認值
}
}
EchoHandler timeServer = new EchoHandler(port);
new Thread(timeServer, "NIO-MultiplexerTimeServer-001").start();
}
}
package com.demo.nio.demo;
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;
import java.util.Set;
public class EchoHandler implements Runnable {
private Selector selector;
private ServerSocketChannel servChannel;
private volatile boolean stop;
private int num = 0;
public EchoHandler(int port) {
try {
selector = Selector.open();
servChannel = ServerSocketChannel.open();
servChannel.configureBlocking(false);
servChannel.socket().bind(new InetSocketAddress(port), 1024);
servChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服務器在端口[" + port + "]等待客戶請求......");
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
public void stop() {
this.stop = true;
}
@Override
public void run() {
while (!stop) {
try {
selector.select(1000);
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectedKeys.iterator();
SelectionKey key = null;
while (it.hasNext()) {
key = it.next();
it.remove();
try {
handleInput(key);
} catch (Exception e) {
if (key != null) {
key.cancel();
if (key.channel() != null) {
key.channel().close();
}
}
}
}
} catch (Throwable t) {
t.printStackTrace();
}
}
// 多路複用器關閉後,所有註冊在上面的Channel和Pipe等資源都會被自動去註冊並關閉,所以不需要重複釋放資源
if (selector != null) {
try {
selector.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void handleInput(SelectionKey key) throws IOException {
if (key.isValid()) {
// 處理新接入的請求消息
if (key.isAcceptable()) {
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
// Non blocking, never null
SocketChannel socketChannel = ssc.accept();
socketChannel.configureBlocking(false);
SelectionKey sk = socketChannel.register(selector, SelectionKey.OP_READ);
sk.attach(num++);
}
if (key.isReadable()) {
// 讀取數據
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int readBytes = sc.read(readBuffer);
if (readBytes > 0) {
readBuffer.flip();
byte[] bytes = new byte[readBuffer.remaining()];
readBuffer.get(bytes);
String body = new String(bytes, "UTF-8");
System.out.println("來自客戶端[" + key.attachment() + "]的輸入: [" + body.trim() + "]!");
if (body.trim().equals("Quit")) {
System.out.println("關閉與客戶端[" + key.attachment() + "]......");
key.cancel();
sc.close();
} else {
String response = "來自服務器端的響應:" + body;
doWrite(sc, response);
}
} else if (readBytes < 0) {
// 對端鏈路關閉
key.cancel();
sc.close();
} else {
}
}
}
}
private void doWrite(SocketChannel channel, String response) throws IOException {
if (response != null && response.trim().length() > 0) {
byte[] bytes = response.getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
writeBuffer.put(bytes);
writeBuffer.flip();
channel.write(writeBuffer);
}
}
}
NIO的優點
- 事件驅動模型
- 單線程處理多任務,避免多線程
- 非阻塞IO,IO讀寫不再阻塞,而是返回0
- 基於block的傳輸,通常比基於流的傳輸更高效
- 更高級的IO函數,zero-copy
- IO多路複用大大提高了java網絡應用的可伸縮性和實用性
NIO的缺點
- NIO不一定更快的場景
客戶端應用
連接數<1000
併發程度不高
局域網環境下 - NIO仍然是基於各個OS平臺的IO系統實現的,差異仍然存在
- 離散的事件驅動模型,編程困難