最近一直在看與IM相關的東西說到這個java中應該要說到netty,下面來認識一下netty。
開始瞭解netty之前我們先了解下傳統的IO程式設計。
IO程式設計
我們來做一個比較簡單的demo:客戶端每隔5秒傳送一條訊息到服務端,服務端收到訊息打印出來。
服務端實現
Server 端首先建立了一個
serverSocket
來監聽 8000 埠,然後建立一個執行緒,執行緒裡面不斷呼叫阻塞方法
serversocket。accept();
獲取新的連線,當獲取到新的連線之後,給每條連線建立一個新的執行緒,這個執行緒負責從該連線中讀取資料,然後讀取資料是以位元組流的方式。
package com。pine。springbootdemo01。netty;import java。io。IOException;import java。io。InputStream;import java。net。ServerSocket;import java。net。Socket;/** * @author anziyang * @version V1。0 * @date 2022/3/1 11:53 上午 **/public class IOServer { public static void main(String[] args) throws Exception { ServerSocket serverSocket = new ServerSocket(8000); // 接收新連線執行緒 new Thread(() -> { while (true) { try { // 阻塞方法獲取新的連線 Socket socket = serverSocket。accept(); // 每一個新的連線都建立一個執行緒,負責讀取資料 new Thread(() -> { try { int len; byte[] data = new byte[1024]; InputStream inputStream = socket。getInputStream(); // 按位元組流方式讀取資料 while ((len = inputStream。read(data)) != -1) { System。out。println(new String(data, 0, len)); } } catch (IOException e) { System。err。println(e); } })。start(); } catch (IOException e) { System。err。println(e); } } })。start(); }}
客戶端實現
啟動一個執行緒連線到服務端,迴圈中每次睡眠五秒也就是每隔五秒傳送一次訊息。
package com。pine。springbootdemo01。netty;import java。io。IOException;import java。net。Socket;import java。util。Date;/** * @author anziyang * @version V1。0 * @date 2022/3/1 11:54 上午 **/public class IOClient { public static void main(String[] args) { // 啟動一個執行緒 new Thread(() -> { try { Socket socket = new Socket(“127。0。0。1”, 8000); while (true) { try { socket。getOutputStream()。write((new Date() + “: hello world socket”)。getBytes()); // 睡眠5秒 Thread。sleep(5000); } catch (Exception e) { System。err。println(e); } } } catch (IOException e) { System。err。println(e); } })。start(); }}
先啟動server端然後啟動client可以看到server收到請求如下:
上面的 demo,從服務端程式碼中我們可以看到,在傳統的 IO 模型中,每個連線建立成功之後都需要一個執行緒來維護,每個執行緒包含一個 while true 死迴圈,那麼 10w 個連線對應 10w 個執行緒,繼而 10w 個 while 死迴圈,這就帶來如下幾個問題:
執行緒資源受限:執行緒是作業系統中非常寶貴的資源,同一時刻有大量的執行緒處於阻塞狀態是非常嚴重的資源浪費,作業系統耗不起
執行緒切換效率低下:單機 CPU 核數固定,執行緒爆炸之後作業系統頻繁進行執行緒切換,應用效能急劇下降。
除了以上兩個問題,IO 程式設計中,我們看到資料讀寫是以位元組流為單位。
為了解決這三個問題,JDK 在 1。4 之後提出了 NIO。
NIO(Non-blocking I/O,在Java領域,也稱為New I/O),是一種同步非阻塞的I/O模型,也是I/O多路複用的基礎,已經被越來越多地應用到大型應用伺服器,成為解決高併發與大量連線、I/O處理問題的有效方式。
NIO程式設計
java對於非堵塞I/O的支援是在2002年引入的,位於JDK1。4的java。nio包中。
IO和NIO的區別:
IO是面向位元組流和字元流的,而NIO是面向緩衝區的。
IO是阻塞模式的,NIO是非阻塞模式的
NIO新增了選擇器的概念,可以透過選擇器監聽多個通道。
NIO用的是事件機制。它可以用一個執行緒把Accept,讀,寫操作,請求處理的邏輯全乾了。如果什麼事都沒得做,它也不會死迴圈,它會將執行緒休眠起來,直到下一個事件來了再繼續幹活,這樣的一個執行緒稱之為NIO執行緒。
package com。pine。springbootdemo01。netty;import java。io。IOException;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。nio。charset。Charset;import java。util。Iterator;import java。util。Set;/** * @author anziyang * @version V1。0 * @date 2022/3/1 2:29 下午 **/public class NIOServer { public static void main(String[] args) throws IOException { Selector serverSelector = Selector。open(); Selector clientSelector = Selector。open(); // 啟動一個執行緒繫結select 用於監聽是否有新的連線 new Thread(() -> { try { // 對應IO程式設計中服務端啟動 ServerSocketChannel listenerChannel = ServerSocketChannel。open(); listenerChannel。socket()。bind(new InetSocketAddress(8000)); listenerChannel。configureBlocking(false); listenerChannel。register(serverSelector, SelectionKey。OP_ACCEPT); while (true) { // 監測是否有新的連線,這裡的1指的是阻塞的時間為 1ms if (serverSelector。select(1) > 0) { Set
從上面的程式碼中我們可以看到
NIO 模型中通常會有兩個執行緒,每個執行緒繫結一個輪詢器 selector ,在我們這個例子中
serverSelector
負責輪詢是否有新的連線,
clientSelector
負責輪詢連線是否有資料可讀
服務端監測到新的連線之後,不再建立一個新的執行緒,而是直接將新連線繫結到
clientSelector
上,這樣就不用 IO 模型中 1w 個 while 迴圈在死等,參見(1)
clientSelector
被一個 while 死迴圈包裹著,如果在某一時刻有多條連線有資料可讀,那麼透過
clientSelector。select(1)
方法可以輪詢出來,進而批次處理,參見(2)
資料的讀寫面向 Buffer,參見(3)
啟動NIOServer main方法然後啟動之前的client可以看到控制列印
Netty程式設計
首先看下netty如何實現服務端的:
package com。pine。springbootdemo01。netty;import io。netty。bootstrap。ServerBootstrap;import io。netty。channel。ChannelHandlerContext;import io。netty。channel。ChannelInitializer;import io。netty。channel。SimpleChannelInboundHandler;import io。netty。channel。nio。NioEventLoopGroup;import io。netty。channel。socket。nio。NioServerSocketChannel;import io。netty。channel。socket。nio。NioSocketChannel;import io。netty。handler。codec。string。StringDecoder;/** * @author anziyang * @version V1。0 * @date 2022/3/1 2:41 下午 **/public class NettyServer { public static void main(String[] args) { ServerBootstrap serverBootstrap = new ServerBootstrap(); NioEventLoopGroup boss = new NioEventLoopGroup(); NioEventLoopGroup worker = new NioEventLoopGroup(); serverBootstrap。group(boss, worker)。channel(NioServerSocketChannel。class) 。childHandler(new ChannelInitializer
上面程式碼明顯比前面的NIO少很多程式碼,優雅了不少。
可以看到netty服務端輸出內容:
本文就先聊這麼多,主要是熟悉下netty程式設計,看下經典的hello world。
感覺有收穫點個贊,轉發下哦
參考文件:
https://juejin。cn/book/6844733738119593991/section/6844733738270588942
https://www。jianshu。com/p/432d5557a19e
https://www。cnblogs。com/flyingeagle/articles/11031163。html