异步连接需要做以下操作:

  • 设置socket为非阻塞模式
  • 注册OP_CONNECT事件
  • OP_CONNECT事件发生时,调用SocketChannel的finishConnection

代码如下:

public class Client1 {

	Selector selector;
	
	int writeEventTrigerCount = 0;
	public void run() throws IOException {
		selector = Selector.open();
		
		SocketChannel sc = SocketChannel.open();
		sc.configureBlocking(false);
		boolean connectImmediately = sc.connect(new InetSocketAddress("182.61.200.6", 80));
		if (!connectImmediately) {
			System.out.println("connect inprogress");
			sc.register(selector, SelectionKey.OP_CONNECT);
		} else {
			System.out.println("connect immediately");
			sc.register(selector, SelectionKey.OP_WRITE);
		}
		
		while (true) {
			selector.select();
			Set<SelectionKey> selectionKeys = selector.selectedKeys();
			for (SelectionKey selectionKey : selectionKeys){
				if (!selectionKey.isValid()) {
					System.out.println(""+selectionKey+" is not valid");
				} else if (selectionKey.isConnectable()) {
					System.out.println(""+selectionKey+" connected");
					SocketChannel client = (SocketChannel)selectionKey.channel();
					client.finishConnect();

				        selectionKey.interestOps(SelectionKey.OP_WRITE);
				} else if (selectionKey.isWritable()) {
					System.out.println(""+selectionKey+" writable");
					if (++writeEventTrigerCount > 10) {
						selectionKey.interestOps(SelectionKey.OP_READ);
					}
				} else if (selectionKey.isReadable()) {
					//System.out.println(""+selectionKey+" readable");
				}
			}
		}
	}
	
	public static void main(String args[]) {
		Client1 client = new Client1();
		try {
			client.run();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

连接失败会发生什么?

我们修改下上面的代码,将

sc.connect(new InetSocketAddress("182.61.200.6", 80));

改成

sc.connect(new InetSocketAddress("192.168.2.1", 80));

注意要确保主机"192.168.2.1"是不可达的。
运行代码,等待几秒钟,会出现下面内容:

sun.nio.ch.SelectionKeyImpl@1d81eb93 connected
java.net.ConnectException: Connection timed out: no further information
	at sun.nio.ch.SocketChannelImpl.checkConnect(Native Method)
	at sun.nio.ch.SocketChannelImpl.finishConnect(Unknown Source)
	at com.github.ralgond.nioexample.ClientConnectToUnreachableHost.run(ClientConnectToUnreachableHost.java:31)
	at com.github.ralgond.nioexample.ClientConnectToUnreachableHost.main(ClientConnectToUnreachableHost.java:50)

我们看到结果的第一行以及第四行,可以得到一个结论:不管连接成功还是失败,selector都会报isConnectable事件,然而至于成功还是失败,需要调用SocketChannel的finishConnect来判断。