netty入門HttpServer例項
阿新 • • 發佈:2018-12-06
好久沒更了,由於目前專案要用到websocket,於是看了下netty。Netty是由JBOSS提供的一個java開源框架。Netty提供非同步的、事件驅動的網路應用程式框架和工具,用以快速開發高效能、高可靠性的網路伺服器和客戶端程式。 也就是說,Netty 是一個基於NIO的客戶、伺服器端程式設計框架,使用Netty 可以確保你快速和簡單的開發出一個網路應用,例如實現了某種協議的客戶、服務端應用。
1、回顧BIO、NIO、AIO
關於這幾個,估計在面試中有很大可能會被問到。我首先解釋一下,然後舉例說明。
BIO:同步阻塞
NIO:同步非阻塞
AIO:非同步非阻塞
專業說明:
通俗舉例:
2、netty的三大概念
這裡引用原文:https://www.jianshu.com/p/b9f3f6a16911描述得非常清楚了。
-
Channel
資料傳輸流,與channel相關的概念有以下四個,上一張圖讓你瞭解netty裡面的Channel。Channel一覽
- Channel,表示一個連線,可以理解為每一個請求,就是一個Channel。
- ChannelHandler,核心處理業務就在這裡,用於處理業務請求。
- ChannelHandlerContext,用於傳輸業務資料。
- ChannelPipeline,用於儲存處理過程需要用到的ChannelHandler和ChannelHandlerContext。
- ByteBuf
ByteBuf是一個儲存位元組的容器,最大特點就是使用方便,它既有自己的讀索引和寫索引,方便你對整段位元組快取進行讀寫,也支援get/set,方便你對其中每一個位元組進行讀寫,他的資料結構如下圖所示:
ByteBuf資料結構
他有三種使用模式:
- Heap Buffer 堆緩衝區
堆緩衝區是ByteBuf最常用的模式,他將資料儲存在堆空間。 - Direct Buffer 直接緩衝區
直接緩衝區是ByteBuf的另外一種常用模式,他的記憶體分配都不發生在堆,jdk1.4引入的nio的ByteBuffer類允許jvm通過本地方法呼叫分配記憶體,這樣做有兩個好處- 通過免去中間交換的記憶體拷貝, 提升IO處理速度; 直接緩衝區的內容可以駐留在垃圾回收掃描的堆區以外。
- DirectBuffer 在 -XX:MaxDirectMemorySize=xxM大小限制下, 使用 Heap 之外的記憶體, GC對此”無能為力”,也就意味著規避了在高負載下頻繁的GC過程對應用執行緒的中斷影響.
- Composite Buffer 複合緩衝區
複合緩衝區相當於多個不同ByteBuf的檢視,這是netty提供的,jdk不提供這樣的功能。
- Codec
Netty中的編碼/解碼器,通過他你能完成位元組與pojo、pojo與pojo的相互轉換,從而達到自定義協議的目的。
在Netty裡面最有名的就是HttpRequestDecoder和HttpResponseEncoder了。
3、HttpServer例項
直接上程式碼了,直接看main方法,步驟都註釋了。具體工程請自行用IDE建立。
主類HelloHttpServer.java
package com.soleil.netty; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; /** * @Description: 實現客戶端傳送一個請求,伺服器會返回 hello netty */ public class HelloServer { public static void main(String[] args) throws Exception { // 定義一對執行緒組 // 主執行緒組, 用於接受客戶端的連線,但是不做任何處理,跟老闆一樣,不做事 EventLoopGroup bossGroup = new NioEventLoopGroup(); // 從執行緒組, 老闆執行緒組會把任務丟給他,讓手下執行緒組去做任務 EventLoopGroup workerGroup = new NioEventLoopGroup(); try { // netty伺服器的建立, ServerBootstrap 是一個啟動類 ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup, workerGroup) // 設定主從執行緒組 .channel(NioServerSocketChannel.class) // 設定nio的雙向通道 .childHandler(new HelloServerInitializer()); // 子處理器,用於處理workerGroup // 啟動server,並且設定8088為啟動的埠號,同時啟動方式為同步 ChannelFuture channelFuture = serverBootstrap.bind(8088).sync(); // 監聽關閉的channel,設定位同步方式 channelFuture.channel().closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } } |
伺服器初始類HelloServerInitializer.java
package com.soleil.netty; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.http.HttpServerCodec; /** * @Description: 初始化器,channel註冊後,會執行裡面的相應的初始化方法 */ public class HelloServerInitializer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel channel) throws Exception { // 通過SocketChannel去獲得對應的管道 ChannelPipeline pipeline = channel.pipeline(); // 通過管道,新增handler // HttpServerCodec是由netty自己提供的助手類,可以理解為攔截器 // 當請求到服務端,我們需要做解碼,響應到客戶端做編碼 pipeline.addLast("HttpServerCodec", new HttpServerCodec()); // 新增自定義的助手類,返回 "hello netty~" pipeline.addLast("customHandler", new CustomHandler()); } } |
助手類CustomHandler.java
package com.soleil.netty; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpObject; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; import io.netty.util.CharsetUtil; /** * @Description: 建立自定義助手類 */ // SimpleChannelInboundHandler: 對於請求來講,其實相當於[入站,入境] public class CustomHandler extends SimpleChannelInboundHandler<HttpObject> { @Override protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception { // 獲取channel Channel channel = ctx.channel(); if (msg instanceof HttpRequest) { // 顯示客戶端的遠端地址 System.out.println(channel.remoteAddress()); // 定義傳送的資料訊息 ByteBuf content = Unpooled.copiedBuffer("Hello netty~", CharsetUtil.UTF_8); // 構建一個http response FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content); // 為響應增加資料型別和長度 response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain"); response.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes()); // 把響應刷到客戶端 ctx.writeAndFlush(response); } } @Override public void channelRegistered(ChannelHandlerContext ctx) throws Exception { System.out.println("channel。。。註冊"); super.channelRegistered(ctx); } @Override public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { System.out.println("channel。。。移除"); super.channelUnregistered(ctx); } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { System.out.println("channel。。。活躍"); super.channelActive(ctx); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { System.out.println("channel。。。不活躍"); super.channelInactive(ctx); } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { System.out.println("channeld讀取完畢。。。"); super.channelReadComplete(ctx); } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { System.out.println("使用者事件觸發。。。"); super.userEventTriggered(ctx, evt); } @Override public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception { System.out.println("channel可寫更改"); super.channelWritabilityChanged(ctx); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { System.out.println("捕獲到異常"); super.exceptionCaught(ctx, cause); } @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { System.out.println("助手類新增"); super.handlerAdded(ctx); } @Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { System.out.println("助手類移除"); super.handlerRemoved(ctx); } } |
執行主類,在瀏覽器中看到:
在控制檯看到: