1. 程式人生 > >netty入門HttpServer例項

netty入門HttpServer例項

好久沒更了,由於目前專案要用到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資料結構

他有三種使用模式:

  1. Heap Buffer 堆緩衝區
    堆緩衝區是ByteBuf最常用的模式,他將資料儲存在堆空間。
  2. Direct Buffer 直接緩衝區
    直接緩衝區是ByteBuf的另外一種常用模式,他的記憶體分配都不發生在堆,jdk1.4引入的nio的ByteBuffer類允許jvm通過本地方法呼叫分配記憶體,這樣做有兩個好處
    • 通過免去中間交換的記憶體拷貝, 提升IO處理速度; 直接緩衝區的內容可以駐留在垃圾回收掃描的堆區以外。
    • DirectBuffer 在 -XX:MaxDirectMemorySize=xxM大小限制下, 使用 Heap 之外的記憶體, GC對此”無能為力”,也就意味著規避了在高負載下頻繁的GC過程對應用執行緒的中斷影響.
  3. 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);
   }

}

執行主類,在瀏覽器中看到:

在控制檯看到: