JAVA NIO Socket通道

概述

通道提供I/O服務的直接連接,用於緩衝區與文件或者Socket之間傳輸數據。JAVA中只定義了一個接口來完成對通道的抽象,在這個接口中只定義了關閉與是否打開兩個方法。在此接口的基礎上又分別抽象了可讀通道、可寫通道、可中斷通道、字節通道等,其類結構圖如下:
ReadableByteChannel:可讀取字節的通道
WritableByteChannel:可寫入字節的通道
InterruptibleChannel:可被異步關閉和中斷的通道
GatheringByteChannel:可從緩衝區序列寫入字節的通道 
ScatteringByteChannel:可將字節讀入緩衝區序列的通道
SelectableChannel:可通過 Selector 實現多路複用的通道

Socket通道(用於監聽的通道ServerSocketChannel、連接套接字的通道SocketChannel和麪向數據報套接字的通道DatagramChannel)都是在以上通道的基礎上擴展的,這些通道類都可以運行非阻塞模式並且是可選擇的。

通道是一個連接I/O服務並提供與該服務交互的方法。就某個socket而言,它不會再次實現與之對應的socket通道類中的socket協議API,通道類在實例化時都會創建一個對等socket對象,對等socket可以通過調用socket()方法從一個通道上獲取。雖然每個socket通道都有一個關聯的socket對象,卻並非所有的socket都有一個關聯的通道。如果用傳統方式(直接實例化)創建了一個Socket對象,不會有關聯的SocketChannel並且它的getChannel( )方法將總是返回null。

傳統的socket總是阻塞方式進行工作,嚴重影響應用的擴展性,這些socket通道類有一個公共的父類SelectableChannel,通過調用父類的configureBlocking和isBlocking方法來設置是否阻塞和檢測是否阻塞。


ServerSocketChannel

監聽套接字通道,與ServerSocket是一個對等體,ServerSocket中的API在這裏也適用,這裏主要看看它的accept方法,accept方法主要做了幾下幾件事:
1、確保此通道是打開的且已被綁定,否則會拋出異常;
2、調用本地方法建立一個socket連接,socket有對應的文件描述符和InetSocketAddress
3、進行安全檢查
4、設置爲阻塞模式,構建一個SocketChannel,然後返回
因此,不管此通道的阻塞模式如何,此方法返回的套接字通道(如果有)將處於阻塞模式。 
如果此通道處於非阻塞模式,那麼在不存在掛起的連接時,此方法將直接返回 null,這樣可以提高程序的可伸縮性並降低複雜度,檢查是否連接的代碼如下:

ServerSocketChannel ssc = ServerSocketChannel.open( );
ssc.socket().bind (new InetSocketAddress (port)); 
//非阻塞
ssc.configureBlocking (false); 

while (true) { 
	SocketChannel sc = ssc.accept( ); 
		
	// 在非阻塞模式下,當沒有連接在等待時會返回null
	if (sc == null) { 
		Thread.sleep(TIME);
	else { 
		doSomeThing();
		sc.close();
	}
}


SocketChannel

面向連接的套接字通道,其對等體是socket。通過在通道上直接調用connect( )方法或在通道關聯的Socket對象上調用connect( )來將該socket通道連接:
(1)如果選擇使用傳統方式進行連接,線程在連接建立好或超時過期之前都將保持阻塞;
(2)如果選擇在通道上直接調用connect( )方法來建立連接並且通道處於阻塞模式(默認模式),那麼連接過程和(1)是一樣的;
(3)如果在非阻塞模式下調用通道的connect()方法進行連接會立即返回,如果返回值是true,說明連接立即建立了,如果連接不能立即建立,connect( )方法會返回false且併發地繼續連接建立過程。
後續可以通過調用finishConnect( )方法來完成連接過程,該方法任何時候都可以安全地進行調用。

假如在一個非阻塞模式的SocketChannel對象上調用finishConnect( )方法,將可能出現下列情形之一:
1、connect( )方法尚未被調用,那麼將產生NoConnectionPendingException異常;
2、連接建立過程正在進行尚未完成,那麼什麼都不會發生,finishConnect( )方法會立即返回false值;
3、在非阻塞模式下調用connect( )方法之後,SocketChannel又被切換回了阻塞模式,調用線程會阻塞直到連接建立完成,finishConnect( )方法接着就會返回true; 
4、調用後剛好連接過程已經完成,那麼SocketChannel對象的內部狀態將被更新到已連接狀態,finishConnect( )方法會返回true;
5、連接已經建立,再次調用什麼都不會發生,finishConnect( )方法會返回true。

這裏提下狀態更新,在非阻塞下調用connect方法時,狀態爲ST_PENDING,在此狀態下isConnected()方法返回false,由於在進行讀寫操作前要確保通道是連接的,所以這個狀態下的通道還不能進行讀寫操作,狀態由ST_PENDING變爲ST_CONNECTED正是在finishConnect方法中完成的。

Socket通道是線程安全的。併發訪問時無需採用特別措施來保護髮起訪問的多個線程。connect( )和finishConnect( )方法是互相同步的(都會鎖住readLock和writeLock),並且只要其中一個操作正在進行,任何讀或寫的方法調用都會被阻塞,即使是在非阻塞模式下。如果不能忍受一個讀或寫操作在某個通道上阻塞,最好先調用isConnected( )方法檢查連接狀態。


DatagramChannel

面向數據報套接字通道,與面向流的的socket不同,DatagramChannel既可以發送單獨的數據報給不同的目的地址,也可以接收來自任意地址的數據包。DatagramChannel中有兩組讀寫方法read/write和send/receive,前者只能在建立連接後才能使用。
數據報socket的無狀態性質不需要同遠程系統進行對話來建立連接狀態,由於此原因,DatagramChannel上也就沒有單獨的finishConnect( )方法。我們可以使用isConnected( )方法來測試一個數據報通道的連接狀態。不同於SocketChannel(必須連接了纔有用並且只能連接一次),DatagramChannel對象可以任意次數地進行連接或斷開連接。每次連接都可以到一個不同的遠程地址。調用disconnect( )方法可以配置通道,以便它能再次接收來自安全管理器(如果已安裝)所允許的任意遠程地址的數據或發送數據到這些地址上。

當一個DatagramChannel處於已連接狀態時,發送數據將不用提供目的地址而且接收時的源地址也是已知的。這意味着DatagramChannel已連接時可以使用常規的read( )和write( )方法。

以下是send()方法的部分源碼,該方法會驗證安全管理器的 checkConnect 方法是否允許使用該數據報的目標地址和端口號,避免此項安全檢查開銷的方法是先通過connect 方法連接該套接字。 
 synchronized (stateLock) {
      if (!isConnected()) {
          if (target == null)
              throw new NullPointerException();
          SecurityManager sm = System.getSecurityManager();
          if (sm != null) {
              if (ia.isMulticastAddress()) {
                  sm.checkMulticast(isa.getAddress());
              } else {
                  sm.checkConnect(isa.getAddress().getHostAddress(),
                                  isa.getPort());
              }
          }
      } else { // Connected case; Check address then write
          if (!target.equals(remoteAddress)) {
              throw new IllegalArgumentException(
                  "Connected address not equal to target address");
          }
          return write(src);
      }
  }
還可以看出對於已連接的send方法就是調用的write方法來實現的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章