posts - 167,  comments - 30,  trackbacks - 0

          markdown形式,簡書地址:基于Netty構(gòu)建服務(wù)的基本步驟
          基于netty構(gòu)建服務(wù)的基本步驟
          我們通過netty實(shí)現(xiàn)一個(gè)Http服務(wù)器的功能,來說明通過netty構(gòu)建的Server基本步驟。
          學(xué)習(xí)一個(gè)新的知識(shí)點(diǎn),都是通過Hello world開始的,對(duì)于netty的學(xué)習(xí)寫一個(gè)Hello world程序不像寫其他程序那么簡單,這里涉及很多非常重要的組件,比如ChannelHandler、EeventLoopGroup、ChannelPipeline等,這些組件隨著后續(xù)不斷學(xué)習(xí)再一一分析其實(shí)現(xiàn)原理。
          基于netty構(gòu)建Http服務(wù)器基本步驟實(shí)踐:
          1. 首先我們定義兩個(gè)線程組 也叫做事件循環(huán)組
          EevetLoopGroup bossGroup =  new NioEevetLoopGroup();
          EevetLoopGroup workerGroup =  new NioEevetLoopGroup();
          為什么定義兩個(gè)線程組,實(shí)際上一個(gè)線程組也能完成所需的功能,不過netty建議我們使用兩個(gè)線程組,分別具有不同的職責(zé)。bossGroup目的是獲取客戶端連接,連接接收到之后再將連接轉(zhuǎn)發(fā)給workerGroup去處理。
          2. 定義一個(gè)輕量級(jí)的啟動(dòng)服務(wù)類
           ServerBootstrap serverBootstrap = new ServerBootstrap(); 
              serverBootstrap.group(bossGroup, wokrerGroup).channel(NioServerSocketChannel.class).childHandler(null);
              // 服務(wù)啟動(dòng)后通過綁定到8899端口上,返回ChannelFuture。
              ChannelFuture channelFuture = serverBootstrap.bind(8899).sync();
              channelFuture.channel().closeFuture().sync();   
          3. 通過ChannelPipeline初始化處理器,類似于攔截器Chain,當(dāng)客戶端首次連接后即調(diào)用initChannel方法完成初始化動(dòng)作。
          [示例代碼]
          public class TestServerInitializer extends ChannelInitializer<SocketChannel>{
              // 初始化器,服務(wù)端啟動(dòng)后會(huì)自動(dòng)調(diào)用這個(gè)方法,它是一個(gè)回調(diào)方法。
              @Override protected void initChannel(SocketChannel ch) throws Exception {
                  System.out.println("initChannel invoked "); // 有客戶端連接就會(huì)執(zhí)行.
                  ChannelPipeline channelPipeline = ch.pipeline(); // pipeline一個(gè)管道里面可以有很多的ChannelHandler,相當(dāng)于包含很多個(gè)攔截器。
                  
          // 添加處理器,可以添加多個(gè),并且可以將處理器放到pipeline管道的不同位置上。
                  channelPipeline.addLast("httpServerCodec", new HttpServerCodec()); //HttpServerCodec也是一個(gè)很重要的組件.
                  channelPipeline.addLast("httpServerHandler", new TestHttpServerHandler()); // 自定義處理器
              }
          }
          4. 創(chuàng)建自定義處理器,通常繼承SimpleChannelInboundHandler<T>,  該處理器覆寫channelRead0方法,該方法負(fù)責(zé)請(qǐng)求接入,讀取客戶端請(qǐng)求,發(fā)送響應(yīng)給客戶端。
          [示例代碼]
          public class TestHttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {
              private final String FAVICON_ICO = "/favicon.ico";
              // 讀取客戶端請(qǐng)求,向客戶端響應(yīng)的方法,所以這里要構(gòu)造響應(yīng)返回給客戶端。
              
          // 注意:這里面跟Servlet沒有任何關(guān)系,也符合Servlet規(guī)范,所以不會(huì)涉及到HttpServerltRequest和HttpServeletResponse對(duì)象。
              @Override protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
                  System.out.println("--------------httpserverHandler, remote_address " + ctx.channel().remoteAddress() + ", msg_class:" + msg.getClass());
          //        Thread.sleep(3000); // 休眠5秒鐘,lsof -i:8899 查看TCP連接狀態(tài)
                  if (msg instanceof HttpRequest) {
                      HttpRequest httpRequest = (HttpRequest) msg;
                      URI uri = new URI(httpRequest.uri());
                      System.out.println("請(qǐng)求方法: " + httpRequest.method() + ", 請(qǐng)求path: " + uri.getPath());
                      if (FAVICON_ICO.equals(uri.getPath())) {
                          System.out.println("請(qǐng)求/favicon.ico");
                          return;
                      }
                      // BytBuf:構(gòu)造給客戶端的響應(yīng)內(nèi)容, 制定好編碼
                      ByteBuf byteBuf = Unpooled.copiedBuffer("Hello World", CharsetUtil.UTF_8);
                      // 接下構(gòu)造響應(yīng)對(duì)象
                      FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, byteBuf);
                      response.headers().set(HttpHeaderNames.CONTENT_LENGTH, byteBuf.readableBytes());
                      response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
                      // 調(diào)用flush才會(huì)將內(nèi)容真正返回給客戶端
                      System.out.println("響應(yīng)給客戶端對(duì)象: " + response);
                      ctx.writeAndFlush(response);
                      ctx.channel().closeFuture();
                  }
              }
              //------以下重寫了ChannelInboundHandlerAdapter父類的方法,分析不同事件方法的調(diào)用時(shí)機(jī)------
              @Override public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
                  System.out.println("channel register invoked");
                  super.channelRegistered(ctx);
              }
              @Override public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
                  System.out.println("channel unregister invoked");
                  super.channelUnregistered(ctx);
              }
              @Override public void channelActive(ChannelHandlerContext ctx) throws Exception {
                  System.out.println("channel active invoked");
                  super.channelActive(ctx);
              }
              @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception {
                  System.out.println("channel inactive invoked");
                  super.channelInactive(ctx);
              }
              // TODO 這里執(zhí)行了2次,具體有待分析
              @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
                  System.out.println("channel read complete");
                  super.channelReadComplete(ctx);
              }
              @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
                  System.out.println("exception caught invoked");
                  super.exceptionCaught(ctx, cause);
              }
              @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
                  System.out.println("handler add invoked");
                  super.handlerAdded(ctx);
              }
          }
          這里注意,我使用的netty4.x版本,方法名叫做channelRead0,如果在其他文章中看到是messageReceived方法,則使用的是netty5.x,另外,因netty5.x已被廢棄,故建議都使用netty4.x穩(wěn)定版。
          5. 將步驟1和2整合,寫Main方法啟動(dòng)服務(wù)
          [示例代碼]
          public class TestNettyServer {
              public static void main(String[] args) throws InterruptedException {
                  // 服務(wù)器端可以理解為while(true){}死循環(huán),去不斷的接受請(qǐng)求連接。        EventLoopGroup bossGroup = new NioEventLoopGroup();
                  EventLoopGroup workerGroup = new NioEventLoopGroup();
                  try {
                      ServerBootstrap serverBootstrap = new ServerBootstrap(); // 啟動(dòng)服務(wù)端,這里的處理器都要是多實(shí)例的.            serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new TestServerInitializer());
                      System.out.println("服務(wù)端已啟動(dòng)..");
                      ChannelFuture channelFuture = serverBootstrap.bind(8899).sync();
                      channelFuture.channel().closeFuture().sync();
                  } finally {
                      System.out.println("服務(wù)端shutdown");
                      bossGroup.shutdownGracefully();
                      workerGroup.shutdownGracefully();
                  }
              }
          }
          6. 通過瀏覽器或者curl方式訪問8899端口。
          最后,通過 curl ‘localhost:8899’訪問成功返回Hello World字符串,
          如果TestHttpServerHandler的channelRead0中不加msg instanceof HttpRequest的判斷,則運(yùn)行時(shí)會(huì)拋出如下異常:
          java.io.IOException: Connection reset by peer
              at sun.nio.ch.FileDispatcherImpl.read0(Native Method)
              at sun.nio.ch.SocketDispatcher.read(SocketDispatcher.java:39)
              at sun.nio.ch.IOUtil.readIntoNativeBuffer(IOUtil.java:223)
              at sun.nio.ch.IOUtil.read(IOUtil.java:192)
              at sun.nio.ch.SocketChannelImpl.read(SocketChannelImpl.java:380)
              at io.netty.buffer.PooledUnsafeDirectByteBuf.setBytes(PooledUnsafeDirectByteBuf.java:288)
              at io.netty.buffer.AbstractByteBuf.writeBytes(AbstractByteBuf.java:1100)
              at io.netty.channel.socket.nio.NioSocketChannel.doReadBytes(NioSocketChannel.java:372)
              at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:123)
              at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:644)
              at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:579)
              at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:496)
              at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:458)
              at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:858)
              at io.netty.util.concurrent.DefaultThreadFactory$DefaultRunnableDecorator.run(DefaultThreadFactory.java:138)
              at java.lang.Thread.run(Thread.java:745)
          ```
          debug代碼時(shí)會(huì)發(fā)現(xiàn)有2個(gè)請(qǐng)求執(zhí)行了channelRead0方法,兩個(gè)msg分別是
          --------------httpserverHandler msg:class io.netty.handler.codec.http.DefaultHttpRequest
          --------------httpserverHandler msg:class io.netty.handler.codec.http.LastHttpContent$1
          我們看到第二次請(qǐng)求并不是HttpRequest對(duì)象,所以此處理器無法處理,通過瀏覽器訪問時(shí),瀏覽器會(huì)自動(dòng)的發(fā)起favion.cio圖片的請(qǐng)求,所以可以增加個(gè)判斷如果path是favion.cio則不往下執(zhí)行。
          ![自定義處理器實(shí)現(xiàn)類圖](http://upload-images.jianshu.io/upload_images/3609866-d5f324f513f98fd3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
          TestHttpServerHandler是自己實(shí)現(xiàn)的處理器,繼承了SimpleChannelInboundHandler,SimpleChannelInboundHandler繼承了ChannelInboundHandlerAdapter類。
          **子類可重寫的方法及其含義**:
          `channelActive() `           >在到服務(wù)器的連接已經(jīng)建立之后將被調(diào)用(成為活躍狀態(tài))
          ` channelRead0()`           > 當(dāng)從服務(wù)器接受到一條消息時(shí)被調(diào)用
          ` exceptionCaught()`        >在處理過程中引發(fā)異常時(shí)調(diào)用
          ` channelReigster() `        >注冊(cè)到EventLoop上
          ` handlerAdd() `                >Channel被添加方法
          ` handlerRemoved()`        >Channel被刪除方法
          ` channelInActive() `         > Channel離開活躍狀態(tài),不再連接到某一遠(yuǎn)端時(shí)被調(diào)用
          ` channelUnRegistered()` >Channel從EventLoop上解除注冊(cè)
          ` channelReadComplete()` >當(dāng)Channel上的某個(gè)讀操作完成時(shí)被調(diào)用
          在步驟4中有打印輸出,通過curl ‘http://localhost:8899'訪問,執(zhí)行結(jié)果順序:
          服務(wù)端已啟動(dòng)..
          initChannel invoked...
          handler add invoked
          channel register invoked
          channel active invoked
          --------------httpserverHandler, remote_address /127.0.0.1:50061, msg_class:class io.netty.handler.codec.http.DefaultHttpRequest
          請(qǐng)求方法: GET, 請(qǐng)求path: /
          響應(yīng)給客戶端對(duì)象: DefaultFullHttpResponse(decodeResult: success, version: HTTP/1.1, content: UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeHeapByteBuf(ridx: 0, widx: 11, cap: 33))
          HTTP/1.1 200 OK
          content-length: 11
          content-type: text/plain
          --------------httpserverHandler, remote_address /127.0.0.1:50061, msg_class:class io.netty.handler.codec.http.LastHttpContent$1
          channel read complete   // ?
          channel read complete
          channel inactive invoked
          channel unregister invoked
          基本的hellworld程序已經(jīng)運(yùn)行起來,并且自行實(shí)現(xiàn)的處理器調(diào)用過程通過重寫方法打印也能夠有所了解了。
          這里要注意的是,對(duì)于Netty來說,上層應(yīng)用獲取客戶端請(qǐng)求之后,當(dāng)請(qǐng)求是基于Http1.1協(xié)議的話會(huì)有個(gè)keepalive時(shí)間,比如30秒鐘時(shí)間,如果在這段時(shí)間內(nèi)沒有接受到新的請(qǐng)求則由[服務(wù)端]主動(dòng)關(guān)閉連接。當(dāng)請(qǐng)求是基于Http1.0短連接協(xié)議,請(qǐng)求發(fā)過來之后,服務(wù)器就將這個(gè)連接關(guān)閉掉,上述示例中可以根據(jù)判斷調(diào)用ctx .channel().close()來關(guān)閉連接。
          **基于netty構(gòu)建服務(wù)基本流程總結(jié):**
          1. 創(chuàng)建EventLoopGroup實(shí)例
          2. 通過ServerBootstrap啟動(dòng)服務(wù),bind到一個(gè)端口. 如果是客戶端,則使用Bootstrap,連接主機(jī)和端口. 
          3. 創(chuàng)建ChannelInitializer實(shí)例,通過ChannelPipieline初始化處理器鏈.
          4. 創(chuàng)建ChannelServerHandler實(shí)例,繼承SimpleChannelInboundHandler,重寫channelRead0方法(netty4.x).
          5. 將ChannelServerHandler實(shí)例addLast到ChannelPipeline上.
          6. 將ChannelInitializer實(shí)例childHandler到bootstrap上.

          posted on 2017-05-30 19:26 David1228 閱讀(3294) 評(píng)論(0)  編輯  收藏 所屬分類: Netty

          只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           

          <2017年5月>
          30123456
          78910111213
          14151617181920
          21222324252627
          28293031123
          45678910

          常用鏈接

          留言簿(4)

          隨筆分類

          隨筆檔案

          文章檔案

          新聞分類

          新聞檔案

          相冊(cè)

          收藏夾

          Java

          Linux知識(shí)相關(guān)

          Spring相關(guān)

          云計(jì)算/Linux/虛擬化技術(shù)/

          友情博客

          多線程并發(fā)編程

          開源技術(shù)

          持久層技術(shù)相關(guān)

          搜索

          •  

          積分與排名

          • 積分 - 359238
          • 排名 - 154

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

          主站蜘蛛池模板: 锡林浩特市| 德兴市| 阳西县| 天津市| 屏山县| 开原市| 黄陵县| 兴业县| 承德县| 商河县| 鲁甸县| 昌宁县| 尉犁县| 葵青区| 张北县| 马边| 鲜城| 基隆市| 黄平县| 贺州市| 广饶县| 盐城市| 镇沅| 凤冈县| 新沂市| 九台市| 年辖:市辖区| 徐汇区| 噶尔县| 墨竹工卡县| 五台县| 青河县| 卓尼县| 金乡县| 镇坪县| 永春县| 翁源县| 乐都县| 龙川县| 黑水县| 弥渡县|