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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

你对Java网络编程了解的如何?Java NIO 网络编程 | Netty前期知识(二)

發布時間:2025/3/19 java 54 豆豆
生活随笔 收集整理的這篇文章主要介紹了 你对Java网络编程了解的如何?Java NIO 网络编程 | Netty前期知识(二) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本文主要講解NIO的簡介、NIO和傳統阻塞I/O有什么區別、NIO模型和傳統I/O模型之間的對比、以及圍繞NIO的三大組件來講解,理論代碼相結合。
很喜歡一句話:"沉下去,再浮上來"。
我想我們會變的不一樣。

一、Java NIO 簡介

在 Java 1.4 中引入了 NIO 框架(java.nio 包),提供了 Channel、Selector、Buffer 等新的抽象,可以構建多路復用的、同步非阻塞 IO 程序,同時提供了更接近操作系統底層的高性能數據操作方式

同步非阻塞

Java NIO 的非阻塞模式:

  • 非阻塞讀:一個線程從某一個通道發送請求或者讀取數據時,它僅能得到目前可用的數據,如果目前沒有數據可用時,就什么都不會獲取,但是并不會像原生IO一樣,阻塞等待著,反而是直到數據變得可以讀取之前,此線程可以去繼續做其他事情。
  • 非阻塞寫:和非阻塞讀一樣,一個線程發送請求寫入一些數據到某通道,在等待它完成寫入這段時間中,無需阻塞等待,這個線程可以同時去做其他的事情。
  • 總結起來就是NIO 是可以做到用一個線程來處理多個操作的。
  • 通俗理解:NIO 是可以做到用一個線程來處理多個操作的。假設有 10000 個請求過來,根據實際情況,可以分配 50 或者 100 個線程來處理。不像之前的阻塞 IO 那樣,非得分配 10000 個。

Java NIO 由以下幾個核心部分組成:

  • Channels (通道)
  • Buffers (緩沖區)
  • Selector (選擇器)

雖然Java NIO除此之外,仍有很多類和組件,但是總的來說仍然是靠Channel,Buffer 和 Selector 構成了核心的API。其余更多的是為了能讓這三個核心組件更方便的使用。之后的講解也會偏向于此。三個核心部分模型圖:

簡單說明:

  • 一個Thread(線程)對應一個Selector選擇器,每個選擇器對應著多個Channel通道,每個通道又都對應著一個緩沖區(Buffer)
  • Selector 會根據不同的事件,在各個通道上切換,但是程序切換到哪個 Channel 是由事件(比如:連接請求、數據到達)決定的。
  • 數據的讀取寫入是通過 Buffer,這個和 BIO是不同的,BIO 中要么是輸入流,或者是輸出流,不能雙向,但是 NIO 的 Buffer 是可以讀也可以寫,需要 flip 方法切換 Channel 是雙向的,可以返回底層操作系統的情況,比如 Linux,底層的操作系統通道就是雙向的
  • 二、傳統阻塞BIO存在的問題

    我們先來回憶一下傳統阻塞IO的經典編程模型:

    public static void main(String[] args) throws Exception {//1. 創建一個線程池ExecutorService newCachedThreadPool = new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L,TimeUnit.SECONDS,new SynchronousQueue<>(),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());//2、創建ServerSocketServerSocket serverSocket = new ServerSocket(8888);while (true) {//3.偵聽要與此套接字建立的連接并接受它。 該方法阻塞,直到建立連接。final Socket socket = serverSocket.accept();//4、就創建一個線程,與之通訊(單獨寫一個方法)newCachedThreadPool.execute(() -> {//可以和客戶端通訊handler(socket);});} }/*** 編寫一個handler方法,和客戶端通訊,讀取客戶端發過來的信息* @param socket*/ public static void handler(Socket socket) {try {byte[] bytes = new byte[1024];//通過socket獲取輸入流InputStream inputStream = socket.getInputStream();//循環的讀取客戶端發送的數據while (true) {int read = inputStream.read(bytes);if (read != -1) {//輸出客戶端發送的數據System.out.println(new String(bytes, 0, read));} else {break;}}} catch (Exception e) {e.printStackTrace();} finally {System.out.println("關閉和client的連接");try {socket.close();} catch (Exception e) {e.printStackTrace();}} }

    這是一個經典的一個連接一個線程的模型,使用多線程的主要原因在于socket.accept()、socket.read()、socket.write()三個主要函數都是同步阻塞的,即沒有成功連接或沒有數據讀、寫時,系統都是阻塞,在這種情況下如果是單線程的話就會一直阻塞在哪里;但CPU是被釋放出來的,開啟多線程,就可以讓CPU去處理更多的事情。

    不過,這個模型是存在問題的,線程池,雖然可以讓線程的創建和回收成本變低。但是線程池本生是有局限的的,在并發數并不是特別高的時候(小于單機1000),使用線程池搭用這種模型還是可行的,并竟線程池是一個天然的漏斗,不用過多考慮負載、限流等問題,編程也簡單。但是仍然是解決不了根本問題的,根本問題在于嚴重的依賴于線程。但是大家都知道哈,線程是個特別貴的資源。

    為什么說線程貴呢?

    主要表現在以下幾個方面:

  • 首先線程的創建和銷毀的成本相當高,并不是我們簡單的看到的new Thread().start()即可了,它之后是去調用本地函數private native void start0(),然后靠操作系統來分配資源創建線程,用完還要銷毀。(要用到底層的操作系統的,就沒有便宜的操作啦。)像在Liunx這樣的操作系統中,一個個線程本質上就是一個個進程,創建、銷毀都是屬于重量級的函數,所以說線程的創建和銷毀成本真的蠻高啦
  • 其次線程本身占用較大內存的,在Java中,線程的線程棧所占用的內存在Java堆外,不受Java程序控制,只受系統資源限制(如若系統給資源不足,可能創建失敗),默認一個線程的線程棧大小是1M。如果每個請求都新建線程,1024個線程就會占用1個G內存,那樣恐怕整個JVM的內存都會被干掉一半,而且也容易會引起系統的崩潰。
  • 另外線程的切換成本也是很高的。當一個cpu從一個線程切換到另一個線程時,cpu需要保存當前線程的本地數據,程序當前的指針等,然后加載下一個等待執行的線程的本地數據,程序指針等,然后執行系統調用。這種切換也被稱之為上下文切換。但是如果線程數過高,可能執行線程切換的時間甚至會大于線程執行的時間,就會導致系統load偏高,CPU使用率偏高,易導致系統陷入崩潰狀態。
  • 所以說,使用BIO面臨十萬或百萬請求時,是無能為力的。所以必然需要更加高效的I/O處理模型。

    三、NIO和BIO的區別

  • BIO 是阻塞的,NIO 則是非阻塞的。
  • BIO 以流的方式處理數據,而 NIO 以塊的方式處理數據,塊 I/O 的效率比流 I/O 高很多。
  • BIO 基于字節流和字符流進行操作,而 NIO 基于 Channel(通道)和 Buffer(緩沖區)進行操作,數據總是從通道讀取到緩沖區中,或者從緩沖區寫入到通道中。Selector(選擇器)用于監聽多個通道的事件(比如:連接請求,數據到達等),因此使用單個線程就可以監聽多個客戶端通道。
  • BIO適用于連接數目比較小且固定的架構,這種方式對服務器資源要求比較高,并發局限于應用中,編程比較簡單JDK1.4以前唯一的選擇。
    NIO適用于連接數目多且連接比較短的架構,比如聊天服務器,彈幕系統,服務器間通訊等,編程比較復雜,JDK1.4開始支持。
  • 四、I/O模型之間的對比

    學習I/O,I/O模型是必須知道的一點。I/O模型共有五種,分別是:

  • 同步阻塞IO(Blocking IO):即傳統的IO模型。
  • 同步非阻塞IO(Non-blocking IO):默認創建的socket都是阻塞的,非阻塞IO要求socket被設置為NONBLOCK。注意這里所說的NIO并非Java的NIO(New IO)庫。
  • 多路復用IO(IO Multiplexing):即經典的Reactor設計模式,有時也稱為異步阻塞IO,Java中的Selector和Linux中的epoll都是這種模型
  • 信號驅動IO:信號驅動式I/O是指進程預先告知內核,使得當某個描述符上發生某事時,內核使用信號通知相關進程。
  • 異步IO(Asynchronous IO):即經典的Proactor設計模式,也稱為異步非阻塞IO。
  • 不過在這只描述了前三種。

    傳統阻塞式I/O

    模型圖如下:Java中原生的IO便是這種。

    特點

    當用戶線程發出IO請求之后,內核會去查看數據是否就緒,如果沒有就緒就會等待數據就緒,而用戶線程就會處于阻塞狀態,用戶線程交出CPU。當數據就緒之后,內核會將數據拷貝到用戶線程,并返回結果給用戶線程,用戶線程才解除block狀態。

    非阻塞式I/O

    模型圖如下:

    特點

    當用戶線程發起一個read操作后,并不需要等待,而是馬上就得到了一個結果。如果結果是一個error時,它就知道數據還沒有準備好,于是就返回到用戶進程去執行其他任務,等過一段時間后在去查看數據是否準備好。一旦內核中的數據準備好了,并且又再次收到了用戶線程的請求,那么它馬上就將數據拷貝到了用戶線程,然后返回。所以事實上,在非阻塞IO模型中,用戶線程需要不斷地詢問內核數據是否就緒,也就說非阻塞IO不會交出CPU,而會一直占用CPU。

    注意:對于非阻塞IO也有一個非常嚴重的問題,在while循環中需要不斷地去詢問內核數據是否就緒,這樣會導致CPU占用率非常高,因此一般情況下很少使用while循環方式來讀取數據。

    多路復用I/O模型

    Java NIO使用的即是這種模型。

    多路復用IO主要用于處理多個IO連接時候的場景在多路復用IO模型中,會有一個線程不斷去輪詢多個socket的狀態,只有當socket真正有讀寫事件時,才會真正調用實際的IO讀寫操作。因為在多路復用IO模型中,只需使用一個線程就可以管理多個socket,系統無需重復建立新的線程和銷毀線程,也不必維護等,另一方面同時也避免了多線程之間的上下文切換導致的開銷。并且只有在真正有socket讀寫事件進行時,才會使用IO資源,大大減少了資源占用,極大的提高了效率。

    I/O多路復用比傳統I/O好在哪里

    也許有小伙伴會說,我可以采用多線程+ 阻塞IO達到類似的效果,但是你需要考慮到多線程+阻塞IO,沒有改變的是每個socket還是對應著一個線程,仍然無法解決根本問題,并且尤其是對于長連接來說,線程的資源一直不釋放,如果后面陸續還有很多連接的話,就會造成系統的極大壓力。

    而使用多路復用IO模式,通過一個線程就可以去管理多個socket,并且只有當socket真正有讀寫事件發生才會占用資源來進行實際的讀寫操作。所以多路復用IO模式一方面減少了創建和銷毀線程的操作,同時也避免了多線程之間切換的開銷,并且提高了并發數。

    I/O多路復用為何比非阻塞式I/O模型效率高

    因為在非阻塞IO中,不斷地詢問socket狀態是通過用戶線程去進行的,但是在多路復用IO模型中,輪詢每個socket狀態是內核在進行的,這個效率要比用戶線程要高的多。

    注意事項

    多路復用IO模型是通過輪詢的方式來檢測是否有事件到達,并且對到達的事件逐一進行響應。因此對于多路復用IO模型來說,如果一旦事件響應體很大,那么就有可能會導致后續的事件遲遲得不到處理,并且會影響到新的事件輪詢。

    在Java NIO中的理解是

    Selector就是一個socket選擇器,它不停地查看所有與他綁定的socket是否準備完成,哪一個io準備完成,它就會處理對應的channel。


    但是終究選擇什么樣的IO模型,還是需要根據實際問題實際分析的。

    五、三大核心組件 | Buffer

    基本介紹

    Java NIO中的Buffer用于和NIO通道進行交互。數據是從通道讀入緩沖區,從緩沖區寫入到通道中的。

    緩沖區本質上是一塊可以寫入數據,然后可以從中讀取數據的內存。這塊內存被包裝成NIO Buffer對象,并提供了一組方法,用來方便的訪問該塊內存。

    Buffer結構

    Buffer是一個頂級的抽象類,底下有很多的實現類,這里是小小的一個圖哈。從這張圖可用看出Java 中的基本數據類型(boolean 除外),都有一個Buffer對應,不過其中ByteBuffer使用的最多。

    Buffer API:

    public abstract class Buffer {public final int capacity() ;//返回此緩沖區的容量。public final int position();//返回此緩沖區的位置。public Buffer position(int newPosition) ;//設置此緩沖區的位置。 如果標記已定義且大于新位置,則將其丟棄。public final int limit();//返回此緩沖區的限制。public Buffer limit(int newLimit) ;//設置此緩沖區的限制。 public Buffer mark() ;//在其位置設置此緩沖區的標記。public Buffer reset();//將此緩沖區的位置重置為先前標記的位置。public Buffer clear() ;//清除此緩沖區。 將位置設置為零,將限制設置為容量,并丟棄標記。public Buffer flip();//翻轉這個緩沖區。 將限制設置為當前位置,然后將位置設置為零。 如果定義了標記,則將其丟棄。public Buffer rewind();//倒帶此緩沖區。 位置設置為零,標記被丟棄。public final int remaining() ;//返回當前位置和限制之間的元素數。public final boolean hasRemaining();//告訴當前位置和限制之間是否有任何元素。public abstract boolean isReadOnly();//告訴這個緩沖區是否是只讀的。// -----------jdk1.6引入----------public abstract boolean hasArray();//告訴此緩沖區是否由可訪問數組支持。public abstract Object array();//返回支持此緩沖區的數組(可選操作) 。public abstract int arrayOffset();//返回緩沖區第一個元素在此緩沖區的后備數組中的偏移量public abstract boolean isDirect();//判斷此緩沖區是否為direct 。 }

    六、三大核心組件 | Channel

    基本介紹

    Java NIO的通道類似流,但又有些不同:

    • 既可以從通道中讀取數據,又可以寫數據到通道。但流的讀寫通常是單向的。
    • 通道可以異步地讀寫。
    • 通道中的數據總是要先讀到一個Buffer,或者總是要從一個Buffer中寫入。

    常用Channel的實現類

    以下是Java NIO中最常用Channel的通道的實現:

    • FileChannel 從文件中讀寫數據。
    • DatagramChannel 能通過UDP讀寫網絡中的數據。
    • SocketChannel 能通過TCP讀寫網絡中的數據。
    • ServerSocketChannel可以監聽新進來的TCP連接,像Web服務器那樣。對每一個新進來的連接都會創建一個SocketChannel。

    結構圖:

    小小的應用案例:

    場景:使用一個Buffer和FileChannel完成文件讀取、寫入。

    my.txt—>you.txt

    public class NIOFileChannel {public static void main(String[] args) throws Exception {FileInputStream fileInputStream = new FileInputStream("my.txt");FileChannel fileChannel01 = fileInputStream.getChannel();FileOutputStream fileOutputStream = new FileOutputStream("you.txt");FileChannel fileChannel02 = fileOutputStream.getChannel();ByteBuffer byteBuffer = ByteBuffer.allocate(512);//循環讀取while (true) {//清空 buffer 緩沖區byteBuffer.clear(); int read = fileChannel01.read(byteBuffer);System.out.println("read = " + read);//表示讀完if (read == -1) { break;}//將 buffer 中的數據寫入到 fileChannel02--2.txtbyteBuffer.flip();fileChannel02.write(byteBuffer);}//關閉相關的流fileInputStream.close();fileOutputStream.close();} }

    七、三大核心組件 | Selector

    基本介紹

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

    在這里只要知道使用Selector能夠處理多個通道就足夠了,其他的暫時先不去瞄了哈。

    模型圖:

    Selector | API:

    public abstract class Selector implements Closeable {public static Selector open() ;//打開一個選擇器。得到一個選擇器對象public abstract boolean isOpen();//告訴這個選擇器是否打開public abstract SelectorProvider provider(); //返回創建此頻道的提供者。public abstract Set<SelectionKey> keys(); //返回此選擇器的鍵集public abstract Set<SelectionKey> selectedKeys();//返回此選擇器的選定鍵集。public abstract int selectNow() throws IOException;//不阻塞,立馬返還 ,其對應的通道已準備好進行 I/O 操作。public abstract int select(long timeout) throws IOException;//監控所有注冊的通道,當其中有IO操作時,將對應的SelectionKey加入到內部集合中并返回,參數用來設置超時時間。public abstract Selector wakeup();//使尚未返回的第一個選擇操作立即返回。如果另一個線程當前在選擇操作中被阻塞,那么該調用將立即返回。 public abstract void close() throws IOException;//關閉此選擇器。 }

    因為要結合其他組件才能體現出Selector,所以案例放在后文中了。


    八、NIO 網絡編程分析圖

    模型圖如下:

    流程分析:

  • 服務器端啟動,打開服務器的ServerSocketChannel,創建并且打開Selector,將ServerSocketChannel注冊到selector上

    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); Selector selector = Selector.open(); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
  • Selector 進行監聽 select 方法, 返回有事件發生的通道的個數

    if (selector.select(1000) == 0) //這里我寫的是每秒刷新一次
  • 客戶端啟動,打開客戶端的SocketChannel,綁定服務端地址,向服務端發送連接請求

    SocketChannel socketChannel = SocketChannel.open(); socketChannel.connect(inetSocketAddress)
  • 服務端Selector監聽到注冊事件發生后,通過selector.selectedKeys()方法拿到SelectionKey,會和該 Selector 關聯(集合)

    //通過selector得到selectionKey Set<SelectionKey> selectionKeys = selector.selectedKeys();
  • 再通過 java.nio.channels.SelectionKey 反向獲取 SocketChannel , 方法 channel()

    SocketChannel channel = (SocketChannel) selectionKey.channel();
  • 可以通過得到的channel , 完成業務處理

  • 九、快速入門案例(一)

    一直寫理論,不寫代碼,看著發愁。所以一起來看看這個簡單的入門案例吧。

    場景:NIO 入門案例,實現服務器端和客戶端之間的數據簡單通訊(非阻塞)

    import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Set;public class NioServerTest {public static void main(String[] args) throws Exception{//1、創建一個ServerSocketChannel對象,綁定端口并配置成非阻塞模式。ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.bind(new InetSocketAddress(8888), 1024);//2、下面這句必需要,否則ServerSocketChannel會使用阻塞的模式,那就不是NIO了serverSocketChannel.configureBlocking(false);//3、把ServerSocketChannel交給Selector監聽Selector selector = Selector.open();//4、注冊進SelectorserverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);//5、循環,不斷的從Selector中獲取準備就緒的Channel,最開始的時候Selector只監聽了一個ServerSocketChannel//但是后續有客戶端連接時,會把客戶端對應的Channel也交給Selector對象while (true) {//6、一直監聽,每秒刷新一次,等待客戶端的連接if (selector.select(1000) == 0){continue;}//獲取所有的準備就緒的Channel,SelectionKey中包含中Channel信息Set<SelectionKey> selectionKeySet = selector.selectedKeys();//遍歷,每個Channel都可處理for (SelectionKey selectionKey : selectionKeySet) {//如果Channel已經無效了,則跳過(如Channel已經關閉了)if(!selectionKey.isValid()) {continue;}//判斷Channel具體的就緒事件,如果是有客戶端連接,則建立連接if (selectionKey.isAcceptable()) {System.out.println("連接成功!!!");//當確定有selectionKey時,說明必有socketChannel,Server接收后得到SocketChannelSocketChannel socketChannel = serverSocketChannel.accept();socketChannel.configureBlocking(false);//把客戶端的Channel交給Selector監控,之后如果有數據可以讀取時,會被select出來socketChannel.register(selector, SelectionKey.OP_READ);}//如果有客戶端可以讀取請求了,則讀取請求然后返回數據if (selectionKey.isReadable()) {System.out.println(readFromSelectionKey(selectionKey));}}//處理完成后把返回的Set清空,如果不清空下次還會再返回這些Key,導致重復處理selectionKeySet.clear();}}//從客戶端讀取數據的廬江private static String readFromSelectionKey(SelectionKey selectionKey) throws Exception{//從SelectionKey中包含選取出來的Channel的信息把Channel獲取出來SocketChannel socketChannel = ((SocketChannel) selectionKey.channel());//讀取數據到ByteBuffer中ByteBuffer byteBuffer = ByteBuffer.allocate(1024);int len = socketChannel.read(byteBuffer);//如果讀到-1,說明數據已經傳輸完成了,可以并閉if (len < 0) {socketChannel.close();selectionKey.cancel();return "";} else if(len == 0) { //什么都沒讀到return "";}byteBuffer.flip();doWrite(selectionKey, "Hello Nio");return new String(byteBuffer.array(), 0, len);}private static void doWrite(SelectionKey selectionKey, String responseMessage) throws Exception{System.err.println("Output message...");SocketChannel socketChannel = ((SocketChannel) selectionKey.channel());ByteBuffer byteBuffer = ByteBuffer.allocate(responseMessage.getBytes().length);byteBuffer.put(responseMessage.getBytes());byteBuffer.flip();socketChannel.write(byteBuffer);} }

    客戶端:

    package com.crush.nio.nio2;import java.net.InetSocketAddress;import java.nio.ByteBuffer;import java.nio.channels.SelectionKey;import java.nio.channels.Selector;import java.nio.channels.SocketChannel;import java.util.Set;/** * @Author: crush * @Date: 2021-08-24 16:01 * version 1.0 */public class NioClientTest { public static void main(String[] args) throws Exception { //創建一個SocketChannel對象,配置成非阻塞模式 SocketChannel socketChannel = SocketChannel.open(); socketChannel.configureBlocking(false); //創建一個選擇器,并把SocketChannel交給selector對象 Selector selector = Selector.open(); socketChannel.register(selector, SelectionKey.OP_CONNECT); //發起建立連接的請求,這里會立即返回,當連接建立完成后,SocketChannel就會被選取出來 socketChannel.connect(new InetSocketAddress("localhost", 8888)); //遍歷,不段的從Selector中選取出已經就緒的Channel,在這個例子中,Selector只監控了一個SocketChannel while (true) { selector.select(1000); Set<SelectionKey> selectionKeySet = selector.selectedKeys(); for (SelectionKey selectionKey : selectionKeySet) { if (!selectionKey.isValid()) { continue; } //連接建立完成后的操作:直接發送請求數據 if (selectionKey.isConnectable()) { if (socketChannel.finishConnect()) { socketChannel.register(selector, SelectionKey.OP_READ); doWriteRequest(((SocketChannel) selectionKey.channel())); } } //如果當前已經可以讀數據了,說明服務端已經響應完了,讀取數據 if (selectionKey.isReadable()) { doRead(selectionKey); } } //最后同樣要清除所有的Key selectionKeySet.removeAll(selectionKeySet); } } //發送請求 private static void doWriteRequest(SocketChannel socketChannel) throws Exception { System.err.println("start connect..."); //創建ByteBuffer對象,會放入數據 ByteBuffer byteBuffer = ByteBuffer.allocate("Hello Server!".getBytes().length); byteBuffer.put("Hello Server!".getBytes()); byteBuffer.flip(); //寫數據 socketChannel.write(byteBuffer); if (!byteBuffer.hasRemaining()) { System.err.println("Send request success..."); } } //讀取服務端的響應 private static void doRead(SelectionKey selectionKey) throws Exception { SocketChannel socketChannel = ((SocketChannel) selectionKey.channel()); ByteBuffer byteBuffer = ByteBuffer.allocate(1024); int len = socketChannel.read(byteBuffer); System.out.println("Recv:" + new String(byteBuffer.array(), 0, len)); }}

    先啟動服務器端,再啟動客戶端,直接可以在客戶端看到輸出消息。

    十、快速入門案例(二)

    /*** @Author: crush* @Date: 2021-08-24 16:33* version 1.0*/ public class GroupChatServer {//定義屬性private Selector selector;private ServerSocketChannel listenChannel;private static final int PORT = 6667;//構造器//初始化工作public GroupChatServer() {try {//得到選擇器selector = Selector.open();//ServerSocketChannellistenChannel = ServerSocketChannel.open();//綁定端口listenChannel.socket().bind(new InetSocketAddress(PORT));//設置非阻塞模式listenChannel.configureBlocking(false);//將該 listenChannel 注冊到 selectorlistenChannel.register(selector, SelectionKey.OP_ACCEPT);} catch (IOException e) {e.printStackTrace();}}public void listen() {try {//循環處理while (true) {int count = selector.select();if (count > 0) { //有事件處理// 遍歷得到 selectionKey 集合Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();while (iterator.hasNext()) {//取出 selectionkeySelectionKey key = iterator.next();//監聽到 acceptif (key.isAcceptable()) {SocketChannel sc = listenChannel.accept();sc.configureBlocking(false);//將該 sc 注冊到 seletorsc.register(selector, SelectionKey.OP_READ);//提示System.out.println(sc.getRemoteAddress() + " 上線 ");}if (key.isReadable()) {//通道發送read事件,即通道是可讀的狀態// 處理讀(專門寫方法..)readData(key);}//當前的 key 刪除,防止重復處理iterator.remove();}} else {System.out.println("等待....");}}} catch (Exception e) {e.printStackTrace();} finally {//發生異常處理....}}//讀取客戶端消息public void readData(SelectionKey key) {SocketChannel channel = null;try {//得到 channelchannel = (SocketChannel) key.channel();//創建 bufferByteBuffer buffer = ByteBuffer.allocate(1024);int count = channel.read(buffer);//根據 count 的值做處理if (count > 0) {//把緩存區的數據轉成字符串String msg = new String(buffer.array());//輸出該消息System.out.println("form客戶端:" + msg);//向其它的客戶端轉發消息(去掉自己),專門寫一個方法來處理sendInfoToOtherClients(msg, channel);}} catch (IOException e) {try {System.out.println(channel.getRemoteAddress() + "離線了..");//取消注冊key.cancel();//關閉通道channel.close();} catch (IOException e2) {e2.printStackTrace();}}}//轉發消息給其它客戶(通道)private void sendInfoToOtherClients(String msg, SocketChannel self) throws IOException {System.out.println("服務器轉發消息中...");//遍歷所有注冊到 selector 上的 SocketChannel,并排除 selffor (SelectionKey key : selector.keys()) {//通過 key 取出對應的 SocketChannelChannel targetChannel = key.channel();//排除自己if (targetChannel instanceof SocketChannel && targetChannel != self) {//轉型SocketChannel dest = (SocketChannel) targetChannel;//將 msg 存儲到 bufferByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());//將 buffer 的數據寫入通道dest.write(buffer);}}}public static void main(String[] args) {//創建服務器對象GroupChatServer groupChatServer = new GroupChatServer();groupChatServer.listen();} }

    客戶端:

    /*** @Author: crush* @Date: 2021-08-24 16:33* version 1.0*/ public class GroupChatClient {//定義相關的屬性private final String HOST = "127.0.0.1";//服務器的ipprivate final int PORT = 6667;//服務器端口private Selector selector;private SocketChannel socketChannel;private String username;//構造器,完成初始化工作public GroupChatClient() throws IOException {selector = Selector.open();//連接服務器socketChannel = SocketChannel.open(new InetSocketAddress(HOST, PORT));//設置非阻塞socketChannel.configureBlocking(false);//將 channel 注冊到selectorsocketChannel.register(selector, SelectionKey.OP_READ);//得到 usernameusername = socketChannel.getLocalAddress().toString().substring(1);System.out.println(username + " is ok...");}//向服務器發送消息public void sendInfo(String info) {info = username + " 說:" + info;try {socketChannel.write(ByteBuffer.wrap(info.getBytes()));} catch (IOException e) {e.printStackTrace();}}//讀取從服務器端回復的消息public void readInfo() {try {int readChannels = selector.select();if (readChannels > 0) {//有可以用的通道Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();while (iterator.hasNext()) {SelectionKey key = iterator.next();if (key.isReadable()) {//得到相關的通道SocketChannel sc = (SocketChannel) key.channel();//得到一個 BufferByteBuffer buffer = ByteBuffer.allocate(1024);//讀取sc.read(buffer);//把讀到的緩沖區的數據轉成字符串String msg = new String(buffer.array());System.out.println(msg.trim());}}iterator.remove(); //刪除當前的 selectionKey,防止重復操作} else {//System.out.println("沒有可以用的通道...");}} catch (Exception e) {e.printStackTrace();}}public static void main(String[] args) throws Exception {//啟動我們客戶端GroupChatClient chatClient = new GroupChatClient();//啟動一個線程,每個 3 秒,讀取從服務器發送數據new Thread() {public void run() {while (true) {chatClient.readInfo();try {Thread.currentThread().sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}}}}.start();//發送數據給服務器端Scanner scanner = new Scanner(System.in);while (scanner.hasNextLine()) {String s = scanner.nextLine();chatClient.sendInfo(s);}} }

    測試:

    十一、自言自語

    個人感受:越學習到后面,就越感覺基礎的重要性。很多技術都只會去用,很多時候甚至都不懂它的設計理念也不懂為什么要那樣做。

    最近在持續更新中,如果你覺得對你有所幫助,也感興趣的話,關注我吧,讓我們一起學習,一起討論吧。

    在學習路上充滿好奇心,明白思考的重要性,是支持我一直學習下去的積極推動力吧。希望你也能喜歡上編程!😁

    熱愛生活,享受生活!!!無論在哪里,無論此時的生活多么艱難,都要記得熱愛生活!!!相信總會有光來的。

    你好,我是博主寧在春,Java學習路上的一顆小小的種子,也希望有一天能扎根長成蒼天大樹。

    希望與君共勉😁

    我們:待別時相見時,都已有所成

    本文是個人的學習筆記,夾雜著個人理解,如有不對,請不嗇賜教。課程是來自于尚硅谷-韓順平老師Netty課程

    參考:
    五種IO模型詳解及優缺點

    五種IO模型及其比較

    Java NIO淺析

    NIO非阻塞網絡編程原理

    總結

    以上是生活随笔為你收集整理的你对Java网络编程了解的如何?Java NIO 网络编程 | Netty前期知识(二)的全部內容,希望文章能夠幫你解決所遇到的問題。

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

    主站蜘蛛池模板: 亚洲草逼视频 | 成人午夜影院 | 中文字幕女同女同女同 | 亚洲资源网站 | 亚洲美女一区二区三区 | 日本特黄色片 | 日本超碰 | 国产亚洲欧美一区二区三区 | 欧美色综合网 | 在线看黄的网站 | 美女视频黄频视频大全 | 筱田优av | 日本在线观看免费 | 爱爱视频网站 | 99久久婷婷国产一区二区三区 | 亚洲天堂自拍偷拍 | 青青视频免费看 | 毛片黄片免费看 | 亚洲第一成人在线 | 经典杯子蛋糕日剧在线观看免费 | www日本黄色 | 天堂网8 | 校园sm主奴调教1v1罚视频 | 97久久精品视频 | 国产成人免费av | 丝袜美腿一区二区三区 | 亚洲午夜精品 | 久久av影院 | 成人免费网站在线 | 久久这里只有精品首页 | 黄黄的网站 | 日韩丝袜一区 | 69精品| 亚洲丁香网 | 国产伦精品一区二区三区视频女 | 三女同志亚洲人狂欢 | 成人午夜在线免费观看 | 国产精品久久久久久久久久久新郎 | av在线网址观看 | 日韩在线视频看看 | 精品久久久久久久久久久aⅴ | 轻轻色在线观看 | 91在线日本 | 精品人妻一区二 | 在线播放一区二区三区 | 六月婷婷在线观看 | 国产精品99久久免费黑人人妻 | 亚洲成人av一区二区三区 | 91精品国产自产精品男人的天堂 | 国产乱码一区二区三区在线观看 | 人人澡超碰碰97碰碰碰 | 穿越异世荒淫h啪肉np文 | 妺妺窝人体色www在线小说 | av在线电影院 | 亚洲大色网 | 四虎永久在线 | 九九九在线观看 | 精品一区二区三区无码视频 | 性欢交69精品久久久 | 日韩一区二区三区中文字幕 | 丁香六月综合 | 国产亚洲av片在线观看18女人 | 日色网站 | 亚洲第一视频在线 | 性激烈视频在线观看 | 国产九色在线 | 成人精品国产免费网站 | 中文视频一区二区 | 九九热精品在线视频 | 中文字幕+乱码+中文字幕一区 | 疯狂做爰的爽文多肉小说王爷 | 美女被c出白浆 | 在线免费观看av网 | 自拍偷拍第五页 | 黄色片大全 | 国产后入清纯学生妹 | 自拍欧美日韩 | 69精品无码成人久久久久久 | 亚洲天堂黄色 | 亚洲乱亚洲 | 国产精品丝袜 | 亚洲国产第一页 | 欧美日韩国产大片 | 91久久一区二区三区 | 国内av网 | 麻豆91在线 | 成人区人妻精品一熟女 | 综合网在线观看 | 舒淇裸体午夜理伦 | 成人午夜精品福利 | 色悠悠久久综合 | 天天摸日日摸狠狠添 | 日韩视频在线观看视频 | 娇妻被老王脔到高潮失禁视频 | 成人77777 | av黄色片在线观看 | 日韩在线视频播放 | np视频 | 羞羞漫画在线播放 |