目錄
什么是 Netty
對比Netty和傳統的Socket
傳統Socket編程服務端
傳統Socket編程客戶端
Netty環境搭建
先創建出來一個項目
Netty服務端程序
Netty客戶端程序
Channel
Channel分類
為什么選擇Netty
什么是 Netty
Netty是由JBOSS提供的一個java開源框架,現為Github上的獨立項目。Netty提供異步的、事件驅動的網絡應用程序框架和工具,用以快速開發高性能、高可靠性的網絡服務器和客戶端程序。
也就是說,Netty 是一個基于NIO的客戶、服務器端的編程框架,使用Netty 可以確保快速和簡單的開發出一個網絡應用,例如實現了某種協議的客戶、服務端應用。Netty相當于簡化和流線化了網絡應用的編程開發過程,例如:基于TCP和UDP的socket服務開發。
上面是來自于百度百科給出的解釋,能清晰的看到,Netty是一個基于NIO的模型,使用Netty的地方很多就是socket服務開發,而關于NIO,相信大家肯定不陌生。?
對比Netty和傳統的Socket
既然要說Netty,那么肯定要對Netty還有Socket不同的代碼進行一個分析,分析的透徹了,才能真的選擇使用Netty,而不再進行Socket的開發了,相信到時候,大家肯定會做出最正確的選擇。?
傳統Socket編程服務端
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/*** @ClassName SocketDemo* @Date 2021/4/19 10:33* @Description SocketDemo*/
public class SocketServerDemo {public static void main(String[] args) {ServerSocket server=null;try {server=new ServerSocket(18080);System.out.println("時間服務已經啟動--端口號為:18080...");while (true){Socket client = server.accept();//每次接收到一個新的客戶端連接,啟動一個新的線程來處理new Thread(new TimeServerHandler(client)).start();}} catch (IOException e) {e.printStackTrace();}finally {try {server.close();} catch (IOException e) {e.printStackTrace();}}}
}
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Calendar;
/*** @ClassName TimeServerHandler* @Date 2021/4/19 10:35* @Description TimeServerHandler*/
public class TimeServerHandler implements Runnable {private Socket clientProxxy;public TimeServerHandler(Socket clientProxxy) {this.clientProxxy = clientProxxy;}@Overridepublic void run() {BufferedReader reader = null;PrintWriter writer = null;try {reader = new BufferedReader(new InputStreamReader(clientProxxy.getInputStream()));writer =new PrintWriter(clientProxxy.getOutputStream()) ;while (true) {//因為一個client可以發送多次請求,這里的每一次循環,相當于接收處理一次請求String request = reader.readLine();if (!"GET CURRENT TIME".equals(request)) {writer.println("BAD_REQUEST");} else {writer.println(Calendar.getInstance().getTime().toLocaleString());}writer.flush();}} catch (Exception e) {throw new RuntimeException(e);} finally {try {writer.close();reader.close();clientProxxy.close();} catch (IOException e) {e.printStackTrace();}}}
}
傳統Socket編程客戶端
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
/*** @ClassName SocketClientDemo* @Date 2021/4/19 10:42* @Description SocketClientDemo*/
public class SocketClientDemo {public static void main(String[] args) {BufferedReader reader = null;PrintWriter writer = null;Socket client=null;try {client=new Socket("127.0.0.1",18080);writer = new PrintWriter(client.getOutputStream());reader = new BufferedReader(new InputStreamReader(client.getInputStream()));while (true){//每隔5秒發送一次請求writer.println("GET CURRENT TIME");writer.flush();String response = reader.readLine();System.out.println("Current Time:"+response);Thread.sleep(5000);}} catch (Exception e) {e.printStackTrace();} finally {try {writer.close();reader.close();client.close();} catch (IOException e) {e.printStackTrace();}}}
}
來執行一下才能知道效果,
首先運行服務端:
TimeServer Started on 18080...
接著啟動客戶端
Current Time:2021-4-19 10:48:21
Current Time:2021-4-19 10:48:26
Current Time:2021-4-19 10:48:31
Current Time:2021-4-19 10:48:36
Current Time:2021-4-19 10:48:41
Current Time:2021-4-19 10:48:46
Current Time:2021-4-19 10:48:51
大家看一下,這是不是就是相當于一個Socket的客戶端和服務端之間進行通信的過程,在client端可以發送請求指令”GET CURRENT TIME”給server端,每隔5秒鐘發送一次,每次server端都返回當前時間。
而這也是傳統的BIO的做法,每一個client都需要去對應一個線程去進行處理,client越多,那么要開啟的線程也就會越多,也就是說,如果采用BIO通信模型的服務端,通常由一個獨立的Acceptor線程負責監聽客戶端的連接,當接收到客戶端的連接請求后,會為每一個客戶端請求創建新的線程進行請求的處理,處理完成后通過輸出流返回信息給客戶端,響應完成后銷毀線程。
模型圖如下
這時候就有大佬說,不會用線程池么?使用線程池的話,它實際上并沒有解決任何實際性的問題,他實際上就是對BIO做了一個優化,屬于偽異步IO通信。
偽異步IO通信模型圖
異步IO通信確實能緩解一部分的壓力,但是這種模型也是有缺陷的,當有大量客戶端請求的時候,隨著并發訪問量的增長,偽異步IO就會造成線程池阻塞。
這時候就取決于是想選擇,系統發生線程堆棧溢出、創建新線程失敗等問題呢,還是選擇大量客戶端請求,造成線程池阻塞。
都說,技術是為了解決問題而出現的,那么接下來就有了解決這個問題的技術出現了,Netty,來看看Netty吧。?
Netty環境搭建
在這里使用的依舊是Springboot來整合Netty的環境,然后在后續過程中,使用Netty實現服務端程序和客戶端程序,雖然Netty并沒有實現傳說中的AIO,但是已經算是吧這個NIO的模型,實現到了極致了。?
先創建出來一個項目
?加入Netty的pom的依賴
<!--Netty-->
<dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.31.Final</version>
</dependency>
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.16.22</version>
</dependency>
<!-- logger -->
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.25</version>
</dependency>
<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-core</artifactId><version>1.2.3</version>
</dependency>
<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version>
</dependency>
Netty服務端程序
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
/*** @ClassName NettyServerDemo* @Date 2021/4/19 11:11* @Description NettyServerDemo*/
public class NettyServerDemo {private int port=18081;public void run() throws Exception {EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup();try {ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Overridepublic void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new LineBasedFrameDecoder(1024));ch.pipeline().addLast(new StringDecoder());ch.pipeline().addLast(new TimeServerHandler());}});ChannelFuture f = b.bind(port).sync(); System.out.println("TimeServer Started on 18081...");f.channel().closeFuture().sync();} finally {workerGroup.shutdownGracefully();bossGroup.shutdownGracefully();}}public static void main(String[] args) throws Exception {new NettyServerDemo().run();}
}
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.util.Date;
/*** @ClassName TimeServerHandler* @Date 2021/4/19 11:19* @Description TimeServerHandler*/
public class TimeServerHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {String request = (String) msg;String response = null;if ("QUERY TIME ORDER".equals(request)) {response = new Date(System.currentTimeMillis()).toString();} else {response = "BAD REQUEST";}response = response + System.getProperty("line.separator");ByteBuf resp = Unpooled.copiedBuffer(response.getBytes());ctx.writeAndFlush(resp);}
}
Netty客戶端程序
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
/*** @ClassName NettyClientDemo* @Date 2021/4/19 11:21* @Description NettyClientDemo*/
public class NettyClientDemo {public static void main(String[] args) throws Exception {String host = "localhost";int port = 18081;EventLoopGroup workerGroup = new NioEventLoopGroup();try {Bootstrap b = new Bootstrap();b.group(workerGroup);b.channel(NioSocketChannel.class);b.handler(new ChannelInitializer<SocketChannel>() {@Overridepublic void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new LineBasedFrameDecoder(1024));ch.pipeline().addLast(new StringDecoder());ch.pipeline().addLast(new TimeClientHandler());}});// 開啟客戶端.ChannelFuture f = b.connect(host, port).sync();// 等到連接關閉.f.channel().closeFuture().sync();} finally {workerGroup.shutdownGracefully();}}
}
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/*** @ClassName TimeClientHandler* @Date 2021/4/19 11:22* @Description TimeClientHandler*/
public class TimeClientHandler extends ChannelInboundHandlerAdapter {private byte[] req=("QUERY TIME ORDER" + System.getProperty("line.separator")).getBytes();@Overridepublic void channelActive(ChannelHandlerContext ctx) {//1ByteBuf message = Unpooled.buffer(req.length);message.writeBytes(req);ctx.writeAndFlush(message);}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {String body = (String) msg;System.out.println("Now is:" + body);}
}
首先啟動服務端,控制臺輸出:
TimeServer Started on 18081...
接著啟動客戶端,控制要輸出:
Now is:Mon Apr 19 11:34:21 CST 2021
既然代碼寫了,那是不是就得來分析一下這個Netty在中間都干了什么東西,他的類是什么樣子的,都有哪些方法。
大家先從代碼的源碼上開始看起,因為在代碼中分別使用到了好幾個類,而這些類的父類,或者是接口定義者追根到底,也就是這個樣子的,從IDEA中打開他的類圖可以清晰的看到。
而在源碼中,最重要的就是這個Channel,接下來就來分析一波吧。?
Channel
All I/O operations are asynchronous.一句話點出核心所有的IO操作都是異步的,這意味著任何I/O調用都將立即返回,但不保證請求的I/O操作已完成。這是在源碼的注釋上面給出的解釋。?
Channel分類
- 服務端:?
NioServerSocketChannel
- 客戶端:?
NioSocketChannel
看到這個,大家肯定也都不陌生,因為Channel即可以在JDK的Socket中充當管道出現,同時,也在Netty的服務端和客戶端進行IO數據交互,充當一個媒介的存在,那么他的區別在哪?
Netty對Jdk原生的ServerSocketChannel進行了封裝和增強封裝成了NioXXXChannel, 相對于原生的JdkChannel,Netty的Channel增加了如下的組件。
- id 標識唯一身份信息
- 可能存在的parent Channel
- 管道 pepiline
- 用于數據讀寫的unsafe內部類
- 關聯上相伴終生的
NioEventLoop
在官網可以了解這個這個類的API有更多的信息io.netty.channel
而關于Channel
,其實換成大家容易理解的話的話,那就是由它負責同對端進行網絡通信、注冊和數據操作等功能
A Channel can have a parent depending on how it was created. For instance, a SocketChannel, that was accepted by ServerSocketChannel, will return the ServerSocketChannel as its parent on parent().
The semantics of the hierarchical structure depends on the transport implementation where the Channel belongs to. For example, you could write a new Channel implementation that creates the sub-channels that share one socket connection, as BEEP and SSH do.
一個Channel
可以有一個父Channel
,這取決于它是如何創建的。例如,被ServerSocketChannel
接受的SocketChannel
將返回ServerSocketChannel
作為其parent()
上的父對象。層次結構的語義取決于通道所屬的傳輸實現。
Channel
的抽象類AbstractChannel
中有一個受保護的構造方法,而AbstractChannel
內部有一個pipeline
屬性,Netty在對Channel
進行初始化的時候將該屬性初始化為DefaultChannelPipeline
的實例。?
為什么選擇Netty
同步阻塞I/O(BIO) | 偽異步I/O | 非阻塞I/O (NIO) | 異步I/O (AIO) | |
---|---|---|---|---|
I/O類型(同步) | 同步I/O | 同步I/O | 同步I/O (I/O多路復用) | 異步I/O |
API使用難度 | 簡單 | 簡單 | 非常復雜 | 復雜 |
調試難度 | 簡單 | 簡單 | 復雜 | 復雜 |
可靠性 | 非常差 | 差 | 高 | 高 |
吞吐量 | 低 | 中 | 高 | 高 |
其實在上面的圖中,已經能看出來了,不同的I/O模型,效率,使用難度,吞吐量都是非常重要的,所以選擇的時候,肯定要慎重選擇,而為什么不使用Java原生的呢?
實際上很簡單,1.復雜,2.不好用
對于Java的NIO的類庫和API繁雜使用麻煩,需要熟練掌握Selectol
,ServerSocketChannel
,SocketChannel
,ByteBuffer
?等
JDK NIO的BUG,比如epoll bug,這個BUG會在linux上導致cpu 100%,使得nio server/client不可用,而且在1.7中都沒有解決完這個bug,只不過發生頻率比較低。
而Netty是一個高性能、異步事件驅動的NIO框架,它提供了對TCP、UDP和文件傳輸的支持,作為一個異步NIO框架,Netty的所有IO操作都是異步非阻塞的,通過Future-Listener機制,用戶可以方便的主動獲取或者通過通知機制獲得IO操作結果。
如果小假的內容對你有幫助,請點贊,評論,收藏。創作不易,大家的支持就是我堅持下去的動力!