1. 程式人生 > >java nio Selector的使用-客戶端

java nio Selector的使用-客戶端

  接上一篇,客戶端的程式就相對於簡單了,只需要負責連線,傳送下載檔名,再讀資料就行了。主要步驟就是註冊->連線伺服器->傳送下載請求->讀資料->斷開連線。

  第一步:註冊,並註冊connect事件。

Java程式碼 複製程式碼
  1. if(selector == null)   
  2.     selector = Selector.open();   
  3. SocketChannel channel = SocketChannel.open();   
  4. channel.configureBlocking(false);   
  5. channel.connect(new InetSocketAddress(
    "localhost"1234));   
  6. channel.register(selector, SelectionKey.OP_CONNECT);  
if(selector == null)
	selector = Selector.open();
SocketChannel channel = SocketChannel.open();
channel.configureBlocking(false);
channel.connect(new InetSocketAddress("localhost", 1234));
channel.register(selector, SelectionKey.OP_CONNECT);

  以上步驟,在實際中發現configure和connet並不能隨意交換位置,即不能將connect放在configureBlocking的前面,更不能在open中直接新增InetSocketAddress引數了。在官方doc中,open(InetAddress)的解釋是“這種便捷方法的工作方式就像以下過程一樣:呼叫 open() 方法、在得到的套接字通道上呼叫 connect 方法、向其傳遞 remote,然後返回該通道。”,但不知道為什麼在connect之後,就不能配置block了,導致無法進行資料下載和通訊。

  第二步:處理connect事件

Java程式碼 複製程式碼
  1. //連線事件
  2. if
    (key.isConnectable()) {   
  3.     SocketChannel socketChannel = (SocketChannel) key.channel();   
  4. if(socketChannel.isConnectionPending())   
  5.         socketChannel.finishConnect();   
  6.     socketChannel.write(ByteBuffer.wrap(serverFileName.getBytes()));//向伺服器發信息,資訊中即伺服器上的檔名
  7.     socketChannel.register(selector, SelectionKey.OP_READ);   
  8. }  
//連線事件
if(key.isConnectable()) {
	SocketChannel socketChannel = (SocketChannel) key.channel();
	if(socketChannel.isConnectionPending())
		socketChannel.finishConnect();
	socketChannel.write(ByteBuffer.wrap(serverFileName.getBytes()));//向伺服器發信息,資訊中即伺服器上的檔名
	socketChannel.register(selector, SelectionKey.OP_READ);
}

  在以上步驟中,要完成連線之後,向伺服器端傳送了下載的檔名的資料資訊,並註冊read事件。伺服器端在接收到相應檔名之後,就開啟write事件向客戶端進行傳送資料了,客戶端此時就可以進行資料讀取了。

  第三步:處理read事件

Java程式碼 複製程式碼
  1. if(key.isReadable()) {   
  2.     SocketChannel socketChannel = (SocketChannel) key.channel();   
  3.     byteBuffer.clear();   
  4. if(!socketChannel.isConnected())   
  5. returnnull;   
  6. //向本機下載檔案建立檔案channel
  7. if(fileChannel == null)   
  8.         fileChannel = new RandomAccessFile(localFileName, "rw").getChannel();   
  9. int r = socketChannel.read(byteBuffer);   
  10. //如果檔案下載完畢,則關掉channel,同時關掉socketChannel
  11. if(r <= 0) {   
  12. if(fileChannel != null)   
  13.             fileChannel.close();   
  14.         channel.close();   
  15.         key.cancel();   
  16. returnnull;   
  17.     }   
  18.     byteBuffer.flip();   
  19. //寫到下載檔案中
  20.     fileChannel.write(byteBuffer);   
  21. }  
if(key.isReadable()) {
	SocketChannel socketChannel = (SocketChannel) key.channel();
	byteBuffer.clear();
	if(!socketChannel.isConnected())
		return null;
//向本機下載檔案建立檔案channel
	if(fileChannel == null)
		fileChannel = new RandomAccessFile(localFileName, "rw").getChannel();
	int r = socketChannel.read(byteBuffer);
//如果檔案下載完畢,則關掉channel,同時關掉socketChannel
	if(r <= 0) {
		if(fileChannel != null)
			fileChannel.close();
		channel.close();
		key.cancel();
		return null;
	}
	byteBuffer.flip();
//寫到下載檔案中
	fileChannel.write(byteBuffer);
}

  就是處理讀資訊,如果資料已經讀取完畢,則完成相應下載儲存檔案的檔案流,並退出程式。

  這樣,整個客戶端就完成了,在執行時,我同時啟用10個執行緒來向伺服器端讀同一個檔案,並儲存為不同的檔案備份,以達到模擬資料傳輸的功能。如下所示:

Java程式碼 複製程式碼
  1. ExecutorService executorService = Executors.newSingleThreadExecutor();   
  2. for(int i = 0; i < 10; i++) {   
  3.     executorService.submit(new DownloadClient<Object>("d:/log4j.log""d:/down" + i + ".log"));   
  4. }   
  5. executorService.shutdown();  
ExecutorService executorService = Executors.newSingleThreadExecutor();
for(int i = 0; i < 10; i++) {
	executorService.submit(new DownloadClient<Object>("d:/log4j.log", "d:/down" + i + ".log"));
}
executorService.shutdown();

  整個selector僅是一個作為練習用的小例子,如果用在實際程式碼中,還需要處理不同的異常和相應的邏輯等。對於學習還是有一定的幫助的。希望對你有用。

  隨附客戶端程式碼。