你对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。其余更多的是為了能讓這三個核心組件更方便的使用。之后的講解也會偏向于此。三個核心部分模型圖:
簡單說明:
二、傳統阻塞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),使用線程池搭用這種模型還是可行的,并竟線程池是一個天然的漏斗,不用過多考慮負載、限流等問題,編程也簡單。但是仍然是解決不了根本問題的,根本問題在于嚴重的依賴于線程。但是大家都知道哈,線程是個特別貴的資源。
為什么說線程貴呢?
主要表現在以下幾個方面:
所以說,使用BIO面臨十萬或百萬請求時,是無能為力的。所以必然需要更加高效的I/O處理模型。
三、NIO和BIO的區別
NIO適用于連接數目多且連接比較短的架構,比如聊天服務器,彈幕系統,服務器間通訊等,編程比較復雜,JDK1.4開始支持。
四、I/O模型之間的對比
學習I/O,I/O模型是必須知道的一點。I/O模型共有五種,分別是:
不過在這只描述了前三種。
傳統阻塞式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前期知识(二)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 你对Java网络编程了解的如何?Java
- 下一篇: 如何使用Docker安装Mycat中间件