日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

JAVA NIO编程入门(二)

發布時間:2024/9/21 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JAVA NIO编程入门(二) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、回顧

上一篇文章 JAVA NIO編程入門(一)我們學習了NIO編程的基礎知識,并通過一個小demo實戰幫助了解NIO編程的channel,buffer等概念。本文會繼續學習JAVA NIO編程,并通過一個小示例來幫助理解相關知識,通過本文你將可以學習到

  • buffer的聚集和分散(Scatter/Gather)
  • SocketChannel和ServerSocketChannel的使用
  • 選擇器的使用

二、什么是聚集和分散(Scatter/Gather)

  • 分散(scatter)從Channel中讀取是指在讀操作時將讀取的數據寫入多個buffer中。因此,Channel將從Channel中讀取的數據“分散(scatter)”到多個Buffer中。
  • 聚集(gather)寫入Channel是指在寫操作時將多個buffer的數據寫入同一個Channel,因此,Channel 將多個Buffer中的數據“聚集(gather)”后發送到Channel。

分散(Scatter)示意圖

從通道填充buffer,必須填充完前一個buffer才會填充后面的buffer,這也意味著不能動態調整每個buffer的接受大小。

聚集(Gather)示意圖

聚集和分散是相反的形式,從buffer寫入數據到通道,只會寫入buffer的positon位置到limit位置的內容,也就是意味著可以動態的寫入內容到通道中。

三、選擇器

什么是選擇器

Selector(選擇器)是Java NIO中能夠檢測多個NIO通道,并能夠知道通道是否為諸如讀寫事件做好準備的組件。這樣,一個單獨的線程可以管理多個channel,從而管理多個網絡連接,提高效率。

為什么要用選擇器

使用了選擇器就可以用一個線程管理多個channel,如果多個channel由多個線程管理,線程之前的切換是消耗資源的,而單個線程就避免了線程之間切換的消耗。

選擇器常用方法

方法名功能
register(Selector sel, int ops)向選擇器注冊通道,并且可以選擇注冊指定的事件,目前事件分為4種;1.Connect,2.Accept,3.Read,4.Write,一個通道可以注冊多個事件
select()阻塞到至少有一個通道在你注冊的事件上就緒了
selectNow()不會阻塞,不管什么通道就緒都立刻返回
select(long timeout)和select()一樣,除了最長會阻塞timeout毫秒(參數)
selectedKeys()一旦調用了select()方法,并且返回值表明有一個或更多個通道就緒了,然后可以通過調用selector的selectedKeys()方法,訪問“已選擇鍵集(selected key set)”中的就緒通道
wakeUp()可以使調用select()阻塞的對象返回,不阻塞。
close()用完Selector后調用其close()方法會關閉該Selector,且使注冊到該Selector上的所有SelectionKey實例無效。通道本身并不會關閉

四、實戰

實戰需求說明

編碼客戶端和服務端,服務端可以接受客戶端的請求,并返回一個報文,客戶端接受報文并解析輸出。

服務端代碼

