【Java網路程式設計】:JDK API實現OIO和NIO
阿新 • • 發佈:2018-12-27
前言
網路程式設計是Java的一大難點,JDK自帶的api可以實現網路程式設計。
我們將從一個應用程式開始我們對傳輸的學習,這個應用程式只簡單地接受連線,然後向客戶端寫“Hi!”,然後關閉連線。
1. OIO實現
應用程式的阻塞(OIO)版程式碼如下:
package chx.demo;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.Charset;
public class PlainOioServer {
public void serve(int port) throws IOException {
//1. 將伺服器繫結到指定埠
final ServerSocket socket = new ServerSocket(port);
try {
for(;;) {
// 2. for迴圈不斷地接受連線
final Socket clientSocket = socket.accept();
System. out.println(
"Accepted connection from " + clientSocket);
// 3. 建立一個新的執行緒來處理該連線
new Thread(new Runnable() {
@Override
public void run() {
OutputStream out;
try {
// 4. 將訊息寫給已連線的客戶端
out = clientSocket.getOutputStream();
out.write("Hi!\r\n".getBytes(
Charset.forName("UTF-8")));
out.flush();
// 5. 關閉連線
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
clientSocket.close();
} catch (IOException ex) {
// ignore on close
}
}
// 7.啟動執行緒
}
}).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
這段程式碼完全可以處理中等數量的併發客戶端。但是隨著應用程式變得流行起來,你會發現它並不能很好地伸縮到支撐成千上萬的併發連入連線。
你決定改用非同步網路程式設計,但是很快就發現非同步API 是完全不同的,以至於現在你不得不重寫你的應用程式。
2. NIO實現
其非阻塞版程式碼如下:
package nia.chapter4;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
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 PlainNioServer {
public void serve(int port) throws IOException {
// 例項化ServerSocketChannel
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
ServerSocket ss = serverChannel.socket();
InetSocketAddress address = new InetSocketAddress(port);
// 1. 將伺服器繫結到選定的埠
ss.bind(address);
// 2. 開啟Selector來處理 Channel
Selector selector = Selector.open();
// 3. 將ServerSocketChannel註冊到Selector以接受連線
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
final ByteBuffer msg = ByteBuffer.wrap("Hi!\r\n".getBytes());
for (;;){
try {
// 4. 等待需要處理的新事件;阻塞將一直持續到下一個傳入事件
selector.select();
} catch (IOException ex) {
ex.printStackTrace();
//handle exception
break;
}
// 5. 獲取所有接收事件的SelectionKey例項
Set<SelectionKey> readyKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = readyKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
try {
// 6.檢查事件是否是一個新的已經就緒可以被接受的連線
if (key.isAcceptable()) {
ServerSocketChannel server =
(ServerSocketChannel) key.channel();
SocketChannel client = server.accept();
client.configureBlocking(false);
// 7.接受客戶端,並將它註冊到選擇器
client.register(selector, SelectionKey.OP_WRITE |
SelectionKey.OP_READ, msg.duplicate());
System.out.println(
"Accepted connection from " + client);
}
// 8.檢查套接字是否已經準備好寫資料
if (key.isWritable()) {
SocketChannel client =
(SocketChannel) key.channel();
ByteBuffer buffer =
(ByteBuffer) key.attachment();
while (buffer.hasRemaining()) {
// 9. 將資料寫到已連線的客戶端
if (client.write(buffer) == 0) {
break;
}
}
// 10.關閉連線
client.close();
}
} catch (IOException ex) {
key.cancel();
try {
key.channel().close();
} catch (IOException cex) {
// ignore on close
}
}
}
}
}
}
如同你所看到的,雖然這段程式碼所做的事情與之前的版本完全相同,但是程式碼卻截然不同。
如果為了用於非阻塞I/O 而重新實現這個簡單的應用程式,都需要一次完全的重寫的話,那麼不難想象,移植真正複雜的應用程式需要付出什麼樣的努力。
所以Java JDK原生實現NIO是比較複雜的,學習門檻比較大。