Netty由淺到深_第四章_ Netty異步模型

基本介紹

  • 1)異步的概念和同步相對。當一個異步過程調用發出後,調用者不能立刻得到結果。實際處理這個調用的組件完成後,通過狀態、通知和回調來通知調用者

  • 2)netty中的I/O操作是異步的,包括Bind、Write、Connect等操作會簡單的返回一個ChannelFuture

  • 3)調用者不能立刻獲得結果。而是通過Future-Listener機制,用戶可以方便的主動獲取或者通過通知機制獲得I/O操作

  • 4)Netty的異步模型是建立在future和callback的之上的。Callback就是回調。重點說future,他的核心思想是:假設一個方法fun,計算過程可能非常耗時,等待fun返回明顯不太合適。那麼可以在調用fun方法的時候,立馬返回一個future,後續可以通過future去監控方法fun的處理過程(即Future
    -Listener)。

Future說明

  • 1)表示異步的執行結果,可以通過他提供的方法來檢測執行是否完成,比如檢索計算等等

  • 2)ChannelFuture是一個接口:public interface ChannelFuture extends
    Future 我們可以添加監聽器,當監聽的事件發生時,會通知。

  • 3)工作原理示意圖
    在這裏插入圖片描述

  • 說明:
    在使用Netty進行編程時,攔截操作和轉換出入棧數據只需要您提供,callback或利用future即可。這使得鏈式操作、簡單、高效,並有利於編碼可重用的、通用的代碼

  • 4.Future -Listener機制

    • 當Future對象剛剛創建時,處於非完成狀態,調用者可以通過返回channelFuture來獲取操作執行的狀態,註冊監聽函數來執行完成後的操作
      常見的操作有:
      通過isDone方法來判斷當前的操作是否完成
      通過isSuccess方法來判斷當前的操作是否成功
      通過getCause方法來判斷當前的操作失敗的原因
      通過isCancelled方法來判斷已完成的當前操作是否被取消
      通過addListener方法來註冊監聽器,當操作已完成(isDone方法返回完成),將會通知指定的監聽器;如果Future對象已完成,則通知指定的監聽器

舉例說明

  • 演示:綁定端口是異步操作,當綁定操作處理完成後,將會調用響應的監聽器處理邏輯
//給cf註冊監聽器,監控我們關心的事件
cf.addListener(new ChannelFutureListener() {
    @Override
    public void operationComplete(ChannelFuture channelFuture) throws Exception {
        if (cf.isSuccess()){
            System.out.println("監聽端口6668,成功");
        }else{
            System.out.println("監聽失敗");
        }
    }
});

快速入門實列-HTTP服務

  • 1)Netty服務器在6668端口監聽,瀏覽器發送請求http://localhost:6668/
  • 2)服務器可以回覆消息給客戶端“hello 我是服務器”,並對特定請求資源進行過濾
  • 3)目的:Netty可以做HTTP服務開發,並理解handler實例和客戶端及其請求的關係
  • 4)代碼如下
package com.dd.netty.http;

import com.dd.netty.simple.NettyServerHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class TestServer {
    public static void main(String[] args) {
        //創建bossGroup 和 WorkerGroup
        //bossGroup只處理連接請求,真正的客戶端業務處理,會交給workerGroup完成
        //兩個都是無限循環
        //bossgroup和workergroup含有的子線程(NIOeventloop)的個數   默認爲cpu核數乘以2
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        NioEventLoopGroup woekerGroup = new NioEventLoopGroup();
        try {

            //創建服務器端啓動的對象
            ServerBootstrap bootstrap = new ServerBootstrap();

            bootstrap.group(bossGroup, woekerGroup)//設置兩個線程組
                    .channel(NioServerSocketChannel.class)//使用NIOsocketChannel實現服務器通道
                    .option(ChannelOption.SO_BACKLOG, 128)//設置線程隊列得到連接的個數
                    .childOption(ChannelOption.SO_KEEPALIVE, true)//設置保持活動連接狀態
                    .childHandler(new TestServerInitializer());//給WorkerGoop的 EventLoop 對應的管道設置處理器

            //綁定一個端口並且同步處理,生成一個ChannelFuture,並啓動服務器
            ChannelFuture cf = bootstrap.bind(16669).sync();

            //給cf註冊監聽器,監控我們關心的事件
            cf.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture channelFuture) throws Exception {
                    if (cf.isSuccess()){
                        System.out.println("監聽端口16669,成功");
                    }else{
                        System.out.println("監聽失敗");
                    }
                }
            });

            //對關閉通道進行監聽
            cf.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            woekerGroup.shutdownGracefully();
        }

    }
}

package com.dd.netty.http;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpServerCodec;

public class TestServerInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {

        //向管道加入處理器

        //得到管道
        ChannelPipeline pipeline = socketChannel.pipeline();

        //加入Netty提供的HttpServerCodec     codec=>[code decoder]
        pipeline.addLast("myHttpServerCodec",new HttpServerCodec());

        //增加自定義的handler
        pipeline.addLast("myTestHttpServerHandler",new TestHttpServerHandler());
    }
}

package com.dd.netty.http;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;

import java.net.URI;

/*
說明:他是ChannelInboundHandlerAdapter的子類
HttpObject表示客戶端和服務器端相互通訊的數據被封裝成HttpObject
 */
public class TestHttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {

    /*
    當有讀取事件會觸發這個方法   讀取客戶端數據
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {

        //判斷msg是不是http請求
        if (msg instanceof HttpRequest){

            System.out.println("pipleline hashcode"+ctx.pipeline().hashCode()+"testHttpServerHandler hashcode"+this.hashCode());


            System.out.println("msg類型"+msg.getClass());

            System.out.println("客戶端地址"+ctx.channel().remoteAddress());

            //獲取請求,或濾掉網站圖標訪問
            HttpRequest httpRequest = (HttpRequest)msg;
            URI uri = new URI(httpRequest.uri());
            if ("/favicon.ico".equals(uri.getPath())){
                System.out.println("請求了 favicon.ico ,不做響應");
                return;
            }

            //回覆信息給瀏覽器 [http協議]
            ByteBuf content = Unpooled.copiedBuffer("hello,我是服務器",CharsetUtil.UTF_8);

            //構造一個http協議,即httpResponse
            FullHttpResponse reponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content);

            reponse.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/plain;charset=UTF-8");
            reponse.headers().set(HttpHeaderNames.CONTENT_LENGTH,content.readableBytes());

            //將構建好的response返回
            ctx.writeAndFlush(reponse);
        }
    }
}

  • 5)發現訪問一次地址,會發送兩次請求
    在這裏插入圖片描述
    在這裏插入圖片描述
    在這裏插入圖片描述
//獲取請求,或濾掉網站圖標訪問
HttpRequest httpRequest = (HttpRequest)msg;
URI uri = new URI(httpRequest.uri());
if ("/favicon.ico".equals(uri.getPath())){
    System.out.println("請求了 favicon.ico ,不做響應");
    return;
}
  • 注意:每個瀏覽器請求該地址,都會分別生成自己對應的piple,handler。由於爲HTTP協議,它不是長連接,刷新瀏覽器會產生新的piple,handler。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章