注:該文章實際爲《java網絡編程》例11-1和例11-2的源碼勘誤!原例程經過實際測試並不能實現期望的功能,在分析代碼邏輯後勘誤如下!
在勘誤之前貼出書中原始例程(僅服務器有誤):
package org.nioTest;
import sun.rmi.runtime.Log;
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;
import java.util.logging.Logger;
public class Servser {
private final static Logger log = Logger.getLogger("Server.class");
private static int client_num = 0;
public static void main(String[] args) {
byte[] rotation = new byte[95 * 2];
for (byte i = ' '; i <= '~'; i++) {
rotation[i - ' '] = i;
rotation[i + 95 - ' '] = i;
}
ServerSocketChannel serverSocketChannel;
Selector selector;
try {
serverSocketChannel = ServerSocketChannel.open();
ServerSocket serverSocket = serverSocketChannel.socket();
InetSocketAddress address = new InetSocketAddress("localhost", 5000);
serverSocket.bind(address);
serverSocketChannel.configureBlocking(false);
selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
log.info("服務器通道" + client_num++ + "註冊完成->" + serverSocketChannel.toString());
} catch (IOException e) {
e.printStackTrace();
return;
}
/**
錯誤一、當遍歷選擇器後,起初result = 1,而後result將爲0,
然後無限循環;並不能跳出循環執行接下來的代碼!
**/
while (true) {
try {
int result = selector.select();
log.info("開始遍歷當前註冊爲選擇器的通道" + result);
} catch (IOException e) {
e.printStackTrace();
break;
}
}
Set<SelectionKey> selectionKeys = selector.selectedKeys();
log.info("獲取所有現在註冊的通道:");
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
log.info("當前通道:" + selectionKey.toString());
iterator.remove();
try {
if (selectionKey.isAcceptable()) {
log.info("當前通道是處理連接的通道,準備取出該通道創建對等端通道(該對等通道是寫通道)");
ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel();
try {
SocketChannel client = server.accept();
log.info("接收客戶端的連接:" + client);
client.configureBlocking(false);
/**
錯誤二:即使錯誤一解決掉,也就是說跳出循環(解決方式實例:
if (result != 0) {
log.info("已經有選擇器就緒");
break;
}
)
代碼原意是當建立一個連接後就繼續註冊一個“寫通道”,
這樣當循環後就註冊器列表就會多出一個通道(即不爲0),
就可以繼續判斷當前通道屬性,然後讀數據!但是回過頭
看該代碼所在的循環就會發現,註冊器列表Set只生成一次:
Set<SelectionKey> selectionKeys = selector.selectedKeys();
log.info("獲取所有現在註冊的通道:");
Iterator<SelectionKey> iterator = selectionKeys.iterator();
即使此次又註冊了一個註冊器(通道),並不會更新此時
註冊器列表的迭代器,這個時候執行完此次循環後迭代器
爲0,直接跳出循環了!所以正確的做法是:每次重新注
冊一個選擇器後就要重新遍歷所有的選擇器,然後形成新
的註冊器列表Set!所以,這個while循環其實應該放在
上一個while循環裏!
**/
SelectionKey selectionKey_write = client.register(selector, SelectionKey.OP_WRITE);
log.info("把該通道繼續註冊到選擇器中");
ByteBuffer byteBuffer = ByteBuffer.allocate(74);
byteBuffer.put(rotation, 0, 72);
byteBuffer.put((byte) '\r');
byteBuffer.put((byte) '\n');
byteBuffer.flip();
log.info("把待發出的數據綁定到寫選擇器(寫通道)上");
selectionKey_write.attach(byteBuffer);
} catch (IOException e) {
e.printStackTrace();
}
} else if (selectionKey.isWritable()) {
log.info("當前通道是處理寫數據的通道,準備取出該通道和該通道綁定的數據(用於輸出數據)");
SocketChannel client = (SocketChannel) selectionKey.channel();
ByteBuffer byteBuffer = (ByteBuffer) selectionKey.attachment();
if (!byteBuffer.hasRemaining()) {
byteBuffer.rewind();
int first = byteBuffer.get();
byteBuffer.rewind();
int postion = first - ' ' + 1;
byteBuffer.put(rotation, postion, 72);
byteBuffer.put((byte) '\r');
byteBuffer.put((byte) '\n');
byteBuffer.flip();
}
client.write(byteBuffer);
}
} catch (IOException e) {
selectionKey.cancel();
try {
selectionKey.channel().close();
} catch (IOException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}
}
}
}
代碼錯誤原因已經在源碼中說明!這是修改過的例程:
客戶端
package org.nioTest;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
import java.util.logging.Logger;
public class Client {
private final static Logger log = Logger.getLogger("Client.class");
public static void main(String[] args) {
try{
SocketAddress address = new InetSocketAddress("localhost",5000);
SocketChannel socketChannel_client = SocketChannel.open(address);
log.info("綁定服務器完成"+socketChannel_client.toString());
ByteBuffer byteBuffer = ByteBuffer.allocate(74);
WritableByteChannel out = Channels.newChannel(System.out);
while(socketChannel_client.read(byteBuffer) != -1){
byteBuffer.flip();
out.write(byteBuffer);
byteBuffer.clear();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
服務器:
package org.nioTest;
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;
import java.util.logging.Logger;
public class Servser {
private final static Logger log = Logger.getLogger("Server.class");
private static int client_num = 0;
public static void main(String[] args) {
byte[] rotation = new byte[95 * 2];
for (byte i = ' '; i <= '~'; i++) {
rotation[i - ' '] = i;
rotation[i + 95 - ' '] = i;
}
ServerSocketChannel serverSocketChannel;
Selector selector;
try {
serverSocketChannel = ServerSocketChannel.open();
ServerSocket serverSocket = serverSocketChannel.socket();
InetSocketAddress address = new InetSocketAddress("localhost", 5000);
serverSocket.bind(address);
serverSocketChannel.configureBlocking(false);
selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
log.info("服務器通道" + client_num++ + "註冊完成->" + serverSocketChannel.toString());
} catch (IOException e) {
e.printStackTrace();
return;
}
while (true) {
try {
int result = selector.select();
log.info("開始遍歷當前註冊爲選擇器的通道" + result);
Set<SelectionKey> selectionKeys = selector.selectedKeys();
log.info("獲取所有現在註冊的通道:");
Iterator<SelectionKey> iterator = selectionKeys.iterator();
/*注意此時的while循環放在這裏*/
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
log.info("當前通道:" + selectionKey.toString());
iterator.remove();
try {
if (selectionKey.isAcceptable()) {
log.info("當前通道是處理連接的通道,準備取出該通道創建對等端通道(該對等通道是寫通道)");
ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel();
try {
SocketChannel client = server.accept();
log.info("接收客戶端的連接:" + client);
client.configureBlocking(false);
SelectionKey selectionKey_write = client.register(selector, SelectionKey.OP_WRITE);
iterator = selector.selectedKeys().iterator();
log.info("把該通道繼續註冊到選擇器中");
ByteBuffer byteBuffer = ByteBuffer.allocate(74);
byteBuffer.put(rotation, 0, 72);
byteBuffer.put((byte) '\r');
byteBuffer.put((byte) '\n');
byteBuffer.flip();
log.info("把待發出的數據綁定到寫選擇器(寫通道)上");
selectionKey_write.attach(byteBuffer);
} catch (IOException e) {
e.printStackTrace();
}
} else if (selectionKey.isWritable()) {
log.info("當前通道是處理寫數據的通道,準備取出該通道和該通道綁定的數據(用於輸出數據)");
SocketChannel client = (SocketChannel) selectionKey.channel();
ByteBuffer byteBuffer = (ByteBuffer) selectionKey.attachment();
if (!byteBuffer.hasRemaining()) {
byteBuffer.rewind();
int first = byteBuffer.get();
byteBuffer.rewind();
int postion = first - ' ' + 1;
byteBuffer.put(rotation, postion, 72);
byteBuffer.put((byte) '\r');
byteBuffer.put((byte) '\n');
byteBuffer.flip();
}
client.write(byteBuffer);
}
} catch (IOException e) {
selectionKey.cancel();
try {
selectionKey.channel().close();
} catch (IOException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
break;
}
}
}
}
修改過後就可正常運行了!可以試着開一個服務器,同時開n個客戶端來驗證非阻塞模式下單線程也能同時處理多個請求的效果!
這裏並沒有詳細敘述NIO的相關知識,所以關於NIO的基本概念、入門教程請參考:
(1)Java NIO系列教程
(2)JAVA網絡編程第四版中文