一、Netty入門

一、概述

Netty是一個異步的,基於事件驅動的,網絡應用框架;

avatar

目前大多使用netty 4,netty 5被廢棄了

二、大綱

  • Netty入門

二、入門示例

1. 示例

開發一個簡單的服務器,綁定端口8899,服務啓動後每次訪問都返回helloworld字符串

  • Server:入口
package com.learner.netty.ch1;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

/**
 * @Desc:
 * @author: [email protected]
 * @date: 2019-11-28
 */
public class Server {

    public static void main(String[] args) throws InterruptedException {
        // boosGroup線程組負責獲取連接,轉接給workGroup,workGroup處理連接
        EventLoopGroup boosGroup = new NioEventLoopGroup();
        EventLoopGroup workGroup = new NioEventLoopGroup();

        try {
            // ServerBootstrap用於簡化啓動netty服務端
            ServerBootstrap serverBootstrap = new ServerBootstrap();

            // 將boosGroup和workGroup進行綁定,利用反射創建通道,綁定子處理器
            serverBootstrap.group(boosGroup, workGroup)
                    .channel(NioServerSocketChannel.class)  // 反射創建實例
                    .childHandler(new ServerInitializer());   // 子處理器,自己的請求處理服務器,用於處理請求

            // 綁定端口
            ChannelFuture channelFuture = serverBootstrap.bind(8899).sync();
            channelFuture.channel().closeFuture().sync();
        } finally {
            boosGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }
}
  • ServerInitializer:服務初始化
package com.learner.netty.ch1;

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http2.Http2Codec;

/**
 * @Desc: 初始化管道
 * @author: [email protected]
 * @date: 2019-11-28
 */
public class ServerInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        // pipeline是一個管道,其中有很多的channelHandle(相當於攔截器),用於處理不同的事務
        ChannelPipeline pipeline = ch.pipeline();

        pipeline.addLast("serverCodec", new HttpServerCodec());
        pipeline.addLast("channelHandle", new ChannelHandle());
    }
}
  • ChannelHandle:自定義處理器
package com.learner.netty.ch1;

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;

/**
 * @Desc: 自定義的handle,用於處理自己的業務,需要繼承SimpleChannelInboundHandler
 * @author: [email protected]
 * @date: 2019-11-28
 */
public class ChannelHandle extends SimpleChannelInboundHandler<HttpObject> {

