首先給出一個通用的服務端代碼:
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 TestOpenAndConnectServer {
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();
}
}
}
}
使用connect效果
使用connect的客戶端代碼:
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.Date;
import java.util.Iterator;
import java.util.Scanner;
public class TestOpenAndConnectClient {
static SocketChannel socketChannel = null;
static Selector selector = null;
public static void main(String[] args) throws IOException {
socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
boolean connected = socketChannel.connect(new InetSocketAddress("127.0.0.1",8888));
System.out.println(connected);
selector = Selector.open();
socketChannel.register(selector, SelectionKey.OP_CONNECT);
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.isConnectable()) {
SocketChannel sc = (SocketChannel)sk.channel();
if (sc.finishConnect()) {
System.out.println("與服務端建立連接成功");
} else {
System.out.println("與服務端建立連接失敗");
}
sc.register(selector, SelectionKey.OP_READ);
} else if (sk.isReadable()) {
System.out.println("有數據可讀");
}
iterator.remove();
}
}
}
}
在服務端已經運行後,再運行客戶端,效果如下:
false
selector 1th loop, ready event number is 1
與服務端建立連接成功
如果服務端沒有運行,再運行客戶端,效果如下:可見在執行sc.finishConnect()
拋出了異常。
false
selector 1th loop, ready event number is 1
Exception in thread "main" java.net.ConnectException: Connection refused: no further information
at sun.nio.ch.SocketChannelImpl.checkConnect(Native Method)
at sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:717)
at NonBlocking.TestOpenAndConnectClient.main(TestOpenAndConnectClient.java:34)
正如connect的api文檔所解釋:
If this channel is in non-blocking mode then an invocation of this method initiates a non-blocking connection operation. If the connection is established immediately, as can happen with a local connection, then this method returns true. Otherwise this method returns false and the connection operation must later be completed by invoking the finishConnect method.
一個channel在非阻塞模式下執行connect後,如果連接能馬上建立好則返回true,否則完成false。如果返回false,那麼只能通過之後調用finishConnect來判斷連接是否完成。
finishConnect的api文檔:
If the connection operation failed then invoking this method will cause an appropriate IOException to be thrown.
如果連接操作已經失敗了,那麼將會拋出異常。
If this channel is in non-blocking mode then this method will return false if the connection process is not yet complete.
如果連接操作還未完成好,那麼將返回false。
但由於我們是通過Selector來觸發事件,所以connect事件到來時,這個連接要麼已經成功,要麼已經失敗(拋出異常的形式告訴我們)。
在select循環外使用finishConnect
客戶端修改部分代碼如下:
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.Date;
import java.util.Iterator;
import java.util.Scanner;
public class TestOpenAndConnectClient {
static SocketChannel socketChannel = null;
static Selector selector = null;
public static void main(String[] args) throws IOException {
socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
boolean connected = socketChannel.connect(new InetSocketAddress("127.0.0.1",8888));
System.out.println("socketChannel.connect result is "+connected);
System.out.println("socketChannel.finishConnect result is "+socketChannel.finishConnect()); //加了這句
selector = Selector.open();
socketChannel.register(selector, SelectionKey.OP_CONNECT);
SelectionKey sek = socketChannel.keyFor(selector);
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.isConnectable()) {
SocketChannel sc = (SocketChannel)sk.channel();
if (sc.finishConnect()) {
System.out.println("與服務端建立連接成功");
} else {
System.out.println("與服務端建立連接失敗");
}
sc.register(selector, SelectionKey.OP_READ);
} else if (sk.isReadable()) {
System.out.println("有數據可讀");
}
iterator.remove();
}
}
}
}
如果服務端沒有運行,再運行客戶端,效果如下:可見還在執行sc.finishConnect()
拋出了異常。
socketChannel.connect result is false
socketChannel.finishConnect result is false
selector 1th loop, ready event number is 1
Exception in thread "main" java.net.ConnectException: Connection refused: no further information
at sun.nio.ch.SocketChannelImpl.checkConnect(Native Method)
at sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:717)
at NonBlocking.TestOpenAndConnectClient.main(TestOpenAndConnectClient.java:35)
在服務端已經運行後,再運行客戶端,效果如下:
socketChannel.connect result is false
socketChannel.finishConnect result is true
Process finished with exit code 0
可見雖然connect返回了false,但馬上finishConnect也返回了true。但是之後的循環就直接退出了,一行都沒有執行。原因應該是finishConnect在連接成功時會消耗一次OP_CONNECT事件,而註冊事件時又只註冊OP_CONNECT事件,所以select阻塞了一下後,只有返回0了,導致循環直接退出。
- 看來要使用select循環的話,就不應該在循環外使用finishConnect了。
使用open
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 TestOpenAndConnectClient {
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();
}
}
}
}
運行後沒有啥效果,因爲一直等待讀就緒,但沒有可讀數據過來。
如果之前沒有運行服務端,再運行客戶端:
Exception in thread "main" java.net.ConnectException: Connection refused: connect
at sun.nio.ch.Net.connect0(Native Method)
at sun.nio.ch.Net.connect(Net.java:454)
at sun.nio.ch.Net.connect(Net.java:446)
at sun.nio.ch.SocketChannelImpl.connect(SocketChannelImpl.java:648)
at java.nio.channels.SocketChannel.open(SocketChannel.java:189)
at NonBlocking.TestOpenAndConnectClient.main(TestOpenAndConnectClient.java:15)
This convenience method works as if by invoking the open() method, invoking the connect method upon the resulting socket channel, passing it remote, and then returning that channel.
看來此方法,就是一個方便方法,相當於在返回的channel上調用了connect,並返回給你這個channel。從上面的報錯信息也能看出。
總結
- 調用connect後除非連接能馬上建立能返回true,否則就返回false。連接是否能建立要通過後續的finishConnect判斷。
- 在select循環裏,先檢測SelectionKey是否isConnectable爲true,如果是則進入分支,再執行SocketChannel.finishConnect。若連接成功,finishConnect返回真;若連接失敗,則拋出異常。
- 調用connect後底層開始TCP的三次握手,握手在一定時間內結束,握手結果要麼成功,要麼失敗。