首先给出一个通用的服务端代码:
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的三次握手,握手在一定时间内结束,握手结果要么成功,要么失败。