亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

非阻塞 Java SocketChannel 介紹

1. 前言

前一小節介紹了 Java NIO Channel 體系結構,其中 java.nio.channels.SocketChannel 和 java.nio.channels.ServerSocketChannel 是編寫非阻塞 Java TCP Soccket 程序的重要模塊。SocketChanel 用在 TCP 的客戶端和服務器端,用于數據的讀寫。SocketChannel 表示一個 Endpoint,并且和遠端的 SocketChannel 建立了連接。ServerSocketChannel 用于創建 TCP 服務器,用于監聽新的 TCP 連接請求。本小節對 SocketChannel 和 ServerSocketChannel 的基本功能做一個初步的分析。

2. SocketChannel 介紹

SocketChannel 的基本功能就是用于 TCP 連接的建立,數據讀寫,TCP 連接關閉。

2.1 SocketChannel 實例創建

SocketChannel 并沒有提供 public 構造方法,創建 SocketChannel 的實例需要通過工廠方法 open 實現。open 聲明如下:

public static SocketChannel open() throws IOException
public static SocketChannel open(SocketAddress remote) throws IOException

SocketChannel 的不帶參數的 open 方法只是創建實例,不會 connect 服務器,帶參的 open 方法會 connect 遠端服務器。如果是創建阻塞式 SocketChannel,可以通過 open 方法傳入遠端服務器的地址;如果是創建非阻塞式 SocketChannel 需要調用不帶參數的 open 方法,然后再調用 connect 方法連接遠端服務器。

2.2 SocketChannel 的 connect 方法

SocketChannel 提供了專門 connect 方法,可以進行阻塞式、非阻塞式連接。聲明如下:

public abstract boolean connect(SocketAddress remote) throws IOException;
public abstract boolean finishConnect() throws IOException

如果 SocketChannel 是阻塞模式,調用它的 connect 方法連接遠端服務器,connect 方法會執行阻塞式調用,直到連接成功或者失敗返回。
如果 SocketChannel 是非阻塞模式,調用它的 connect 方法連接遠端服務器,connect 方法會執行非阻塞式調用。connect 發出連接請求后,不管 TCP 連接是否成功,都會馬上返回。必須調用 finishConnect 方法,檢查連接是否成功,如果連接成功 finishConnect,返回 true;如果連接沒有成功,finishConnect 返回 false。

2.3 SocketChannel 的數據讀取

SocketChannel 提供了讀取單片數據的方法,聲明如下:

public abstract int read(ByteBuffer dst) throws IOException

其實,單片數據的 read 方法是重寫了 java.nio.channels.ReadableByteChannel 中的 read 方法。 read 方法是從 I/O 設備讀取數據,保存在 ByteBuffer 中,為此調用者必須提供 ByteBuffer 用以保存數據。返回值是讀取的字節數、0、或者 -1。如果是阻塞式 Channel,read 至少返回 1 或者 -1;如果是非阻塞式 Chanel,read 可能會返回 0。

SocketChannel 提供了讀取多片數據的方法,聲明如下:

public final long read(ByteBuffer[] dsts) throws IOException
public final long read(ByteBuffer[] dsts, int offset, int length) throws IOException

其實,多片數據的 read 方法是重寫了 java.nio.channels.ScatteringByteChannel 中的 read 方法。多片數據 read 方法的返回值和單片數據 read 方法的返回值具有相同的含義。多片數據的 read 方法,其實是將 TCP 字節流保存在不同的 ByteBuffer 中,這些 ByteBuffer 是不同的內存塊,通常叫做 Scatter 機制。

2.4 SocketChannel 的數據寫入

SocketChannel 提供了寫入單片數據的方法,聲明如下:

public abstract int write(ByteBuffer src) throws IOException

其實,單片數據的 write 方法是重寫了 java.nio.channels.WritableByteChannel 中的 write 方法。write 方法是從 ByteBuffer 讀取數據,寫入 I/O 設備中,為此調用者必須將要寫出去的數據保存到 ByteBuffer 中。返回值是寫入的字節數、0、或者 -1。如果是阻塞式 Channel,write 返回請求寫入的字節數 或者 -1;如果是非阻塞式 write 可能會返回 0。

SocketChannel 提供了寫入多片數據的方法,聲明如下:

public final long write(ByteBuffer[] dsts) throws IOException
public final long write(ByteBuffer[] dsts, int offset, int length) throws IOException

多片數據的 write 方法是重寫了 java.nio.channels.GatheringByteChannel 中的 write 方法。多片數據 write 方法的返回值和單片數據 write 方法的返回值具有相同的含義。多片數據的 write 方法,其實是將保存在不同的 ByteBuffer 中字節流寫入 TCP Socket,這些 ByteBuffer 是不同的內存塊,通常叫做 Gathering 機制。

2.5 SocketChannel 的關閉

SocketChannel 覆寫了 java.nio.channels.Channel 中的 close 方法,完成 TCP 連接的關閉。方法聲明如下:

public void close() throws IOException

3. ServerSocketChannel 介紹

ServerSocketChannel 用于 TCP 創建服務器,監聽客戶端的連接請求。ServerSocketChannel 也沒有提供 public 的構造方法,創建 ServerSocketChannel 的實例,需要調用它的工廠方法 open。聲明如下:

public static ServerSocketChannel open() throws IOException

ServerSocketChannel 提供了 bind 方法,用于綁定監聽的 IP 地址和端口。聲明如下:

public final ServerSocketChannel bind(SocketAddress local) throws IOException
public abstract ServerSocketChannel bind(SocketAddress local, int backlog) throws IOException;

ServerSocketChannel 提供了 accept 方法,接收新的客戶端請求,返回值是 SocketChannel 類型的對象,表示一個新的 TCP 連接。

public abstract SocketChannel accept() throws IOException;

通過 ServerSocketChannel 創建一個 TCP 服務器,一般需要如下幾個步驟:

try {
    Selector selector = Selector.open();
    ServerSocketChannel serverChannel = ServerSocketChannel.open();
    // 綁定監聽的 socket 地址,監聽 any_addr
    serverChannel.socket().bind(new InetSocketAddress(PORT));
    // 設置 SO_REUSEADDR 選項,作為服務器,這是基本的要求
    serverChannel.socket().setReuseAddress(true);
    // 設置非阻塞模式,作為服務器,也是基本要求
    serverChannel.configureBlocking(false);
    // 注冊 accept 事件
    serverChannel.register(selector, SelectionKey.OP_ACCEPT, serverChannel);
} catch (IOException e) {
    e.printStackTrace();
}

后續的邏輯就是通過 Selector 的 select 方法監聽 I/O 事件。然后通過 SocketChannel 的 read 和 write 方法進行數據讀寫。

4. 小結

本小節重點是介紹了 SocketChannel 和 ServerSocketChannel 兩個類提供的重點 API。關于這兩個類的具體應用示例,已經在前面的小節展示,可以參考。