关于NIO进行socket通信的一个不解的地方

服务器端代码:


package nio.tcp;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.Iterator;
import java.util.Set;

public class UnblockServer {
public static void main(String[] args) {
SocketChannel socket = null;
try {
// 打开一个选择器
Selector selector = Selector.open();

// 客户端要使用SocketChannel,对应服务器的ServerSocketChannel
ServerSocketChannel ssc = ServerSocketChannel.open();

// 绑定服务器IP和端口
InetSocketAddress isa = new InetSocketAddress(8710);
ssc.socket().bind(isa);
ssc.configureBlocking(false);

// 向selector注册,要处理的是接收传入事件,所以权限设置为OP_ACCEPT
SelectionKey acceptKey = ssc.register(selector,SelectionKey.OP_ACCEPT);

// 操作?
while (selector.select() > 0) {
Set<SelectionKey> readyKeys = selector.selectedKeys();
Iterator it = readyKeys.iterator();
while (it.hasNext()) {
SelectionKey key = (SelectionKey) it.next();
it.remove();
if (key.isAcceptable()) {
System.out.println(
"Key is Acceptable");
socket = (SocketChannel) ssc.accept();
socket.configureBlocking(false);
socket.register(selector,
SelectionKey.OP_READ | SelectionKey.OP_WRITE);
}
if (key.isReadable()) {
System.out.println(
"Key is readable");
socket = (SocketChannel) key.channel();
ByteBuffer buf = ByteBuffer.allocate(1024);
socket.read(buf);
buf.flip();
// 这段实用
Charset charset = Charset.forName(
"us-ascii");
CharsetDecoder decoder = charset.newDecoder();
CharBuffer charBuffer = decoder.decode(buf);
String result = charBuffer.toString();
System.out.println(
"Receive Data:" + result);
}
if (key.isWritable()) {
System.out.println(
"Key is writable");
String msg =
"Message from server";
socket = (SocketChannel) key.channel();
//类型转换,实用
Charset set = Charset.forName(
"us-ascii");
CharsetDecoder dec = set.newDecoder();
CharBuffer charBuf = dec.decode(ByteBuffer.wrap(msg.getBytes()));
System.out.println(
"Message from server:" + charBuf.toString());
int nBytes = socket.write(ByteBuffer.wrap((charBuf.toString()).getBytes()));
}
}
}
} catch (Exception e) {
e.printStackTrace();
}

}
}

客户端代码:



package nio.tcp;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class UnblockClient {

public static void main(String[] args) {
try {

InetSocketAddress ia = new InetSocketAddress(InetAddress.getLocalHost(),8710);

//客户端要使用SocketChannel,对应服务器的ServerSocketChannel
SocketChannel client = SocketChannel.open();
client.connect(ia);
//设置为非阻塞,否则就和刚才的程序没啥区别了
client.configureBlocking(false);

//发送数据
sendMessage(client);


} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

public static int sendMessage(SocketChannel client) {
System.out.println(
"Inside SendMessage");
String msg = null;
ByteBuffer bytebuf;
int nBytes = 0;
try {
msg =
"It's message from client!";
System.out.println(
"msg is "+msg);
bytebuf = ByteBuffer.wrap(msg.getBytes());
for (int i = 0; i < 3; i++) {
nBytes = client.write(bytebuf);
System.out.println(i +
" finished");
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
client.close();
return -1;

} catch (IOException e) {
e.printStackTrace();
}
return nBytes;
}
}

我已经和网上的N多类似程序对照过了,但是我就是弄不明白,为什么客户端明明只是发送了三次数据,而服务器端却会不停地执行write和read操作呢?不是应该执行一次之后就完了的吗?

你仔细看过用到的类的说明吗?其中java.nio.channels.SelectionKey是这么说明的:
A selection key is created each time a channel is registered with a selector. A key remains valid until it is cancelled by invoking its cancel method, by closing its channel, or by closing its selector. Cancelling a key does not immediately remove it from its selector; it is instead added to the selector's cancelled-key set for removal during the next selection operation. The validity of a key may be tested by invoking its isValid method.
也就是说一个key用完不用了要调用cancel方法,或者关闭他的channel或selector,你的server代码中if(key.isAcceptable)处理完毕后应该关闭该key,要不下次循环认为它是valid还会再次符合key.isAcceptable。另外,一旦该key已经cancel掉了,下面再判断isReadable或者isWritable时要提前判断isValid或者干脆把你这个if逻辑改成
if (key.isAcceptable()) {
...
key.cancel();
} else if (key.isReadable()) {
...
key.cancel();
} else if (key.isWritable()) {
...
key.cancel();
}