try {//創建一個服socket并打開ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();//監聽綁定8090端口serverSocketChannel.socket().bind(new InetSocketAddress(8090));//設置為非阻塞模式serverSocketChannel.configureBlocking(false);while(true){//獲取請求連接SocketChannel socketChannel = serverSocketChannel.accept();if (socketChannel!=null){ByteBuffer buf1 = ByteBuffer.allocate(1024);socketChannel.read(buf1);buf1.flip();if(buf1.hasRemaining())System.out.println(">>>服務端收到數據:"+new String(buf1.array()));buf1.clear();//構造返回的報文,分為頭部和主體,實際情況可以構造復雜的報文協議,這里只演示,不做特殊設計。ByteBuffer header = ByteBuffer.allocate(6);header.put("[head]".getBytes());ByteBuffer body = ByteBuffer.allocate(1024);body.put("i am body!".getBytes());header.flip();body.flip();ByteBuffer[] bufferArray = { header, body };socketChannel.write(bufferArray);socketChannel.close();}else{Thread.sleep(1000);}}} catch (IOException e) {e.printStackTrace();} catch (InterruptedException e) {e.printStackTrace();} 復制代碼

服務端selector(選擇器版本)

try {//打開選擇器Selector selector = Selector.open();ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.socket().bind(new InetSocketAddress(8090));serverSocketChannel.configureBlocking(false);//向通道注冊選擇器,并且注冊接受事件serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);while (true) {//獲取已經準備好的通道數量int readyChannels = selector.selectNow();//如果沒準備好,重試if (readyChannels == 0) continue;//獲取準備好的通道中的事件集合Set selectedKeys = selector.selectedKeys();Iterator keyIterator = selectedKeys.iterator();while (keyIterator.hasNext()) {SelectionKey key = (SelectionKey) keyIterator.next();if (key.isAcceptable()) {//在自己注冊的事件中寫業務邏輯,//我這里注冊的是accept事件,//這部分邏輯和上面非選擇器服務端代碼一樣。ServerSocketChannel serverSocketChannel1 = (ServerSocketChannel) key.channel();SocketChannel socketChannel = serverSocketChannel1.accept();ByteBuffer buf1 = ByteBuffer.allocate(1024);socketChannel.read(buf1);buf1.flip();if (buf1.hasRemaining())System.out.println(">>>服務端收到數據:" + new String(buf1.array()));buf1.clear();ByteBuffer header = ByteBuffer.allocate(6);header.put("[head]".getBytes());ByteBuffer body = ByteBuffer.allocate(1024);body.put("i am body!".getBytes());header.flip();body.flip();ByteBuffer[] bufferArray = {header, body};socketChannel.write(bufferArray);socketChannel.close();} else if (key.isConnectable()) {} else if (key.isReadable()) {} else if (key.isWritable()) {}//注意每次迭代末尾的keyIterator.remove()調用。//Selector不會自己從已選擇鍵集中移除SelectionKey實例。必須在處理完通道時自己移除。//下次該通道變成就緒時,Selector會再次將其放入已選擇鍵集中keyIterator.remove();}}} catch (IOException e) {e.printStackTrace();} 復制代碼

客戶端代碼

try {//打開socket連接,連接本地8090端口,也就是服務端SocketChannel socketChannel = SocketChannel.open();socketChannel.connect(new InetSocketAddress("127.0.0.1", 8090));//請求服務端,發送請求ByteBuffer buf1 = ByteBuffer.allocate(1024);buf1.put("來著客戶端的請求".getBytes());buf1.flip();if (buf1.hasRemaining())socketChannel.write(buf1);buf1.clear();//接受服務端的返回,構造接受緩沖區,我們定義頭6個字節為頭部,后續其他字節為主體內容。ByteBuffer header = ByteBuffer.allocate(6);ByteBuffer body = ByteBuffer.allocate(1024);ByteBuffer[] bufferArray = { header, body };socketChannel.read(bufferArray);header.flip();body.flip();if (header.hasRemaining())System.out.println(">>>客戶端接收頭部數據:" + new String(header.array()));if (body.hasRemaining())System.out.println(">>>客戶端接收body數據:" + new String(body.array()));header.clear();body.clear();socketChannel.close();} catch (IOException e) {e.printStackTrace();} 復制代碼

運行結果

服務端:

客戶端:

這里給出了服務端代碼的兩個版本,一個是非選擇器的版本,一個是選擇器的版本。查看最后運行結果,發現客戶端根據雙方約定的協議格式,正確解析到了頭部和body的內容,其實這也是聚集和分散最主要的作用和應用場景,在網絡交互中,進行協議報文格式的定義和實現。后續學完NIO編程入門后我們最后進行總結性的實戰,編寫一個RPC的demo框架,實現分布式系統的遠程調用,有興趣的同學可以關注筆者和后續的文章。

參考

《JAVA NIO》

推薦閱讀

《Java鎖之ReentrantLock(一)》

《Java鎖之ReentrantLock(二)》

《Java鎖之ReentrantReadWriteLock》

《JAVA NIO編程入門(一)》

總結

以上是生活随笔為你收集整理的JAVA NIO编程入门(二)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。