03.13 Scalable TCP一個與眾不同的網絡編程

前幾天我給大家分享了一些JAVA網絡編程的知識,從基本的JAVA網絡知識開始,我們學習了結合多線程管理的Socket編程,掌握了關於系統內核中的RecBuffer和SendBuffer的概念模型。然後我們開始了NIO編程模型的最佳搭檔,Channel和Buffer,以及Channel的讀寫操作和Buffer的get、put的關係。因為我們有了前面知識的鋪墊,所以今天我打算給大家分享一個不一樣的網絡編程模型,使用Selector實現多路複用網絡編程。(實際上是JAVA NIO系列第五篇文章)

今天分享目錄如下:

  • 1. TCP上的Channel有哪些

  • 2. TCP上Channel的讀寫操作

  • 3. 使用Selector實現TCP多路複用網絡編程

(還是老話,如果對Selector的op_read、op_write很熟悉,就不要往下看了,去看對你更有幫助的文章)

1. TCP上的Channel

Scalable TCP一個與眾不同的網絡編程

先看下TCP的Channel繼承圖,注意SelectableChannel

1.1 Server Socket上的Channel:ServerSocketChannel

class ServerSocketChannel{

static ServerSocketChannel open();

ServerSocket socket();

SocketChannel accept();

}

從上面的API中我們可以看出,ServerSocketChannel是使用open這個靜態方法中獲得的。並且我們可以中該Channel中獲得ServerSocket對象。

ServerSocketChannel channel = ServerSocketChannel.open();

ServerSocket serverSocket = channel.socket();

這裡需要注意:

  • 上面代碼ServerSocket沒有綁定地址,需要綁定之後才可以使用,例如使用serverSocket.bind(port)

  • 另外我們要使用ServerSocketChannel的accept來獲得SocketChannel,而不是使用ServerSocket的accept獲得Socket。

  • 如果serverSocketChannel是non-blocking的,那麼accept可能返回null,因為沒有連接進來。

  • 使用accept獲得的channel不是null,那麼該channel默認是Blocking模式,需要手動指定成為Non-Blocking

SelectableChannel configureBlocking(boolean block);

1.2 客戶端連接Socket的Channel:SocketChannel

class SocketChannel{

static SocketChannel open();

static SocketChannel open(SocketAddress address);

Socket socket();

}

從上面的API看到,大概和ServerSocketChannel類似,從Channel中獲得的socket仍然沒有連接到服務,需要使用帶有參數的open方法才能打開連接。同樣這裡獲得的channel默認是Blocking模式,需要手動指定成為Non-Blocking。

例如:我們指定ip和端口

SocketChannel channel = SocketChannel.open(new InetSocketAddress(ip,port));

2. Channel的操作

注意channel的Blocking模式,可以使用API設置

SelectableChannel configureBlocking(boolean block);

Channel的Read操作


在阻塞式編程中,TCP Channel的讀操作會阻塞,一直等到有數據可用。而在非阻塞編程中,讀操作不會被阻塞,如果recBuffer中有數據,則read返回數據,如果recBuffer空,就不會有數據返回。這裡需要注意的地方是非阻塞編程讀過程中,如果對方關閉了連接,或者本地關閉了read操作,會有異常拋出,否則read會立刻返回所讀數據數,儘管數據可能是0。

代碼示例稍後。

Channel的Write操作

在阻塞編程中,和讀一樣,寫操作一直等待sendBuffer中有數據。在非阻塞編程中,寫不會被阻塞。這裡和讀是一樣的,如果沒有異常拋出的話,這裡的寫操作會立即返回寫的數據數,這裡可以是0。什麼時候拋出異常?連接被對端關閉了,或者write操作被關閉都會拋異常的。

代碼示例稍後。

3. 多路複用網絡編程模型

Scalable TCP一個與眾不同的網絡編程

終於到了今天重點的地方,希望讀者慢慢看,吸收裡面的知識點。

除了上面的讀、寫操作之外,在多路複用編程中還有兩個準備就緒類的操作,一個是準備好了Accept,另一個是準備連接已就緒。完整的操作是:

  • OP_ACCEPT:只有ServerSocketChannel才可以有這個操作,如果有連接進來,這裡就會有返回。

  • OP_CONNECT:SocketChannel在連接過程中的操作,已經出於連接完成的狀態,下一步是finishConnect。

  • OP_READ:只有連接完成的SocketChannel才有讀操作。返回數據見上面,除非有異常,否則就會有數據個數返回,即使數據返回0.

  • OP_WRITE:只有連接完成的SocketChannel才有寫操作。除非有異常,否則返回寫出的數據數,或0.

稍後在深入解釋這4個操作。我們先看代碼,混個面熟先。

如果下面代碼你會了,那麼JAVA的網絡編程你就掌握了。

Scalable TCP一個與眾不同的網絡編程

片段01

Scalable TCP一個與眾不同的網絡編程

片段02

代碼亮點,關鍵點:

  • 非阻塞配置。(nonblocking)

  • Selector.select方法的使用。(timeout)

  • 使用key的attachement管理每個連接的ByteBuffer。(attach)

好了,我們回到上面的4個操作上,op_accept,connect,read,wirte。實際上,上面代碼中select()操作,返回的只有兩種事件,可讀的事件和可寫的事件兩種。

可讀事件:有數據在recBuffer中或者accept方法有可用連接進來了,換句話說,OP_READ和OP_ACCETP是一個事件。

可寫事件:sendBuffer中有數據了,或者客戶端連接完成了。換句話說,OP_CONNECT 和OP_WRITE是一回事。

我們定義accept和read事件在一起為可讀事件沒有什麼問題,因accept在server端可以時,read還不可用,所有監聽是分開的,不會衝突。但是connect事件和write事件為可寫事件不能同時監聽,因為在client socket中,這倆可以是同時存在的,會出現歧義。因為在一個準備就緒的Socket中,op_write事件幾乎是一直可用的,所有除非有數據明確了,需要寫出了,我們才可以在socket中把write事件添加到select流程中,否則NIO調度線程總是選擇報告可寫事件,CPU壓力很大,因此我們才規定,除非有數據可以寫,我們不要訂閱可寫事件。

總結:

今天只是網絡編程基礎知識的分享,所以並沒有結合實際業務需求,明天我打算結合實際業務需求,實現一個斷點續傳的Socket編程模式。

感謝你的閱讀,希望本文的分享能給你帶來提升,有問題在下方評論中討論,我會及時反饋。


分享到:


相關文章: