SocketChannel 使用open或connect/finishConnect


首先給出一個通用的服務端代碼:

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的三次握手,握手在一定時間內結束,握手結果要麼成功,要麼失敗。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章