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。