    /**
     * 用於獲取請求,並進行處理,最後返回響應
     * @author: [email protected]
     * @date: 2019/11/28 9:36 下午
     * @param ctx
     * @param msg
     * @return void
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {

        System.out.println("請求處理");

        if (msg instanceof HttpRequest) {
            HttpRequest request = (HttpRequest) msg;

            // 獲取請求類型
            System.out.println("請求類型:"+request.method().name());

            // 獲取訪問地址
            System.out.println("請求地址:"+request.uri());

            // 1. 構建ByteBuf
            ByteBuf content = Unpooled.copiedBuffer("Hello, Netty 學習!", CharsetUtil.UTF_8);
            // 2. 根據ByteBuf構建Response
            FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
                    HttpResponseStatus.OK, content);
            // 3. 設置響應頭
            response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
            response.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes());
            // 4. 返回
            ctx.writeAndFlush(response);
        }

    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        super.handlerAdded(ctx);
        System.out.println("1. handle added");
    }

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        super.channelRegistered(ctx);
        System.out.println("2. channel registered");
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        super.channelActive(ctx);
        System.out.println("3. channel active");
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        super.channelInactive(ctx);
        System.out.println("4. channel inactive");
    }

    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        super.channelUnregistered(ctx);
        System.out.println("5. channel unregistered");
    }
}
  • 請求示例
curl 'http://localhost:8899'
  • 返回示例:
Hello, Netty 學習!

三、開發流程

上面的入門示例的開發流程總結如下:

  1. 創建兩個EventLoopGroup線程組:boos,work
// boosGroup線程組負責獲取連接,轉接給workGroup,workGroup處理連接
EventLoopGroup boosGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();

boos負責獲取連接,work負責處理連接

  1. 創建ServerBootStrap實例,用於綁定boss和work,創建channel,綁定handle,綁定端口,啓動服務,最後優雅的關閉服務
// 將boosGroup和workGroup進行綁定,利用反射創建通道,綁定子處理器
serverBootstrap.group(boosGroup, workGroup)
        .channel(NioServerSocketChannel.class)  // 反射創建實例
        .childHandler(new ServerInitializer());   // 子處理器,自己的請求處理服務器,用於處理請求
// 綁定端口
ChannelFuture channelFuture = serverBootstrap.bind(8899).sync();
channelFuture.channel().closeFuture().sync();
boosGroup.shutdownGracefully();
workGroup.shutdownGracefully();

3.繼承ChannelInitializer創建Initializer,重寫initChannel

@Override
protected void initChannel(SocketChannel ch) throws Exception {
    // pipeline是一個管道,其中有很多的channelHandle(相當於攔截器),用於處理不同的事務
    ChannelPipeline pipeline = ch.pipeline();

    pipeline.addLast("serverCodec", new HttpServerCodec());
    pipeline.addLast("channelHandle", new ChannelHandle());
}

ChannelPipeline是一個管道,裏邊可以添加很多的handle,每個handle可以處理不同的事務

  1. 繼承SimpleChannelInboundHandler,創建自己的ChannelHandle,重寫channelRead0()用於處理自己的業務
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {

    System.out.println("請求處理");

    if (msg instanceof HttpRequest) {
        HttpRequest request = (HttpRequest) msg;

        // 獲取請求類型
        System.out.println("請求類型:"+request.method().name());

        // 獲取訪問地址
        System.out.println("請求地址:"+request.uri());

        // 1. 構建ByteBuf
        ByteBuf content = Unpooled.copiedBuffer("Hello, Netty 學習!", CharsetUtil.UTF_8);
        // 2. 根據ByteBuf構建Response
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
                HttpResponseStatus.OK, content);
        // 3. 設置響應頭
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
        response.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes());
        // 4. 返回
        ctx.writeAndFlush(response);
    }

}

四、Netty執行流程

1. 總覽

在自定義的handle中可以重寫handlerAdded,channelRegistered,channelActive,channelInactive,channelUnregistered這五個方法,並進行打印,用來分析Netty的執行流程

package com.learner.netty.ch1;

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;

/**
 * @Desc: 自定義的handle,用於處理自己的業務,需要繼承SimpleChannelInboundHandler
 * @author: [email protected]
 * @date: 2019-11-28
 */
public class ChannelHandle extends SimpleChannelInboundHandler<HttpObject> {

    /**
     * 用於獲取請求,並進行處理,最後返回響應
     * @author: [email protected]
     * @date: 2019/11/28 9:36 下午
     * @param ctx
     * @param msg
     * @return void
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {

        System.out.println("請求處理");

        if (msg instanceof HttpRequest) {
            HttpRequest request = (HttpRequest) msg;

            // 獲取請求類型
            System.out.println("請求類型:"+request.method().name());

            // 獲取訪問地址
            System.out.println("請求地址:"+request.uri());

            // 1. 構建ByteBuf
            ByteBuf content = Unpooled.copiedBuffer("Hello, Netty 學習!", CharsetUtil.UTF_8);
            // 2. 根據ByteBuf構建Response
            FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
                    HttpResponseStatus.OK, content);
            // 3. 設置響應頭
            response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
            response.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes());
            // 4. 返回
            ctx.writeAndFlush(response);
        }

    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        super.handlerAdded(ctx);
        System.out.println("1. handle added");
    }

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        super.channelRegistered(ctx);
        System.out.println("2. channel registered");
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        super.channelActive(ctx);
        System.out.println("3. channel active");
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        super.channelInactive(ctx);
        System.out.println("4. channel inactive");
    }

    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        super.channelUnregistered(ctx);
        System.out.println("5. channel unregistered");
    }
}

啓動服務,查看執行結果:

> Task :Server.main()
1. handle added
2. channel registered
3. channel active
請求處理
請求類型:GET
請求地址:/
請求處理
4. channel inactive
5. channel unregistered

可以看到其執行流程是:

  1. handle added
  2. channel registered
  3. channel active
  4. 處理業務邏輯
  5. channel inactive
  6. channel unregistered
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章