select循环里,不断有READ就绪事件
服务端代码:
package NonBlocking;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
public class TestDisconnectServer {
static ServerSocketChannel serverSocketChannel = null;
static Selector selector = null;
public static void main(String[] args) throws IOException {
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress(8888));
selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
int result = 0; int i = 1;
while ((result = selector.select()) > 0) {
System.out.println(String.format("selector %dth loop, ready event number is %d", i++, result));
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while(iterator.hasNext()){
SelectionKey sk = iterator.next();
if (sk.isAcceptable()) {
ServerSocketChannel ss = (ServerSocketChannel)sk.channel();
SocketChannel socketChannel = ss.accept();
socketChannel.configureBlocking(false); //也切换非阻塞
socketChannel.register(selector, SelectionKey.OP_READ); //注册read事件
System.out.println("接受到新的客户端连接");
} else if (sk.isReadable()) {
System.out.println("有数据可读");
}
iterator.remove();
}
}
}
}
客户端代码:
package NonBlocking;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
public class TestDisconnectClient {
static SocketChannel socketChannel = null;
static Selector selector = null;
public static void main(String[] args) throws IOException {
socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",8888));
socketChannel.configureBlocking(false);
selector = Selector.open();
socketChannel.register(selector, SelectionKey.OP_READ);
int result = 0; int i = 1;
while((result = selector.select()) > 0) {
System.out.println(String.format("selector %dth loop, ready event number is %d", i++, result));
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey sk = iterator.next();
if (sk.isReadable()) {
System.out.println("有数据可读");
}
iterator.remove();
}
}
}
}
- 先后运行服务端、客户端,然后没有什么效果,因为双方都没有给对方写入数据。
- 当你手动停止任意一方时,另一方都会不断收到READ事件。
手动停止服务端,客户端read抛出异常
package NonBlocking;
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.SocketChannel;
import java.util.Iterator;
public class TestDisconnectClient {
static SocketChannel socketChannel = null;
static Selector selector = null;
public static void main(String[] args) throws IOException {
socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",8888));
socketChannel.configureBlocking(false);
selector = Selector.open();
socketChannel.register(selector, SelectionKey.OP_READ);
int result = 0; int i = 1;
while((result = selector.select()) > 0) {
System.out.println(String.format("selector %dth loop, ready event number is %d", i++, result));
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey sk = iterator.next();
if (sk.isReadable()) {
System.out.println("有数据可读");
SocketChannel canReadChannel = (SocketChannel)sk.channel();
ByteBuffer buf = ByteBuffer.allocate(1024);
try {
while (canReadChannel.read(buf) > 0) {
buf.flip();
System.out.println(new String(buf.array()));
buf.clear();
}
} catch (IOException e) {
canReadChannel.close();
sk.cancel();
System.out.println("检测到远程连接断开");
e.printStackTrace();
}
}
iterator.remove();
}
}
}
}
- 当
canReadChannel.read(buf)
抛出异常时,则是连接断开了。需要关闭channel,且取消SelectionKey。
服务端调用close,客户端read返回-1
服务端加一句:
if (sk.isAcceptable()) {
ServerSocketChannel ss = (ServerSocketChannel)sk.channel();
SocketChannel socketChannel = ss.accept();
socketChannel.configureBlocking(false); //也切换非阻塞
socketChannel.register(selector, SelectionKey.OP_READ); //注册read事件
System.out.println("接受到新的客户端连接");
socketChannel.close(); //加一句。接受连接后,马上close
}
客户端修改:
package NonBlocking;
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.SocketChannel;
import java.util.Iterator;
public class TestDisconnectClient {
static SocketChannel socketChannel = null;
static Selector selector = null;
public static void main(String[] args) throws IOException {
socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",8888));
socketChannel.configureBlocking(false);
selector = Selector.open();
socketChannel.register(selector, SelectionKey.OP_READ);
int result = 0; int i = 1;
while((result = selector.select()) > 0) {
System.out.println(String.format("selector %dth loop, ready event number is %d", i++, result));
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey sk = iterator.next();
if (sk.isReadable()) {
System.out.println("有数据可读");
SocketChannel canReadChannel = (SocketChannel)sk.channel();
ByteBuffer buf = ByteBuffer.allocate(1024);
try {
int readResult;
while ((readResult = canReadChannel.read(buf)) > 0) {
buf.flip();
System.out.println(new String(buf.array()));
buf.clear();
}
if (readResult == -1) { //需要单独检测read返回-1的情况
System.out.println("readResult is -1");
canReadChannel.close();
sk.cancel();
}
} catch (IOException e) {
canReadChannel.close();
sk.cancel();
System.out.println("检测到远程连接断开");
e.printStackTrace();
}
}
iterator.remove();
}
}
}
}
- 当服务端对SocketChannel调用close后,客户端会不断有READ事件,且read不抛异常,但返回值为-1。
- 所以客户端需要加上单独检测read返回-1的逻辑。
调用shutdownOutput后,不断的READ事件
如果一方调用shutdownOutput后,那么连接会处于半关闭状态,另一方也会不断收到READ事件,但表现是canReadChannel.read(buf)
的返回值为-1.注意也是不抛出异常。
修改服务端代码:
package NonBlocking;
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;
public class TestDisconnectServer {
static ServerSocketChannel serverSocketChannel = null;
static Selector selector = null;
public static void main(String[] args) throws IOException {
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress(8888));
selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
int result = 0; int i = 1;
while ((result = selector.select()) > 0) {
System.out.println(String.format("selector %dth loop, ready event number is %d", i++, result));
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while(iterator.hasNext()){
SelectionKey sk = iterator.next();
if (sk.isAcceptable()) {
ServerSocketChannel ss = (ServerSocketChannel)sk.channel();
SocketChannel socketChannel = ss.accept();
socketChannel.configureBlocking(false); //也切换非阻塞
socketChannel.register(selector, SelectionKey.OP_READ); //注册read事件
System.out.println("接受到新的客户端连接");
socketChannel.shutdownOutput();
} else if (sk.isReadable()) {
System.out.println("有数据可读");
SocketChannel canReadChannel = (SocketChannel)sk.channel();
ByteBuffer buf = ByteBuffer.allocate(1024);
try {
int readResult;
while ((readResult = canReadChannel.read(buf)) > 0) {
buf.flip();
System.out.println(new String(buf.array()));
buf.clear();
}
if (readResult == -1) { //需要单独检测read返回-1的情况
System.out.println("readResult is -1");
canReadChannel.close();
sk.cancel();
}
} catch (IOException e) {
canReadChannel.close();
sk.cancel();
System.out.println("检测到远程连接断开");
e.printStackTrace();
}
}
iterator.remove();
}
}
}
}
修改客户端代码:
package NonBlocking;
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.SocketChannel;
import java.util.Iterator;
public class TestDisconnectClient {
static SocketChannel socketChannel = null;
static Selector selector = null;
public static void main(String[] args) throws IOException {
socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",8888));
socketChannel.configureBlocking(false);
selector = Selector.open();
socketChannel.register(selector, SelectionKey.OP_READ);
int result = 0; int i = 1;
while((result = selector.select()) > 0) {
System.out.println(String.format("selector %dth loop, ready event number is %d", i++, result));
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey sk = iterator.next();
if (sk.isReadable()) {
System.out.println("有数据可读");
SocketChannel canReadChannel = (SocketChannel)sk.channel();
ByteBuffer buf = ByteBuffer.allocate(1024);
try {
int readResult;
while ((readResult = canReadChannel.read(buf)) > 0) {
buf.flip();
System.out.println(new String(buf.array()));
buf.clear();
}
if (readResult == -1) { //需要单独检测read返回-1的情况
System.out.println("readResult is -1");
canReadChannel.close();
sk.cancel();
}
} catch (IOException e) {
canReadChannel.close();
sk.cancel();
System.out.println("检测到远程连接断开");
e.printStackTrace();
}
}
iterator.remove();
}
}
}
}
先后运行服务端、客户端。
客户端效果:
selector 1th loop, ready event number is 1
有数据可读
readResult is -1
//然后程序阻塞在下一次循环的select那里
服务端效果:
selector 1th loop, ready event number is 1
接受到新的客户端连接
selector 2th loop, ready event number is 1
有数据可读
readResult is -1
//然后程序阻塞在下一次循环的select那里
整个流程是这样的:
- 三次握手建立成功,服务端检测到ACCEPT事件。
- 服务端获得SocketChannel实例后,马上调用了shutdownOutput。
- 这使得客户端检测到READ事件,且read结果为-1。
- 然后客户端执行
canReadChannel.close()
,这又使得服务端检测到了READ事件,且read结果也为-1。