注:该文章实际为《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网络编程第四版中文