基于Netty的简单HTTP服务例子

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> 基于Netty的简单HTTP服务例子

作者:emacsist
链接:https://emacsist.github.io

例子是基于 Netty 4.1.25.Final + Spring Boot 2 + JDK 1.8 + Maven

demo:

https://github.com/emacsist/netty-http-demo

AppInitializer.java


package io.github.emacsist.netty.httpdemo.config;
import io.github.emacsist.netty.httpdemo.handler.AppHandler;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
 * @author emacsist
 */
@Component
@ChannelHandler.Sharable
public class AppInitializer extends ChannelInitializer {
    private static final int MB = 1024 * 1024;
    @Autowired
    private AppHandler appHandler;
    @Override
    protected void initChannel(final Channel channel) {
        final ChannelPipeline p = channel.pipeline();
        p.addLast(new HttpServerCodec());
        p.addLast(new HttpObjectAggregator(1 * MB));
        p.addLast(appHandler);
    }
}

AppHandler.java


package io.github.emacsist.netty.httpdemo.handler;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.util.AsciiString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
 * @author emacsist
 */
@Component
@ChannelHandler.Sharable
public class AppHandler extends SimpleChannelInboundHandlerFullHttpRequest {
    private static final Logger log = LoggerFactory.getLogger(AppHandler.class);
    private static final AsciiString CONTENT_TYPE = AsciiString.cached("Content-Type");
    private static final AsciiString TEXT_PLAIN = AsciiString.cached("text/plain; charset=utf-8");
    private static final AsciiString CONTENT_LENGTH = AsciiString.cached("Content-Length");
    @Override
    protected void channelRead0(final ChannelHandlerContext ctx, final FullHttpRequest fullHttpRequest) {
        final String uri = fullHttpRequest.uri();
        final QueryStringDecoder queryStringDecoder = new QueryStringDecoder(uri);
        final String requestPath = queryStringDecoder.path();
        String body = "";
        switch (requestPath) {
            case "/hello":
                log.info("in hello = {}", queryStringDecoder.parameters());
                //请自行检测参数, 这里假设  /hello 是会带上 ?name=world 类似这参数值的
                body = "Hello " + queryStringDecoder.parameters().get("name").get(0);
                break;
            case "/netty":
                log.info("in netty = {}", queryStringDecoder.parameters());
                body = "Hello Netty.";
                break;
            default:
                break;
        }
        final DefaultFullHttpResponse defaultFullHttpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.copiedBuffer(body.getBytes()));
        defaultFullHttpResponse.headers().set(CONTENT_TYPE, TEXT_PLAIN);
        defaultFullHttpResponse.headers().set(CONTENT_LENGTH, defaultFullHttpResponse.content().readableBytes());
        ctx.write(defaultFullHttpResponse);
    }
    @Override
    public void channelReadComplete(final ChannelHandlerContext ctx) {
        ctx.flush();
    }
    @Override
    public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) {
        log.error(cause.getMessage(), cause);
        ctx.close();
    }
}

HttpDemoApplication.java


package io.github.emacsist.netty.httpdemo;
import io.github.emacsist.netty.httpdemo.config.AppInitializer;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
 * @author emacsist
 */
@SpringBootApplication
public class HttpDemoApplication implements CommandLineRunner {
    @Value("${server.port}")
    private int port;
    private static final int KB = 1024;
    @Autowired
    private AppInitializer appInitializer;
    public static void main(final String[] args) {
        SpringApplication.run(HttpDemoApplication.class, args);
    }
    @Override
    public void run(final String... args) {
        final ServerBootstrap serverBootstrap = new ServerBootstrap();
        final EventLoopGroup master = new NioEventLoopGroup();
        final EventLoopGroup worker = new NioEventLoopGroup();
        try {
            serverBootstrap
                    .option(ChannelOption.SO_BACKLOG, 4 * KB)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .group(master, worker)
                    .channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.DEBUG))
                    .childHandler(appInitializer);
            final Channel ch = serverBootstrap.bind(port).sync().channel();
            System.out.println("start app ok...");
            ch.closeFuture().sync();
        } catch (final InterruptedException e) {
            //ignore
        } finally {
            master.shutdownGracefully();
            worker.shutdownGracefully();
            System.out.println("stop app ok...");
        }
    }
}

注意

编写自己的HTTP业务时, 请记住千万不要阻塞EventLoop线程~, 不然会导致 Netty 的性能急剧下降.

当要处理一些阻塞操作时, 请用其他的线程池来处理。

在Netty中一般有两种方式:

ChannelPipeline 指定 executor:

例如这样子:


/**
 * @author emacsist
 */
@Component
@ChannelHandler.Sharable
public class AppInitializer extends ChannelInitializer {
    private static final int MB = 1024 * 1024;
    @Autowired
    private AppHandler appHandler;
    private static final DefaultEventExecutorGroup executorGroup = new DefaultEventExecutorGroup(Runtime.getRuntime().availableProcessors() * 2);
    @Override
    protected void initChannel(final Channel channel) {
        final ChannelPipeline p = channel.pipeline();
        p.addLast(new HttpServerCodec());
        p.addLast(new HttpObjectAggregator(1 * MB));
        p.addLast(executorGroup, appHandler);
    }
}

创建一个全局的 ExecutorGroup , 自己手工创建任务


executorGroup.submit(() - maxService.dealClick(queryStringDecoder));

这个是公司项目代码的一个片段, 要将HTTP参数 接收- 解密 - 进队和处理 Redis, 这些都是阻塞操作来的, 所以这里创建了一个异步任务来处理这些耗时的操作, 以免阻塞 EventLoop 线程.

性能

经过测试, 还是手工这种按需处理异步任务的性能更高点. 差不多高1倍. 当然, 具体问题要具体分析, 总之就是不要阻塞 EventLoop 线程就好, 毕竟Netty高性能的核心, 就在于不要阻塞 EventLoop.

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> 基于Netty的简单HTTP服务例子


 上一篇
精尽 Netty 原理与源码专栏( 已经完成 61+ 篇,预计总共 70+ 篇 ) 精尽 Netty 原理与源码专栏( 已经完成 61+ 篇,预计总共 70+ 篇 )
**只更新在笔者的知识星球,欢迎加入一起讨论 Netty 源码与实现**。 目前已经有 **1000+** 位球友加入… 进度:已经完成 **60+** 篇,预计总共 70+ 篇,完成度 **90%** 。 目前已经有 1
2021-04-05
下一篇 
看 Netty 在 Dubbo 中如何应用 看 Netty 在 Dubbo 中如何应用
点击上方“Java知音”,选择“置顶公众号” 技术文章第一时间送达! 作者:莫那 鲁道 cnblogs.com/stateis0/p/9062171.html 目录 dubbo 的 Consumer 消费者如何使用 Nett
2021-04-05